fix/status-review-sync-20260409 #1

Merged
long merged 65 commits from fix/status-review-sync-20260409 into main 2026-04-18 15:05:51 +00:00
8 changed files with 79 additions and 67 deletions
Showing only changes of commit b6aff65975 - Show all commits

View File

@@ -26,13 +26,17 @@ func (h *CaptchaHandler) GenerateCaptcha(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"captcha_id": result.CaptchaID,
"image": result.ImageData,
"code": 0,
"message": "success",
"data": gin.H{
"captcha_id": result.CaptchaID,
"image": result.ImageData,
},
})
}
func (h *CaptchaHandler) GetCaptchaImage(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "captcha image endpoint"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success"})
}
func (h *CaptchaHandler) VerifyCaptcha(c *gin.Context) {
@@ -42,13 +46,13 @@ func (h *CaptchaHandler) VerifyCaptcha(c *gin.Context) {
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
if h.captchaService.Verify(c.Request.Context(), req.CaptchaID, req.Answer) {
c.JSON(http.StatusOK, gin.H{"verified": true})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": gin.H{"verified": true}})
} else {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid captcha"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid captcha"})
}
}

View File

@@ -33,7 +33,7 @@ func (h *PasswordResetHandler) ForgotPassword(c *gin.Context) {
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -42,13 +42,13 @@ func (h *PasswordResetHandler) ForgotPassword(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "password reset email sent"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "password reset email sent"})
}
func (h *PasswordResetHandler) ValidateResetToken(c *gin.Context) {
token := c.Query("token")
if token == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "token is required"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "token is required"})
return
}
@@ -58,7 +58,7 @@ func (h *PasswordResetHandler) ValidateResetToken(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"valid": valid})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": gin.H{"valid": valid}})
}
func (h *PasswordResetHandler) ResetPassword(c *gin.Context) {
@@ -68,7 +68,7 @@ func (h *PasswordResetHandler) ResetPassword(c *gin.Context) {
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -77,7 +77,7 @@ func (h *PasswordResetHandler) ResetPassword(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "password reset successful"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "password reset successful"})
}
// ForgotPasswordByPhoneRequest 短信密码重置请求
@@ -88,13 +88,13 @@ type ForgotPasswordByPhoneRequest struct {
// ForgotPasswordByPhone 发送短信验证码
func (h *PasswordResetHandler) ForgotPasswordByPhone(c *gin.Context) {
if h.smsService == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "SMS service not configured"})
c.JSON(http.StatusServiceUnavailable, gin.H{"code": 503, "message": "SMS service not configured"})
return
}
var req ForgotPasswordByPhoneRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -106,7 +106,7 @@ func (h *PasswordResetHandler) ForgotPasswordByPhone(c *gin.Context) {
}
if code == "" {
// 用户不存在,不提示
c.JSON(http.StatusOK, gin.H{"message": "verification code sent"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success"})
return
}
@@ -121,7 +121,7 @@ func (h *PasswordResetHandler) ForgotPasswordByPhone(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "verification code sent"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success"})
}
// ResetPasswordByPhoneRequest 短信验证码重置密码请求
@@ -135,7 +135,7 @@ type ResetPasswordByPhoneRequest struct {
func (h *PasswordResetHandler) ResetPasswordByPhone(c *gin.Context) {
var req ResetPasswordByPhoneRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -149,5 +149,5 @@ func (h *PasswordResetHandler) ResetPasswordByPhone(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "password reset successful"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "password reset successful"})
}

View File

@@ -33,5 +33,5 @@ func (h *SettingsHandler) GetSettings(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"data": settings})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": settings})
}

View File

@@ -30,13 +30,13 @@ func NewSMSHandlerWithService(authService *service.AuthService, smsCodeService *
// SendCode 发送短信验证码(用于注册/登录)
func (h *SMSHandler) SendCode(c *gin.Context) {
if h.smsCodeService == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "SMS service not configured"})
c.JSON(http.StatusServiceUnavailable, gin.H{"code": 503, "message": "SMS service not configured"})
return
}
var req service.SendCodeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -56,7 +56,7 @@ func (h *SMSHandler) SendCode(c *gin.Context) {
// LoginByCode 短信验证码登录(带设备信息以支持设备信任链路)
func (h *SMSHandler) LoginByCode(c *gin.Context) {
if h.authService == nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"error": "SMS login not configured"})
c.JSON(http.StatusServiceUnavailable, gin.H{"code": 503, "message": "SMS login not configured"})
return
}
@@ -70,7 +70,7 @@ func (h *SMSHandler) LoginByCode(c *gin.Context) {
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}

View File

@@ -38,20 +38,20 @@ type AuthorizeRequest struct {
func (h *SSOHandler) Authorize(c *gin.Context) {
var req AuthorizeRequest
if err := c.ShouldBindQuery(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
// 验证 response_type
if req.ResponseType != "code" && req.ResponseType != "token" {
c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported response_type"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "unsupported response_type"})
return
}
// 验证 redirect_uri 是否在白名单中
if h.clientsStore != nil {
if !h.clientsStore.ValidateClientRedirectURI(req.ClientID, req.RedirectURI) {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid redirect_uri"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid redirect_uri"})
return
}
}
@@ -59,7 +59,7 @@ func (h *SSOHandler) Authorize(c *gin.Context) {
// 获取当前登录用户(从 auth middleware 设置的 context
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"})
return
}
@@ -75,7 +75,7 @@ func (h *SSOHandler) Authorize(c *gin.Context) {
username.(string),
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate code"})
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to generate code"})
return
}
@@ -95,20 +95,20 @@ func (h *SSOHandler) Authorize(c *gin.Context) {
username.(string),
)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate code"})
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to generate code"})
return
}
// 验证授权码获取 session
session, err := h.ssoManager.ValidateAuthorizationCode(code)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to validate code"})
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to validate code"})
return
}
token, _, err := h.ssoManager.GenerateAccessToken(req.ClientID, session)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to generate token"})
return
}
@@ -143,13 +143,13 @@ type TokenResponse struct {
func (h *SSOHandler) Token(c *gin.Context) {
var req TokenRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
// 验证 grant_type
if req.GrantType != "authorization_code" {
c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported grant_type"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "unsupported grant_type"})
return
}
@@ -157,12 +157,12 @@ func (h *SSOHandler) Token(c *gin.Context) {
if h.clientsStore != nil {
client, err := h.clientsStore.GetByClientID(req.ClientID)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "invalid client"})
return
}
// 使用常量时间比较防止时序攻击
if subtle.ConstantTimeCompare([]byte(req.ClientSecret), []byte(client.ClientSecret)) != 1 {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid client_secret"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "invalid client_secret"})
return
}
}
@@ -170,14 +170,14 @@ func (h *SSOHandler) Token(c *gin.Context) {
// 验证授权码
session, err := h.ssoManager.ValidateAuthorizationCode(req.Code)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "invalid code"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "invalid code"})
return
}
// 生成 access token
token, expiresAt, err := h.ssoManager.GenerateAccessToken(req.ClientID, session)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to generate token"})
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "failed to generate token"})
return
}
@@ -209,7 +209,7 @@ type IntrospectResponse struct {
func (h *SSOHandler) Introspect(c *gin.Context) {
var req IntrospectRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -238,13 +238,13 @@ type RevokeRequest struct {
func (h *SSOHandler) Revoke(c *gin.Context) {
var req RevokeRequest
if err := c.ShouldBind(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
h.ssoManager.RevokeToken(req.Token)
c.JSON(http.StatusOK, gin.H{"message": "token revoked"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "token revoked"})
}
// UserInfoResponse 用户信息响应
@@ -258,14 +258,18 @@ type UserInfoResponse struct {
func (h *SSOHandler) UserInfo(c *gin.Context) {
userID, exists := c.Get("user_id")
if !exists {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"})
return
}
username, _ := c.Get("username")
c.JSON(http.StatusOK, UserInfoResponse{
UserID: userID.(int64),
Username: username.(string),
c.JSON(http.StatusOK, gin.H{
"code": 0,
"message": "success",
"data": UserInfoResponse{
UserID: userID.(int64),
Username: username.(string),
},
})
}

View File

@@ -24,7 +24,7 @@ func (h *StatsHandler) GetDashboard(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "获取仪表盘数据失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": stats})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": stats})
}
func (h *StatsHandler) GetUserStats(c *gin.Context) {
@@ -33,5 +33,5 @@ func (h *StatsHandler) GetUserStats(c *gin.Context) {
c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "获取用户统计失败"})
return
}
c.JSON(http.StatusOK, gin.H{"code": 0, "data": stats})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": stats})
}

View File

@@ -23,7 +23,7 @@ func NewThemeHandler(themeService *service.ThemeService) *ThemeHandler {
func (h *ThemeHandler) CreateTheme(c *gin.Context) {
var req service.CreateThemeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -44,13 +44,13 @@ func (h *ThemeHandler) CreateTheme(c *gin.Context) {
func (h *ThemeHandler) UpdateTheme(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid theme id"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid theme id"})
return
}
var req service.UpdateThemeRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -71,7 +71,7 @@ func (h *ThemeHandler) UpdateTheme(c *gin.Context) {
func (h *ThemeHandler) DeleteTheme(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid theme id"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid theme id"})
return
}
@@ -90,7 +90,7 @@ func (h *ThemeHandler) DeleteTheme(c *gin.Context) {
func (h *ThemeHandler) GetTheme(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid theme id"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid theme id"})
return
}
@@ -156,7 +156,7 @@ func (h *ThemeHandler) GetDefaultTheme(c *gin.Context) {
func (h *ThemeHandler) SetDefaultTheme(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid theme id"})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "invalid theme id"})
return
}

View File

@@ -25,7 +25,7 @@ func NewTOTPHandler(authService *service.AuthService, totpService *service.TOTPS
func (h *TOTPHandler) GetTOTPStatus(c *gin.Context) {
userID, ok := getUserIDFromContext(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"})
return
}
@@ -35,13 +35,13 @@ func (h *TOTPHandler) GetTOTPStatus(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"enabled": enabled})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": gin.H{"enabled": enabled}})
}
func (h *TOTPHandler) SetupTOTP(c *gin.Context) {
userID, ok := getUserIDFromContext(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"})
return
}
@@ -52,16 +52,20 @@ func (h *TOTPHandler) SetupTOTP(c *gin.Context) {
}
c.JSON(http.StatusOK, gin.H{
"secret": resp.Secret,
"qr_code_base64": resp.QRCodeBase64,
"recovery_codes": resp.RecoveryCodes,
"code": 0,
"message": "success",
"data": gin.H{
"secret": resp.Secret,
"qr_code_base64": resp.QRCodeBase64,
"recovery_codes": resp.RecoveryCodes,
},
})
}
func (h *TOTPHandler) EnableTOTP(c *gin.Context) {
userID, ok := getUserIDFromContext(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"})
return
}
@@ -70,7 +74,7 @@ func (h *TOTPHandler) EnableTOTP(c *gin.Context) {
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -79,13 +83,13 @@ func (h *TOTPHandler) EnableTOTP(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "TOTP enabled"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success"})
}
func (h *TOTPHandler) DisableTOTP(c *gin.Context) {
userID, ok := getUserIDFromContext(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"})
return
}
@@ -94,7 +98,7 @@ func (h *TOTPHandler) DisableTOTP(c *gin.Context) {
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -103,13 +107,13 @@ func (h *TOTPHandler) DisableTOTP(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"message": "TOTP disabled"})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success"})
}
func (h *TOTPHandler) VerifyTOTP(c *gin.Context) {
userID, ok := getUserIDFromContext(c)
if !ok {
c.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
c.JSON(http.StatusUnauthorized, gin.H{"code": 401, "message": "unauthorized"})
return
}
@@ -119,7 +123,7 @@ func (h *TOTPHandler) VerifyTOTP(c *gin.Context) {
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": err.Error()})
return
}
@@ -128,5 +132,5 @@ func (h *TOTPHandler) VerifyTOTP(c *gin.Context) {
return
}
c.JSON(http.StatusOK, gin.H{"verified": true})
c.JSON(http.StatusOK, gin.H{"code": 0, "message": "success", "data": gin.H{"verified": true}})
}