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)) }