package auth import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/hhs/camtalk/internal/trace" ) // contextKey 用于在 Gin context 中存储 Claims 的 key。 const ( ContextKeyUserID = "user_id" ContextKeyUsername = "username" ) // AuthMiddleware 返回 Gin 中间件,从 Authorization: Bearer 提取并校验 JWT。 // 校验成功后将 user_id 和 username 写入 Gin Context。 func AuthMiddleware(tokenMgr *TokenManager) gin.HandlerFunc { return func(c *gin.Context) { log := trace.FromContext(c.Request.Context()) authHeader := c.GetHeader("Authorization") if authHeader == "" { log.Warnw("auth rejected", "client_ip", c.ClientIP(), "path", c.Request.URL.Path, "reason", "missing authorization header") c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "code": "INVALID_TOKEN", "message": "missing authorization header", }) return } // 提取 Bearer token parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { log.Warnw("auth rejected", "client_ip", c.ClientIP(), "path", c.Request.URL.Path, "reason", "invalid authorization format") c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "code": "INVALID_TOKEN", "message": "invalid authorization format", }) return } claims, err := tokenMgr.ValidateAccess(parts[1]) if err != nil { log.Warnw("auth rejected", "client_ip", c.ClientIP(), "path", c.Request.URL.Path, "reason", "invalid or expired token", "error", err) c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "code": "INVALID_TOKEN", "message": "invalid or expired token", }) return } // 将用户信息写入 context c.Set(ContextKeyUserID, claims.UserID) c.Set(ContextKeyUsername, claims.Username) c.Next() } }