This commit is contained in:
Sakurasan
2023-04-10 02:27:52 +08:00
parent 31341cf5bf
commit 0348d383a3
11 changed files with 123 additions and 318 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.env
docker_tag.sh
tests/

View File

@@ -11,6 +11,8 @@ import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/google/go-github/v50/github"
@@ -42,6 +44,9 @@ func main() {
router := gin.Default()
store := cookie.NewStore([]byte("secret"))
router.Use(sessions.Sessions("my-session", store))
router.Use(cors.New(cors.Config{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD"},
@@ -56,15 +61,14 @@ func main() {
}
func githubLoginHandler(c *gin.Context) {
// state := generateState()
// code := generateCode()
state := generateState()
// 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
// }
session := sessions.Default(c)
session.Set("state", state)
if err := session.Save(); err != nil {
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
url := oauthConf.AuthCodeURL("state")
log.Println(url)
@@ -72,30 +76,27 @@ func githubLoginHandler(c *gin.Context) {
}
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, state := c.Query("code"), c.Query("state")
session := sessions.Default(c)
savedState := session.Get("state")
if savedState == nil || savedState.(string) != state {
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "Invalid state parameter."})
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)))
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
}
@@ -111,6 +112,7 @@ func githubCallbackHandler(c *gin.Context) {
log.Println(user.GetEmail(), user.GetName(), user.GetID(), user.GetAvatarURL())
// 处理用户信息
c.JSON(http.StatusOK, gin.H{
"id": user.ID,
"login": user.Login,
"name": user.Name,
"email": user.Email,
@@ -203,19 +205,21 @@ func storeUserToDB(user *github.User) error {
return nil
}
func generateJWTToken(userID int64) (string, error) {
// Generate a signed JWT containing the user ID as a claim.
func generateJWT(userID int64) (string, error) {
token := jwt.New(jwt.SigningMethodHS256)
claims := jwt.MapClaims{
"sub": userID,
"exp": time.Now().Add(24 * time.Hour).Unix(),
}
token.Claims = claims
claims := token.Claims.(jwt.MapClaims)
claims["user"] = userID
claims["exp"] = time.Now().Add(time.Hour * 24).Unix()
jwtToken, err := token.SignedString(jwtSecret)
signedToken, err := token.SignedString(jwtSecret)
if err != nil {
return "", err
}
return jwtToken, nil
return signedToken, nil
}
func verifyJWTToken(tokenString string) {
@@ -242,3 +246,48 @@ func verifyJWTToken(tokenString string) {
fmt.Println("Invalid token.")
}
}
// Middleware to require authentication via JWT.
func requireAuth() gin.HandlerFunc {
return func(c *gin.Context) {
tokenString := c.GetHeader("Authorization")
if tokenString == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing Authorization header."})
return
}
token, 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 {
if err == jwt.ErrSignatureInvalid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token signature."})
return
}
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token."})
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims."})
return
}
userID, ok := claims["sub"].(float64)
if !ok {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid subject claim type."})
return
}
// Use the userID to look up the user in your database or perform other authorization logic.
// ...
// If authorized, set the user ID on the Gin context for use by downstream handlers.
c.Set("userID", int64(userID))
c.Next()
}
}

8
go.mod
View File

@@ -5,13 +5,14 @@ go 1.19
require (
github.com/Sakurasan/to v0.0.0-20180919163141-e72657dd7c7d
github.com/gin-contrib/cors v1.4.0
github.com/gin-contrib/sessions v0.0.5
github.com/gin-gonic/gin v1.9.0
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/google/go-github v17.0.0+incompatible
github.com/google/go-github/v50 v50.2.0
github.com/google/uuid v1.3.0
github.com/gorilla/websocket v1.5.0
github.com/joho/godotenv v1.5.1
github.com/patrickmn/go-cache v2.1.0+incompatible
golang.org/x/oauth2 v0.6.0
gorm.io/driver/mysql v1.4.7
gorm.io/driver/sqlite v1.4.4
@@ -31,13 +32,16 @@ require (
github.com/goccy/go-json v0.10.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/gorilla/context v1.1.1 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/gorilla/sessions v1.2.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/mattn/go-sqlite3 v1.14.15 // indirect
github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.0.6 // indirect

15
go.sum
View File

@@ -17,6 +17,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g=
github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs=
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
@@ -54,8 +56,12 @@ github.com/google/go-github/v50 v50.2.0/go.mod h1:VBY8FB6yPIjrtKhozXv4FQupxKLS6H
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -82,12 +88,15 @@ github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ic
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI=
github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=

66
main.go
View File

@@ -1,66 +0,0 @@
package main
import (
"context"
"fmt"
"net/http"
"github.com/google/go-github/github"
"golang.org/x/oauth2"
)
var (
clientID = "9f75836d51c1cb447fa5"
clientSecret = "3cbedeb77ffa7593b3cc60985aa4212b2cfc0686"
redirectURL = "http://localhost:8080/callback"
)
func main() {
// 创建OAuth配置
oauthConf := &oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
RedirectURL: redirectURL,
Scopes: []string{"user:email"},
Endpoint: oauth2.Endpoint{
AuthURL: "https://github.com/login/oauth/authorize",
TokenURL: "https://github.com/login/oauth/access_token",
},
}
// 创建HTTP处理程序
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 重定向到GitHub OAuth登录页面
url := oauthConf.AuthCodeURL("state", oauth2.AccessTypeOffline)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
})
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
// 获取访问令牌
code := r.URL.Query().Get("code")
token, err := oauthConf.Exchange(context.Background(), code)
if err != nil {
http.Error(w, "Failed to exchange token", http.StatusBadRequest)
return
}
// 创建GitHub客户端
client := github.NewClient(oauthConf.Client(context.Background(), token))
// 获取用户数据
user, _, err := client.Users.Get(context.Background(), "")
if err != nil {
http.Error(w, "Failed to get user", http.StatusBadRequest)
return
}
// 在控制台上打印用户数据
fmt.Printf("Hello, %s\n", *user.Name)
})
// 启动HTTP服务器
fmt.Println("Server listening on http://localhost:8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}

View File

@@ -1,71 +0,0 @@
package db
import (
"os"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 定义User模型
type User struct {
UserID int `gorm:"primaryKey"`
Username string `gorm:"not null"`
Email string `gorm:"not null"`
AvatarURL string
AccessToken string `gorm:"not null"`
}
// 初始化GORM数据库
func initializeDB() (*gorm.DB, error) {
dsn := os.Getenv("MYSQL_DSN")
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return nil, err
}
return db, nil
}
// 创建用户
func createUser(db *gorm.DB, user *User) error {
result := db.Create(user)
if result.Error != nil {
return result.Error
}
return nil
}
// 根据用户ID查询用户
func getUserByID(db *gorm.DB, userID int) (*User, error) {
var user User
result := db.First(&user, userID)
if result.Error != nil {
return nil, result.Error
}
return &user, nil
}
// 更新用户
func updateUser(db *gorm.DB, user *User) error {
result := db.Save(user)
if result.Error != nil {
return result.Error
}
return nil
}
// 删除用户
func deleteUser(db *gorm.DB, user *User) error {
result := db.Delete(user)
if result.Error != nil {
return result.Error
}
return nil
}

View File

@@ -1,61 +0,0 @@
package db
import "time"
type Key struct {
ID uint `gorm:"primarykey" json:"id,omitempty"`
Key string `gorm:"unique;not null" json:"key,omitempty"`
Name string `gorm:"unique;not null" json:"name,omitempty"`
UserId string `json:"-,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
}
func GetKeyrByName(name string) (*Key, error) {
var key Key
result := db.First(&key, "name = ?", name)
if result.Error != nil {
return nil, result.Error
}
return &key, nil
}
func GetAllKeys() ([]Key, error) {
var keys []Key
if err := db.Find(&keys).Error; err != nil {
return nil, err
}
return keys, nil
}
// 添加记录
func AddKey(apikey, name string) error {
key := Key{
Key: apikey,
Name: name,
}
if err := db.Create(&key).Error; err != nil {
return err
}
return nil
}
// 删除记录
func DeleteKey(id uint) error {
if err := db.Delete(&Key{}, id).Error; err != nil {
return err
}
return nil
}
// 更新记录
func UpdateKey(id uint, apikey string, userId string) error {
key := Key{
Key: apikey,
UserId: userId,
}
if err := db.Model(&Key{}).Where("id = ?", id).Updates(key).Error; err != nil {
return err
}
return nil
}

View File

@@ -1,80 +0,0 @@
package db
import (
"time"
)
type User struct {
IsDelete bool `gorm:"default:false" json:"IsDelete"`
ID uint `gorm:"primarykey" json:"id,omitempty"`
Name string `gorm:"unique;not null" json:"name,omitempty"`
Token string `gorm:"unique;not null" json:"token,omitempty"`
CreatedAt time.Time `json:"createdAt,omitempty"`
UpdatedAt time.Time `json:"updatedAt,omitempty"`
TotalTokens int `json:"total_tokens,omitempty"`
// DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
func CreateUser(u *User) error {
result := db.Create(u)
if result.Error != nil {
return result.Error
}
return nil
}
// 添加用户
func AddUser(name, token string) error {
user := &User{Name: name, Token: token}
result := db.Create(&user)
if result.Error != nil {
return result.Error
}
return nil
}
// 删除用户
func DeleteUser(id uint) error {
result := db.Delete(&User{}, id)
if result.Error != nil {
return result.Error
}
return nil
}
// 修改用户
func UpdateUser(id uint, token string) error {
user := &User{Token: token}
result := db.Model(&User{}).Where("id = ?", id).Updates(user)
if result.Error != nil {
return result.Error
}
return nil
}
func GetUserByID(id uint) (*User, error) {
var user User
result := db.Where("id = ?", id).First(&user)
if result.Error != nil {
return nil, result.Error
}
return &user, nil
}
func GetUserByName(name string) (*User, error) {
var user User
result := db.Where(&User{Name: name}).First(&user)
if result.Error != nil {
return nil, result.Error
}
return &user, nil
}
func GetAllUsers() ([]*User, error) {
var users []*User
result := db.Find(&users)
if result.Error != nil {
return nil, result.Error
}
return users, nil
}

16
pkg/store/mysql/mysql.go Normal file
View File

@@ -0,0 +1,16 @@
package mysql
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
// 初始化GORM数据库
func NewDB(dsn string) *gorm.DB {
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}
return db
}

View File

@@ -8,9 +8,7 @@ import (
"gorm.io/gorm"
)
var db *gorm.DB
func init() {
func NewDB() *gorm.DB {
if _, err := os.Stat("db"); os.IsNotExist(err) {
errDir := os.MkdirAll("db", 0755)
if errDir != nil {
@@ -23,9 +21,4 @@ func init() {
panic("failed to connect database")
}
// 自动迁移 User 结构体
err = db.AutoMigrate(&User{}, &Key{})
if err != nil {
panic(err)
}
}

11
services/board/board.go Normal file
View File

@@ -0,0 +1,11 @@
package board
type user struct {
ID string `json:"id,omitempty"` // 自增长的唯一标识符。
GithubID string `json:"github_Id,omitempty"` // 从GitHub API获取的数字型用户ID。
UserID string `json:"userid,omitempty"` // 用户在GitHub上的用户名。
Name string `json:"name,omitempty"` // 用户在GitHub上的昵称如果有
Email string `json:"email,omitempty"` // 用户在GitHub上公开的电子邮件地址如果有
AvatarUrl string `json:"avatar_Url,omitempty"` // 用户在GitHub上的头像URL如果有
CreatedAt string `json:"created_At,omitempty"` // 记录插入到数据库中的时间。
}