diff --git a/api.py b/api.py index 97d4425..7eb5984 100644 --- a/api.py +++ b/api.py @@ -187,8 +187,18 @@ def custom_openapi(): ) openapi_schema["components"]["securitySchemes"] = { - "HTTPBearer": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT"}, - "ApiKeyAuth": {"type": "apiKey", "in": "header", "name": "X-API-Key"}, + "HTTPBearer": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT", + "description": "Authentification JWT pour utilisateurs (POST /auth/login)", + }, + "ApiKeyAuth": { + "type": "apiKey", + "in": "header", + "name": "X-API-Key", + "description": "Clé API pour intégrations externes (format: sdk_live_xxx)", + }, } openapi_schema["security"] = [{"HTTPBearer": []}, {"ApiKeyAuth": []}] diff --git a/middleware/security.py b/middleware/security.py index e9ff831..98801dc 100644 --- a/middleware/security.py +++ b/middleware/security.py @@ -132,26 +132,30 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware): method = request.method if self._is_excluded_path(path): - logger.debug(f" Route publique: {method} {path}") return await call_next(request) auth_header = request.headers.get("Authorization") - has_jwt = auth_header and auth_header.startswith("Bearer ") + api_key_header = request.headers.get("X-API-Key") - api_key = request.headers.get("X-API-Key") - has_api_key = bool(api_key) + if auth_header and auth_header.startswith("Bearer "): + token = auth_header.split(" ")[1] - if has_jwt: - logger.debug(f" JWT détecté pour {method} {path}") - return await call_next(request) + if token.startswith("sdk_live_"): + logger.warning( + " API Key envoyée dans Authorization au lieu de X-API-Key" + ) + api_key_header = token + else: + logger.debug(f" JWT détecté pour {method} {path}") + return await call_next(request) - if has_api_key: + if api_key_header: logger.debug(f" API Key détectée pour {method} {path}") return await self._handle_api_key_auth( - request, api_key, path, method, call_next + request, api_key_header, path, method, call_next ) - logger.warning(f" Aucune authentification pour {method} {path}") + logger.warning(f" Aucune authentification: {method} {path}") return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, content={ @@ -170,7 +174,7 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware): method: str, call_next: Callable, ): - """Gère l'authentification par API Key""" + """Gère l'authentification par API Key avec vérification STRICTE""" try: from database.db_config import async_session_factory from services.api_key import ApiKeyService @@ -181,7 +185,7 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware): api_key_obj = await service.verify_api_key(api_key) if not api_key_obj: - logger.warning(f" Clé API invalide pour {method} {path}") + logger.warning(f" Clé API invalide: {method} {path}") return JSONResponse( status_code=status.HTTP_401_UNAUTHORIZED, content={ @@ -192,7 +196,7 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware): is_allowed, rate_info = await service.check_rate_limit(api_key_obj) if not is_allowed: - logger.warning(f"⚠️ Rate limit dépassé: {api_key_obj.name}") + logger.warning(f"⚠️ Rate limit: {api_key_obj.name}") return JSONResponse( status_code=status.HTTP_429_TOO_MANY_REQUESTS, content={"detail": "Rate limit dépassé"}, @@ -203,28 +207,37 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware): ) has_access = await service.check_endpoint_access(api_key_obj, path) + if not has_access: - logger.warning( - f"Accès refusé: {api_key_obj.name} → {method} {path}" + import json + + allowed = ( + json.loads(api_key_obj.allowed_endpoints) + if api_key_obj.allowed_endpoints + else ["Tous"] ) + + logger.warning( + f" ACCÈS REFUSÉ: {api_key_obj.name}\n" + f" Endpoint demandé: {path}\n" + f" Endpoints autorisés: {allowed}" + ) + return JSONResponse( status_code=status.HTTP_403_FORBIDDEN, content={ "detail": "Accès non autorisé à cet endpoint", - "endpoint": path, + "endpoint_requested": path, "api_key_name": api_key_obj.name, - "allowed_endpoints": ( - api_key_obj.allowed_endpoints - if api_key_obj.allowed_endpoints - else "Tous" - ), + "allowed_endpoints": allowed, + "hint": "Cette clé API n'a pas accès à cet endpoint. Contactez l'administrateur.", }, ) request.state.api_key = api_key_obj request.state.authenticated_via = "api_key" - logger.info(f"✅ API Key valide: {api_key_obj.name} → {method} {path}") + logger.info(f" ACCÈS AUTORISÉ: {api_key_obj.name} → {method} {path}") return await call_next(request) @@ -232,7 +245,7 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware): logger.error(f" Erreur validation API Key: {e}", exc_info=True) return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - content={"detail": "Erreur interne lors de la validation"}, + content={"detail": f"Erreur interne: {str(e)}"}, )