// Package middleware provides HTTP middleware for the API Gateway package middleware import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" ) // Claims represents JWT claims type Claims struct { TenantID string `json:"tenant_id"` UserID string `json:"user_id"` Scopes []string `json:"scopes"` jwt.RegisteredClaims } // Auth middleware validates JWT tokens or API keys func Auth(jwtSecret string) gin.HandlerFunc { return func(c *gin.Context) { authHeader := c.GetHeader("Authorization") if authHeader == "" { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "Missing authorization header", }) return } // Check for Bearer token (JWT) if strings.HasPrefix(authHeader, "Bearer ") { tokenString := strings.TrimPrefix(authHeader, "Bearer ") // Check if it's an API key (starts with pk_ or sk_) if strings.HasPrefix(tokenString, "pk_") || strings.HasPrefix(tokenString, "sk_") { // API Key authentication if err := validateAPIKey(c, tokenString); err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "Invalid API key", }) return } } else { // JWT authentication claims, err := validateJWT(tokenString, jwtSecret) if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "Invalid token", }) return } // Store claims in context c.Set("tenant_id", claims.TenantID) c.Set("user_id", claims.UserID) c.Set("scopes", claims.Scopes) } } else { c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{ "error": "Invalid authorization format", }) return } c.Next() } } func validateJWT(tokenString, secret string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { return []byte(secret), nil }) if err != nil { return nil, err } if claims, ok := token.Claims.(*Claims); ok && token.Valid { return claims, nil } return nil, jwt.ErrTokenInvalidClaims } func validateAPIKey(c *gin.Context, apiKey string) error { // In production, this would validate against a database of API keys // For now, we extract tenant info from the X-Tenant-ID header tenantID := c.GetHeader("X-Tenant-ID") if tenantID == "" { tenantID = "default" } c.Set("tenant_id", tenantID) c.Set("user_id", "api-key-user") c.Set("scopes", []string{"read", "write"}) return nil } // TenantIsolation ensures requests only access their own tenant's data func TenantIsolation() gin.HandlerFunc { return func(c *gin.Context) { tenantID, exists := c.Get("tenant_id") if !exists || tenantID == "" { c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ "error": "Tenant ID required", }) return } // Add tenant ID to all database queries (handled by handlers) c.Set("tenant_filter", tenantID) c.Next() } }