Sage100-vps/config/cors_config.py
2026-01-19 20:38:01 +03:00

321 lines
10 KiB
Python

"""
CONFIGURATION CORS POUR API AVEC CLÉS API MULTIPLES
Problématique:
- Les clés API seront utilisées depuis de nombreux domaines/IPs différents
- Impossible de lister tous les origins autorisés à l'avance
- Solution: CORS permissif mais sécurisé par les clés API
Stratégies:
1. CORS ouvert avec validation par clé API (RECOMMANDÉ)
2. CORS dynamique basé sur whitelist
3. CORS avec wildcard et credentials=False
"""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from typing import List
import os
import logging
logger = logging.getLogger(__name__)
# ============================================
# STRATÉGIE 1: CORS OUVERT + VALIDATION API KEY
# ============================================
# ✅ RECOMMANDÉ pour les API publiques avec clés
#
# Principe:
# - Accepter toutes les origines (allow_origins=["*"])
# - La sécurité est assurée par la validation des clés API
# - Les clés API protègent l'accès, pas le CORS
def configure_cors_open(app: FastAPI):
"""
Configuration CORS ouverte (RECOMMANDÉE)
✅ Accepte toutes les origines
✅ Sécurité assurée par les clés API
✅ Simplifie l'utilisation pour les clients
⚠️ Attention: credentials=False obligatoire avec allow_origins=["*"]
"""
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Accepte toutes les origines
allow_credentials=False, # ⚠️ Obligatoire avec "*"
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
allow_headers=["*"], # Accepte tous les headers (dont X-API-Key)
expose_headers=["X-RateLimit-Limit", "X-RateLimit-Remaining"],
max_age=3600, # Cache preflight requests pendant 1h
)
logger.info("🌐 CORS configuré: Mode OUVERT (sécurisé par API Keys)")
logger.info(" - Origins: * (toutes)")
logger.info(" - Headers: * (dont X-API-Key)")
logger.info(" - Credentials: False")
# ============================================
# STRATÉGIE 2: CORS AVEC WHITELIST DYNAMIQUE
# ============================================
# 🔶 Pour environnements contrôlés
#
# Principe:
# - Lister explicitement les domaines autorisés
# - Peut inclure des patterns wildcards
# - Credentials possible (cookies, etc.)
def configure_cors_whitelist(app: FastAPI):
"""
Configuration CORS avec whitelist (MODE CONTRÔLÉ)
✅ Meilleur contrôle des origines
✅ Credentials possible
❌ Nécessite maintenance de la liste
À utiliser si vous connaissez tous les domaines clients
"""
# Charger depuis .env ou config
allowed_origins_str = os.getenv("CORS_ALLOWED_ORIGINS", "")
if allowed_origins_str:
allowed_origins = [
origin.strip()
for origin in allowed_origins_str.split(",")
if origin.strip()
]
else:
# Valeurs par défaut
allowed_origins = [
"http://localhost:3000", # Frontend dev React/Vue
"http://localhost:5173", # Vite dev
"http://localhost:8080", # Frontend dev alternatif
"https://app.votre-domaine.com",
"https://admin.votre-domaine.com",
# Ajouter vos domaines de production
]
app.add_middleware(
CORSMiddleware,
allow_origins=allowed_origins,
allow_credentials=True, # ✅ Possible avec liste explicite
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-API-Key"],
expose_headers=["X-RateLimit-Limit", "X-RateLimit-Remaining"],
max_age=3600,
)
logger.info("🌐 CORS configuré: Mode WHITELIST")
logger.info(f" - Origins autorisées: {len(allowed_origins)}")
for origin in allowed_origins:
logger.info(f"{origin}")
# ============================================
# STRATÉGIE 3: CORS AVEC REGEX (AVANCÉ)
# ============================================
# 🔶 Pour patterns complexes
#
# Exemple: Autoriser tous les sous-domaines *.votre-domaine.com
def configure_cors_regex(app: FastAPI):
"""
Configuration CORS avec patterns regex (AVANCÉ)
✅ Flexible pour sous-domaines
✅ Supporte patterns complexes
❌ Plus complexe à configurer
Utilise allow_origin_regex au lieu de allow_origins
"""
# Pattern regex pour autoriser tous les sous-domaines
origin_regex = r"https://.*\.votre-domaine\.com"
app.add_middleware(
CORSMiddleware,
allow_origin_regex=origin_regex, # ⚠️ Utilise regex au lieu de liste
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-API-Key"],
expose_headers=["X-RateLimit-Limit", "X-RateLimit-Remaining"],
max_age=3600,
)
logger.info("🌐 CORS configuré: Mode REGEX")
logger.info(f" - Pattern: {origin_regex}")
# ============================================
# STRATÉGIE 4: CORS HYBRIDE (PRODUCTION)
# ============================================
# ✅ RECOMMANDÉ pour production
#
# Principe:
# - Whitelist pour les domaines connus (credentials=True)
# - Fallback sur "*" pour le reste (credentials=False)
def configure_cors_hybrid(app: FastAPI):
"""
Configuration CORS hybride (PRODUCTION)
✅ Meilleur des deux mondes
✅ Whitelist pour domaines connus
✅ Fallback ouvert pour API Keys externes
Note: Nécessite un middleware custom pour gérer les deux modes
"""
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
class HybridCORSMiddleware(BaseHTTPMiddleware):
def __init__(self, app, known_origins: List[str]):
super().__init__(app)
self.known_origins = set(known_origins)
async def dispatch(self, request, call_next):
origin = request.headers.get("origin")
# Si origin connue → CORS strict avec credentials
if origin in self.known_origins:
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = origin
response.headers["Access-Control-Allow-Credentials"] = "true"
response.headers["Access-Control-Allow-Methods"] = (
"GET, POST, PUT, DELETE, PATCH, OPTIONS"
)
response.headers["Access-Control-Allow-Headers"] = (
"Content-Type, Authorization, X-API-Key"
)
return response
# Sinon → CORS ouvert sans credentials
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = (
"GET, POST, PUT, DELETE, PATCH, OPTIONS"
)
response.headers["Access-Control-Allow-Headers"] = "*"
return response
# Domaines connus (whitelist)
known_origins = [
"https://app.votre-domaine.com",
"https://admin.votre-domaine.com",
"http://localhost:3000",
"http://localhost:5173",
]
app.add_middleware(HybridCORSMiddleware, known_origins=known_origins)
logger.info("🌐 CORS configuré: Mode HYBRIDE")
logger.info(f" - Whitelist: {len(known_origins)} domaines")
logger.info(" - Fallback: * (ouvert)")
# ============================================
# FONCTION PRINCIPALE
# ============================================
def setup_cors(app: FastAPI, mode: str = "open"):
"""
Configure CORS selon le mode choisi
Args:
app: Instance FastAPI
mode: "open" | "whitelist" | "regex" | "hybrid"
Recommandations:
- Development: "open"
- Production (API publique): "open" ou "hybrid"
- Production (API interne): "whitelist"
"""
if mode == "open":
configure_cors_open(app)
elif mode == "whitelist":
configure_cors_whitelist(app)
elif mode == "regex":
configure_cors_regex(app)
elif mode == "hybrid":
configure_cors_hybrid(app)
else:
logger.warning(
f"⚠️ Mode CORS inconnu: {mode}. Utilisation de 'open' par défaut."
)
configure_cors_open(app)
# ============================================
# EXEMPLE D'UTILISATION DANS api.py
# ============================================
"""
# Dans api.py
from config.cors_config import setup_cors
app = FastAPI(...)
# DÉVELOPPEMENT
setup_cors(app, mode="open")
# PRODUCTION (API publique avec clés)
setup_cors(app, mode="hybrid")
# PRODUCTION (API interne uniquement)
setup_cors(app, mode="whitelist")
"""
# ============================================
# VARIABLES D'ENVIRONNEMENT (.env)
# ============================================
"""
# Pour mode "whitelist"
CORS_ALLOWED_ORIGINS=https://app.example.com,https://admin.example.com,http://localhost:3000
# Pour mode "regex"
CORS_ORIGIN_REGEX=https://.*\.example\.com
# Choisir le mode
CORS_MODE=open
"""
# ============================================
# FAQ CORS + API KEYS
# ============================================
"""
Q: Pourquoi allow_origins=["*"] est sécurisé avec des API Keys ?
R: Les clés API protègent l'accès aux données. CORS empêche seulement les
navigateurs web de faire des requêtes cross-origin. Un attaquant peut
contourner CORS facilement (curl, postman), donc la vraie sécurité vient
de la validation des clés API, pas du CORS.
Q: Pourquoi credentials=False avec allow_origins=["*"] ?
R: C'est une restriction du navigateur (spec CORS). Vous ne pouvez pas avoir
credentials=True ET origins=["*"] en même temps.
Q: Mes clients utilisent des IPs dynamiques, que faire ?
R: Utilisez mode "open". Les clés API sont justement faites pour ça -
permettre l'accès depuis n'importe quelle origine, de manière sécurisée.
Q: Je veux quand même utiliser des cookies/sessions ?
R: Utilisez mode "whitelist" ou "hybrid" pour les domaines qui ont besoin
de credentials, et laissez les autres utiliser X-API-Key sans credentials.
Q: Comment tester CORS localement ?
R: Créez un simple fichier HTML avec fetch() et ouvrez-le en file://
Ou utilisez un serveur local (python -m http.server)
"""