veza/veza-backend-api/internal/middleware/cors.go

104 lines
3.2 KiB
Go

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{"*"})
}