package middleware import ( "fmt" "os" "strings" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ValidateCORSConfiguration valide la configuration CORS pour éviter les problèmes de sécurité // INT-018: Amélioration de la validation CORS pour la production // - En production: retourne une erreur si wildcard est utilisé ou si CORS est vide // - En développement: log un warning mais ne bloque pas func ValidateCORSConfiguration(allowedOrigins []string, environment string, logger *zap.Logger) error { hasWildcard := false weakOrigins := []string{} for _, origin := range allowedOrigins { // Détecter les wildcards if origin == "*" || strings.Contains(origin, "*") { hasWildcard = true weakOrigins = append(weakOrigins, origin) } } // INT-018: Validation stricte en production if environment == "production" { // 1. CORS ne doit pas être vide en production if len(allowedOrigins) == 0 { errMsg := "CORS_ALLOWED_ORIGINS is required in production environment. Empty CORS origins will reject all CORS requests, making the service inaccessible from frontend" if logger != nil { logger.Error(errMsg, zap.String("environment", environment), zap.String("recommendation", "Set CORS_ALLOWED_ORIGINS with explicit origins (e.g., CORS_ALLOWED_ORIGINS=https://app.veza.com,https://www.veza.com)"), ) } return fmt.Errorf("%s. Please set CORS_ALLOWED_ORIGINS with explicit origins", errMsg) } // 2. CORS ne doit pas contenir de wildcard en production // CORS spec: credentials=true ne peut pas être utilisé avec Access-Control-Allow-Origin: * // C'est une faille de sécurité car cela permettrait à n'importe quel site d'accéder aux credentials if hasWildcard { errMsg := fmt.Sprintf( "CORS wildcard origins (%v) are not allowed in production environment with credentials=true. "+ "This violates CORS specification and is a security risk. "+ "Use specific origins instead of wildcards.", weakOrigins, ) if logger != nil { logger.Error(errMsg, zap.Strings("weak_origins", weakOrigins), zap.String("environment", environment), zap.String("recommendation", "Use specific origins instead of wildcards (e.g., CORS_ALLOWED_ORIGINS=https://app.veza.com,https://www.veza.com)"), ) } return fmt.Errorf("%s", errMsg) } } else { // INT-018: En développement/staging, log un warning mais ne bloque pas if hasWildcard { warningMsg := fmt.Sprintf( "SECURITY WARNING: CORS configuration allows wildcard origins (%v) with credentials=true. "+ "This is insecure and violates CORS specification. "+ "Use specific origins instead of wildcards when credentials=true. "+ "This will cause startup failure in production.", weakOrigins, ) if logger != nil { logger.Warn(warningMsg, zap.Strings("weak_origins", weakOrigins), zap.String("environment", environment), zap.String("recommendation", "Use specific origins instead of wildcards for production"), ) } else { // Fallback si logger n'est pas disponible fmt.Printf("⚠️ %s\n", warningMsg) } } } return nil } // CORS middleware pour gérer les en-têtes CORS avec whitelist d'origins configurable // allowedOrigins: liste des origines autorisées (ex: []string{"http://localhost:3000", "https://example.com"}) // Si "*" est dans la liste, toutes les origines sont autorisées // ATTENTION: L'utilisation de "*" avec credentials=true est interdite par la spec CORS func CORS(allowedOrigins []string) gin.HandlerFunc { return func(c *gin.Context) { origin := c.GetHeader("Origin") // Vérifier si l'origine est autorisée if isAllowedOrigin(origin, allowedOrigins) { c.Header("Access-Control-Allow-Origin", 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, X-CSRF-Token, X-API-Version, x-api-version") c.Header("Access-Control-Allow-Credentials", "true") c.Header("Access-Control-Expose-Headers", "X-CSRF-Token, X-Request-ID, Content-Range") c.Header("Access-Control-Max-Age", "86400") // Cache preflight pour 24h if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } } // isAllowedOrigin vérifie si une origine est dans la liste des origines autorisées func isAllowedOrigin(origin string, allowed []string) bool { // Sécurité par défaut : si liste vide, on rejette tout if len(allowed) == 0 { return false } for _, o := range allowed { // Permettre toutes les origines si "*" est dans la liste // ATTENTION: À utiliser seulement en dev if o == "*" { return true } if o == origin { return true } } return false } // CORSDefault crée un middleware CORS avec une whitelist par défaut. // SEC-016: DEPRECATED — DO NOT USE IN PRODUCTION. Panics if APP_ENV=production. func CORSDefault() gin.HandlerFunc { if os.Getenv("APP_ENV") == "production" { panic("CORSDefault() must not be used in production - use CORS() with explicit origins") } return CORS([]string{"*"}) }