Files
api-proxy/getway/main.go
2023-03-29 02:40:21 +08:00

237 lines
5.9 KiB
Go

package main
import (
"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"
ogithub "golang.org/x/oauth2/github"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var db *gorm.DB
var jwtSecret = []byte("JWT_SECRET")
func main() {
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
initDB()
router := gin.Default()
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:8000"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"},
AllowHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
AllowCredentials: true,
}))
router.GET("/auth/github", githubLoginHandler)
router.GET("/auth/github/callback", githubCallbackHandler)
router.Run(":8000")
}
func githubLoginHandler(c *gin.Context) {
state := "1234567890"
// 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
// }
oauthConfig := &oauth2.Config{
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
RedirectURL: "http://localhost:8000/auth/github/callback",
Scopes: []string{"user:email"},
Endpoint: ogithub.Endpoint,
}
url := oauthConfig.AuthCodeURL(state)
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
// }
oauthConfig := &oauth2.Config{
ClientID: os.Getenv("GITHUB_CLIENT_ID"),
ClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
RedirectURL: "http://localhost:8000/auth/github/callback",
Scopes: []string{"user:email"},
Endpoint: ogithub.Endpoint,
}
token, err := oauthConfig.Exchange(c.Request.Context(), code)
if err != nil {
log.Println("Error exchanging token:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "Internal server error"})
return
}
client := github.NewClient(oauthConfig.Client(c.Request.Context(), token))
user, _, err := client.Users.Get(c.Request.Context(), "")
if err != nil {
log.Println("Error getting user:", err)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"message": "Internal server 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
}
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://localhost:8080/")
}
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.")
}
}