refactor(security): clean up authentication middleware and api docs

This commit is contained in:
Fanilo-Nantenaina 2026-01-21 16:23:34 +03:00
parent 1c6c45465f
commit d25c2cffa9
2 changed files with 36 additions and 23 deletions

50
api.py
View file

@ -278,13 +278,12 @@ 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": {
@ -303,7 +302,7 @@ 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.",
}
@ -461,9 +460,18 @@ async def custom_openapi_endpoint(request: Request):
schema = generate_filtered_openapi_schema(app, allowed_tags, swagger_user)
if request.url.scheme == "https":
if "servers" not in schema or not schema["servers"]:
schema["servers"] = [{"url": str(request.base_url).rstrip("/")}]
scheme = request.url.scheme
forwarded_proto = request.headers.get("X-Forwarded-Proto")
if forwarded_proto:
scheme = forwarded_proto
base_url = str(request.base_url).rstrip("/")
if scheme == "https" and base_url.startswith("http://"):
base_url = base_url.replace("http://", "https://", 1)
schema["servers"] = [{"url": base_url}]
return JSONResponse(content=schema)
@ -479,19 +487,31 @@ async def custom_swagger_ui(request: Request):
headers={"WWW-Authenticate": 'Basic realm="Swagger UI"'},
)
allowed_tags = swagger_user.get("allowed_tags")
is_restricted = allowed_tags is not None and len(allowed_tags) > 0
swagger_params = {
"persistAuthorization": True,
"displayRequestDuration": True,
"filter": True,
"tryItOutEnabled": True,
"docExpansion": "list",
}
if is_restricted:
swagger_params["preAuthorizeApiKey"] = {
"ApiKeyAuth": {
"name": "X-API-Key",
"schema": {"type": "apiKey", "in": "header", "name": "X-API-Key"},
"value": "",
}
}
return get_swagger_ui_html(
openapi_url="/openapi.json",
title=f"{app.title} - Documentation",
swagger_favicon_url="https://fastapi.tiangolo.com/img/favicon.png",
swagger_ui_parameters={
"persistAuthorization": True,
"displayRequestDuration": True,
"filter": True,
"tryItOutEnabled": True,
"docExpansion": "list",
# CORRECTIF : Ne pas pré-remplir les credentials
"preAuthorizeApiKey": False,
},
swagger_ui_parameters=swagger_params,
)

View file

@ -158,18 +158,14 @@ 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)[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"
@ -178,19 +174,16 @@ 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")
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}")
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)