package middleware import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "git.gobha.me/xcaliber/chat-switchboard/config" "git.gobha.me/xcaliber/chat-switchboard/database" ) // Claims represents the JWT payload. type Claims struct { UserID string `json:"user_id"` Email string `json:"email"` jwt.RegisteredClaims } // Auth returns a Gin middleware that validates JWT bearer tokens. // When no database is connected (unmanaged mode), all requests // are allowed through without authentication. func Auth(cfg *config.Config) gin.HandlerFunc { return func(c *gin.Context) { // Skip auth when running without a database (unmanaged mode) if !database.IsConnected() { c.Next() return } header := c.GetHeader("Authorization") if header == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "missing authorization header", }) return } tokenString := strings.TrimPrefix(header, "Bearer ") if tokenString == header { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "invalid authorization format", }) return } claims := &Claims{} token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) { if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, jwt.ErrSignatureInvalid } return []byte(cfg.JWTSecret), nil }) if err != nil || !token.Valid { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "invalid or expired token", }) return } // Store claims in context for downstream handlers c.Set("user_id", claims.UserID) c.Set("email", claims.Email) c.Next() } }