From 1c6c45465f97f2ea45b8344625b71b331a1dbd75 Mon Sep 17 00:00:00 2001 From: Fanilo-Nantenaina Date: Wed, 21 Jan 2026 14:00:57 +0300 Subject: [PATCH] fix(security): improve api key and jwt validation handling --- api.py | 17 ++++++++++++----- middleware/security.py | 11 +++++++++-- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/api.py b/api.py index fe73373..23289b0 100644 --- a/api.py +++ b/api.py @@ -278,18 +278,21 @@ def get_auth_schemes_for_user(swagger_user: dict) -> dict: allowed_tags = swagger_user.get("allowed_tags") if not allowed_tags: + # Admin complet return { "HTTPBearer": { "type": "http", "scheme": "bearer", "bearerFormat": "JWT", - "description": "Authentification JWT pour utilisateurs (POST /auth/login)", + "description": "đŸŽ« Authentification JWT pour utilisateurs (POST /auth/login). " + "Utilisez SOIT JWT SOIT API Key, pas les deux.", }, "ApiKeyAuth": { "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "ClĂ© API pour intĂ©grations externes (format: sdk_live_xxx)", + "description": " ClĂ© API pour intĂ©grations externes (format: sdk_live_xxx). " + "Utilisez SOIT JWT SOIT API Key, pas les deux.", }, } @@ -300,14 +303,16 @@ def get_auth_schemes_for_user(swagger_user: dict) -> dict: "type": "http", "scheme": "bearer", "bearerFormat": "JWT", - "description": "Authentification JWT pour utilisateurs (POST /auth/login)", + "description": "đŸŽ« Authentification JWT pour utilisateurs (POST /auth/login). " + "Utilisez SOIT JWT SOIT API Key, pas les deux.", } schemes["ApiKeyAuth"] = { "type": "apiKey", "in": "header", "name": "X-API-Key", - "description": "ClĂ© API pour intĂ©grations externes (format: sdk_live_xxx)", + "description": " ClĂ© API pour intĂ©grations externes (format: sdk_live_xxx). " + "Utilisez SOIT JWT SOIT API Key, pas les deux.", } return schemes @@ -483,7 +488,9 @@ async def custom_swagger_ui(request: Request): "displayRequestDuration": True, "filter": True, "tryItOutEnabled": True, - "docExpansion": "list", # Meilleure UX + "docExpansion": "list", + # CORRECTIF : Ne pas prĂ©-remplir les credentials + "preAuthorizeApiKey": False, }, ) diff --git a/middleware/security.py b/middleware/security.py index efd25fa..a1d0dda 100644 --- a/middleware/security.py +++ b/middleware/security.py @@ -158,14 +158,18 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware): auth_header = request.headers.get("Authorization") api_key_header = request.headers.get("X-API-Key") + # CORRECTIF : Nettoyer et valider la clĂ© API if api_key_header: api_key_header = api_key_header.strip() + # Si la clĂ© est vide ou juste des espaces, la considĂ©rer comme absente if not api_key_header or api_key_header == "": api_key_header = None + # VĂ©rifier si c'est un token JWT Bearer if auth_header and auth_header.startswith("Bearer "): - token = auth_header.split(" ")[1].strip() + token = auth_header.split(" ", 1)[1].strip() + # CORRECTIF : VĂ©rifier si c'est une API Key envoyĂ©e par erreur dans Authorization if token.startswith("sdk_live_"): logger.warning( "⚠ API Key envoyĂ©e dans Authorization au lieu de X-API-Key" @@ -174,16 +178,19 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware): request, token, path, method, call_next ) + # C'est un JWT valide, dĂ©lĂ©guer Ă  FastAPI logger.debug(f"đŸŽ« JWT dĂ©tectĂ© pour {method} {path} → dĂ©lĂ©gation Ă  FastAPI") request.state.authenticated_via = "jwt" return await call_next(request) + # CORRECTIF : Si une clĂ© API est prĂ©sente ET non vide, la traiter if api_key_header: - logger.debug(f"🔑 API Key dĂ©tectĂ©e pour {method} {path}") + logger.debug(f" API Key dĂ©tectĂ©e pour {method} {path}") return await self._handle_api_key_auth( request, api_key_header, path, method, call_next ) + # Aucune authentification fournie, dĂ©lĂ©guer Ă  FastAPI qui renverra 401 logger.debug(f"❌ Aucune auth pour {method} {path} → dĂ©lĂ©gation Ă  FastAPI") return await call_next(request)