Files
gitpm/internal/api/handlers/auth.go
huxunan 885fad6c64 Initial commit: Gitea Project Management System
Features:
- Complete project management system with Epic/Story/Task hierarchy
- Vue.js 3 + Element Plus frontend with kanban board
- Go backend with Gin framework and GORM
- OAuth2 integration with Gitea
- Docker containerization with MySQL
- RESTful API for project, task, and user management
- JWT authentication and authorization
- Responsive web interface with dashboard
2025-09-22 14:53:53 +08:00

159 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package handlers
import (
"encoding/json"
"fmt"
"io"
"net/http"
"time"
"giteapm/internal/middleware"
"giteapm/internal/models"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
"golang.org/x/oauth2"
)
type GiteaUser struct {
ID int `json:"id"`
Login string `json:"login"`
FullName string `json:"full_name"`
Email string `json:"email"`
AvatarURL string `json:"avatar_url"`
}
func (h *Handlers) GiteaLogin(c *gin.Context) {
config := &oauth2.Config{
ClientID: h.Cfg.Gitea.ClientID,
ClientSecret: h.Cfg.Gitea.ClientSecret,
RedirectURL: h.Cfg.Gitea.RedirectURL,
Scopes: []string{"read:user"},
Endpoint: oauth2.Endpoint{
AuthURL: h.Cfg.Gitea.BaseURL + "/login/oauth/authorize",
TokenURL: h.Cfg.Gitea.BaseURL + "/login/oauth/access_token",
},
}
state := "random-state-string"
url := config.AuthCodeURL(state, oauth2.AccessTypeOffline)
c.JSON(http.StatusOK, SuccessResponse(gin.H{
"auth_url": url,
}))
}
func (h *Handlers) GiteaCallback(c *gin.Context) {
// 记录所有查询参数用于调试
fmt.Printf("OAuth回调收到的所有参数: %v\n", c.Request.URL.Query())
code := c.Query("code")
state := c.Query("state")
errorParam := c.Query("error")
fmt.Printf("OAuth回调 - code: %s, state: %s, error: %s\n", code, state, errorParam)
if errorParam != "" {
c.JSON(http.StatusBadRequest, ErrorResponse(400, fmt.Sprintf("OAuth授权失败: %s", errorParam)))
return
}
if code == "" {
c.JSON(http.StatusBadRequest, ErrorResponse(400, "缺少授权码"))
return
}
config := &oauth2.Config{
ClientID: h.Cfg.Gitea.ClientID,
ClientSecret: h.Cfg.Gitea.ClientSecret,
RedirectURL: h.Cfg.Gitea.RedirectURL,
Scopes: []string{"read:user"},
Endpoint: oauth2.Endpoint{
AuthURL: h.Cfg.Gitea.BaseURL + "/login/oauth/authorize",
TokenURL: h.Cfg.Gitea.BaseURL + "/login/oauth/access_token",
},
}
token, err := config.Exchange(c.Request.Context(), code)
if err != nil {
c.JSON(http.StatusBadRequest, ErrorResponse(400, "授权码交换失败"))
return
}
client := config.Client(c.Request.Context(), token)
resp, err := client.Get(h.Cfg.Gitea.BaseURL + "/api/v1/user")
if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "获取用户信息失败"))
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "读取用户信息失败"))
return
}
var giteaUser GiteaUser
if err := json.Unmarshal(body, &giteaUser); err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "解析用户信息失败"))
return
}
user, err := models.GetUserByGiteaID(uint(giteaUser.ID))
if err != nil {
user = &models.User{
GiteaUserID: func() *uint { id := uint(giteaUser.ID); return &id }(),
Username: giteaUser.Login,
Email: giteaUser.Email,
FullName: giteaUser.FullName,
AvatarURL: giteaUser.AvatarURL,
Role: "developer",
}
if err := models.CreateUser(user); err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "创建用户失败"))
return
}
} else {
user.Username = giteaUser.Login
user.Email = giteaUser.Email
user.FullName = giteaUser.FullName
user.AvatarURL = giteaUser.AvatarURL
if err := models.UpdateUser(user); err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "更新用户失败"))
return
}
}
jwtToken, err := h.generateJWT(user)
if err != nil {
c.JSON(http.StatusInternalServerError, ErrorResponse(500, "生成JWT失败"))
return
}
// 重定向到前端页面并通过URL参数传递token
redirectURL := fmt.Sprintf("/?token=%s", jwtToken)
c.Redirect(http.StatusFound, redirectURL)
}
func (h *Handlers) Logout(c *gin.Context) {
c.JSON(http.StatusOK, SuccessResponse(gin.H{
"message": "登出成功",
}))
}
func (h *Handlers) generateJWT(user *models.User) (string, error) {
claims := &middleware.Claims{
UserID: user.ID,
Username: user.Username,
Role: user.Role,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(h.Cfg.JWT.ExpireHour) * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Subject: fmt.Sprintf("%d", user.ID),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(h.Cfg.JWT.Secret))
}