// Package middleware exposes Gin middlewares used by the HTTP server: // request logging, CORS, JWT authentication and admin guard. package middleware import ( "errors" "net/http" "strings" "time" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "go.uber.org/zap" ) // Context keys for values produced by the auth middleware. const ( CtxUserID = "ctx_user_id" CtxUserRole = "ctx_user_role" ) // RequestLogger logs one structured line per request. func RequestLogger(log *zap.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() c.Next() log.Info("http", zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path), zap.Int("status", c.Writer.Status()), zap.Duration("dur", time.Since(start)), zap.String("ip", c.ClientIP()), ) } } // CORS implements a permissive cross-origin policy when origins is empty // (development convenience) and a strict allow-list otherwise. func CORS(origins []string) gin.HandlerFunc { allowAll := len(origins) == 0 allowed := make(map[string]struct{}, len(origins)) for _, o := range origins { allowed[strings.TrimSpace(o)] = struct{}{} } return func(c *gin.Context) { origin := c.GetHeader("Origin") if allowAll { c.Header("Access-Control-Allow-Origin", "*") } else if _, ok := allowed[origin]; ok && origin != "" { c.Header("Access-Control-Allow-Origin", origin) c.Header("Access-Control-Allow-Credentials", "true") c.Header("Vary", "Origin") } c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With") if c.Request.Method == http.MethodOptions { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } } // Claims is the JWT payload we issue. type Claims struct { UserID string `json:"uid"` Role string `json:"role"` jwt.RegisteredClaims } // AuthRequired parses and validates a JWT from the Authorization header // (Bearer ...) or the `token` query parameter (used by