package main import ( "context" "crypto/rand" "encoding/base64" "fmt" "log" "net/http" "os" "time" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt" "github.com/google/go-github/v50/github" "github.com/joho/godotenv" "golang.org/x/oauth2" "gorm.io/driver/mysql" "gorm.io/gorm" ) var db *gorm.DB var jwtSecret = []byte("JWT_SECRET") var oauthConf *oauth2.Config func main() { err := godotenv.Load() if err != nil { log.Fatal("Error loading .env file") } oauthConf = &oauth2.Config{ ClientID: os.Getenv("GITHUB_CLIENT_ID"), ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"), // Scopes: []string{"read:user", "user:email"}, Endpoint: oauth2.Endpoint{ AuthURL: "https://github.com/login/oauth/authorize", TokenURL: "https://github.com/login/oauth/access_token", }} initDB() router := gin.Default() router.Use(cors.New(cors.Config{ AllowOrigins: []string{"*"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"}, AllowHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, AllowCredentials: true, })) router.GET("/", func(ctx *gin.Context) { ctx.Writer.WriteString("hello world") }) router.GET("/auth/github", githubLoginHandler) router.GET("/auth/github/callback", githubCallbackHandler) router.Run(":8000") } func githubLoginHandler(c *gin.Context) { // state := generateState() // code := generateCode() // err := storeStateToDB(state, code) // if err != nil { // log.Println("Error storing state to DB:", err) // c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "Internal server error"}) // return // } url := oauthConf.AuthCodeURL("state") log.Println(url) c.Redirect(http.StatusFound, url) } func githubCallbackHandler(c *gin.Context) { state := c.Query("state") code := c.Query("code") log.Println(state, code) // if !verifyState(state, code) { // c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"message": "Invalid state"}) // return // } // 使用 code 换取 token token, err := oauthConf.Exchange(context.Background(), code) if err != nil { // c.String(http.StatusBadRequest, "授权失败: %s", err.Error()) c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": fmt.Errorf("授权失败: %s", err.Error())}) return } log.Println("token:", token) // 使用 token 获取 GitHub 用户信息 // client := github.NewClient(oauthConf.Client(context.Background(), token)) client := github.NewClient(oauth2.NewClient(context.Background(), oauth2.StaticTokenSource(token))) user, _, err := client.Users.Get(c.Request.Context(), "") if err != nil { // c.String(http.StatusInternalServerError, "获取用户信息失败: %s", err.Error()) c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": fmt.Errorf("获取用户信息失败: %s", err.Error())}) return } // err = storeUserToDB(user) // if err != nil { // log.Println("Error storing user to DB:", err) // c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "Internal server error"}) // return // } log.Printf("%#v\n", user) log.Println(user.GetEmail(), user.GetName(), user.GetID(), user.GetAvatarURL()) // 处理用户信息 c.JSON(http.StatusOK, gin.H{ "login": user.Login, "name": user.Name, "email": user.Email, "location": user.Location, "scopes": token.Extra("scope"), }) // jwtToken, err := generateJWTToken(*user.ID) // if err != nil { // log.Println("Error generating JWT token:", err) // c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "Internal server error"}) // return // } // c.SetCookie("token", jwtToken, 60*60*24, "/", "localhost", false, true) // c.Redirect(http.StatusFound, "http://152.70.110.4:8000") } func initDB() { dsn := os.Getenv("MYSQL_DSN") db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { log.Fatal("Error opening database:", err) } // db.AutoMigrate() sqlDB, err := db.DB() if err != nil { log.Fatal("Error connecting to database:", err) } sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) log.Println("Database connection established") } func generateState() string { return generateRandomString(20) } func generateCode() string { return generateRandomString(20) } func generateRandomString(length int) string { byteArr := make([]byte, length) _, err := rand.Read(byteArr) if err != nil { log.Fatal("Error generating random string:", err) } return base64.URLEncoding.EncodeToString(byteArr) } func storeStateToDB(state, code string) error { query := "INSERT INTO oauth_state (state, code) VALUES (?, ?)" err := db.Exec(query, state, code).Error if err != nil { return err } return nil } func verifyState(state, code string) bool { query := "SELECT COUNT(*) FROM oauth_state WHERE state = ? AND code = ?" row := db.Exec(query, state, code) var count int err := row.Scan(&count) if err != nil { log.Println("Error verifying state:", err) return false } if count == 0 { return false } return true } func storeUserToDB(user *github.User) error { query := "INSERT INTO users (id, login, email) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE login = VALUES(login), email = VALUES(email)" err := db.Exec(query, user.GetID(), user.GetLogin(), user.GetEmail()).Error if err != nil { return err } return nil } func generateJWTToken(userID int64) (string, error) { token := jwt.New(jwt.SigningMethodHS256) claims := token.Claims.(jwt.MapClaims) claims["user"] = userID claims["exp"] = time.Now().Add(time.Hour * 24).Unix() jwtToken, err := token.SignedString(jwtSecret) if err != nil { return "", err } return jwtToken, nil } func verifyJWTToken(tokenString string) { // 验证 token tokenValue, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { // 该函数用于验证令牌的签名方法是否正确 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } return jwtSecret, nil }) if err != nil { fmt.Println("Error parsing token string:", err) return } // 获取声明信息 if claims, ok := tokenValue.Claims.(jwt.MapClaims); ok && tokenValue.Valid { // fmt.Println("Authorized:", claims["authorized"]) fmt.Println("User:", claims["user"]) fmt.Println("Expiration time:", time.Unix(int64(claims["exp"].(float64)), 0)) } else { fmt.Println("Invalid token.") } }