package middleware import ( "context" "net/http" "strings" "hfb_sys/backend/internal/modules/auth" "hfb_sys/backend/pkg/response" "github.com/gin-gonic/gin" ) const ( ContextUserID = "user_id" ContextPhone = "phone" ContextAdminID = "admin_id" ContextUsername = "username" ContextPasswordMustChange = "password_must_change" AdminAccessCookieName = "hfb_admin_access" ) type AdminTokenContext struct { Username string PasswordMustChange bool } type AdminTokenValidatorFunc func(ctx context.Context, adminID uint64, tokenVersion int64) (AdminTokenContext, error) func extractBearerToken(c *gin.Context) string { header := c.GetHeader("Authorization") tokenText := strings.TrimSpace(strings.TrimPrefix(header, "Bearer ")) if tokenText != "" && tokenText != header { return tokenText } return "" } func extractToken(c *gin.Context) string { if tokenText := extractBearerToken(c); tokenText != "" { return tokenText } return c.Query("token") } func Auth(jwtManager *auth.JWTManager) gin.HandlerFunc { return func(c *gin.Context) { tokenText := extractToken(c) if tokenText == "" { response.Unauthorized(c, "缺少访问令牌") c.Abort() return } claims, err := jwtManager.ParseSubject(tokenText, "access", "user") if err != nil { response.Unauthorized(c, "访问令牌无效或已过期") c.Abort() return } c.Set(ContextUserID, claims.UserID) c.Set(ContextPhone, claims.Phone) c.Next() } } func AdminAuth(jwtManager *auth.JWTManager, validate AdminTokenValidatorFunc) gin.HandlerFunc { return func(c *gin.Context) { tokenText := extractBearerToken(c) if tokenText == "" { if cookieToken, err := c.Cookie(AdminAccessCookieName); err == nil { tokenText = strings.TrimSpace(cookieToken) } } if tokenText == "" { response.Unauthorized(c, "缺少后台访问令牌") c.Abort() return } claims, err := jwtManager.ParseSubject(tokenText, "access", "admin") if err != nil { response.Unauthorized(c, "后台访问令牌无效或已过期") c.Abort() return } username := claims.Phone passwordMustChange := false if validate != nil { tokenContext, err := validate(c.Request.Context(), claims.UserID, claims.TokenVersion) if err != nil { response.Unauthorized(c, "后台访问令牌无效或已过期") c.Abort() return } username = tokenContext.Username passwordMustChange = tokenContext.PasswordMustChange } c.Set(ContextAdminID, claims.UserID) c.Set(ContextUsername, username) c.Set(ContextPasswordMustChange, passwordMustChange) c.Next() } } func RequireAdminPasswordChanged() gin.HandlerFunc { allowed := map[string]bool{ "/api/admin/me": true, "/api/admin/auth/logout": true, "/api/admin/admin-users/me/password": true, } return func(c *gin.Context) { value, ok := c.Get(ContextPasswordMustChange) if !ok { c.Next() return } mustChange, ok := value.(bool) if !ok || !mustChange || allowed[c.FullPath()] { c.Next() return } response.Error(c, http.StatusForbidden, "password_must_change", "请先修改初始密码") c.Abort() } }