package middleware import ( "fmt" "strings" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ValidateCORSConfiguration valide la configuration CORS pour éviter les problèmes de sécurité // MVP-014: Valider que credentials=true n'est pas utilisé avec des wildcards func ValidateCORSConfiguration(allowedOrigins []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) } } // 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 { 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.", weakOrigins, ) if logger != nil { logger.Warn(warningMsg, zap.Strings("weak_origins", weakOrigins), zap.String("recommendation", "Use specific origins instead of wildcards"), ) } else { // Fallback si logger n'est pas disponible fmt.Printf("⚠️ %s\n", warningMsg) } // En production, on peut choisir de faire échouer le démarrage // Pour MVP, on log juste un warning pour ne pas bloquer le développement // TODO: Faire échouer le démarrage en production si nécessaire } 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, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Authorization, Content-Type, X-Requested-With") c.Header("Access-Control-Allow-Credentials", "true") 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 // Utile pour compatibilité avec le code existant func CORSDefault() gin.HandlerFunc { return CORS([]string{"*"}) }