104 lines
3.2 KiB
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{"*"})
|
|
}
|