refactor(security): clean up authentication middleware and api docs
This commit is contained in:
parent
1c6c45465f
commit
d25c2cffa9
2 changed files with 36 additions and 23 deletions
48
api.py
48
api.py
|
|
@ -278,13 +278,12 @@ def get_auth_schemes_for_user(swagger_user: dict) -> dict:
|
||||||
allowed_tags = swagger_user.get("allowed_tags")
|
allowed_tags = swagger_user.get("allowed_tags")
|
||||||
|
|
||||||
if not allowed_tags:
|
if not allowed_tags:
|
||||||
# Admin complet
|
|
||||||
return {
|
return {
|
||||||
"HTTPBearer": {
|
"HTTPBearer": {
|
||||||
"type": "http",
|
"type": "http",
|
||||||
"scheme": "bearer",
|
"scheme": "bearer",
|
||||||
"bearerFormat": "JWT",
|
"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.",
|
"Utilisez SOIT JWT SOIT API Key, pas les deux.",
|
||||||
},
|
},
|
||||||
"ApiKeyAuth": {
|
"ApiKeyAuth": {
|
||||||
|
|
@ -303,7 +302,7 @@ def get_auth_schemes_for_user(swagger_user: dict) -> dict:
|
||||||
"type": "http",
|
"type": "http",
|
||||||
"scheme": "bearer",
|
"scheme": "bearer",
|
||||||
"bearerFormat": "JWT",
|
"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.",
|
"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)
|
schema = generate_filtered_openapi_schema(app, allowed_tags, swagger_user)
|
||||||
|
|
||||||
if request.url.scheme == "https":
|
scheme = request.url.scheme
|
||||||
if "servers" not in schema or not schema["servers"]:
|
forwarded_proto = request.headers.get("X-Forwarded-Proto")
|
||||||
schema["servers"] = [{"url": str(request.base_url).rstrip("/")}]
|
|
||||||
|
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)
|
return JSONResponse(content=schema)
|
||||||
|
|
||||||
|
|
@ -479,19 +487,31 @@ async def custom_swagger_ui(request: Request):
|
||||||
headers={"WWW-Authenticate": 'Basic realm="Swagger UI"'},
|
headers={"WWW-Authenticate": 'Basic realm="Swagger UI"'},
|
||||||
)
|
)
|
||||||
|
|
||||||
return get_swagger_ui_html(
|
allowed_tags = swagger_user.get("allowed_tags")
|
||||||
openapi_url="/openapi.json",
|
is_restricted = allowed_tags is not None and len(allowed_tags) > 0
|
||||||
title=f"{app.title} - Documentation",
|
|
||||||
swagger_favicon_url="https://fastapi.tiangolo.com/img/favicon.png",
|
swagger_params = {
|
||||||
swagger_ui_parameters={
|
|
||||||
"persistAuthorization": True,
|
"persistAuthorization": True,
|
||||||
"displayRequestDuration": True,
|
"displayRequestDuration": True,
|
||||||
"filter": True,
|
"filter": True,
|
||||||
"tryItOutEnabled": True,
|
"tryItOutEnabled": True,
|
||||||
"docExpansion": "list",
|
"docExpansion": "list",
|
||||||
# CORRECTIF : Ne pas pré-remplir les credentials
|
}
|
||||||
"preAuthorizeApiKey": False,
|
|
||||||
},
|
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=swagger_params,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -158,18 +158,14 @@ class ApiKeyMiddlewareHTTP(BaseHTTPMiddleware):
|
||||||
auth_header = request.headers.get("Authorization")
|
auth_header = request.headers.get("Authorization")
|
||||||
api_key_header = request.headers.get("X-API-Key")
|
api_key_header = request.headers.get("X-API-Key")
|
||||||
|
|
||||||
# CORRECTIF : Nettoyer et valider la clé API
|
|
||||||
if api_key_header:
|
if api_key_header:
|
||||||
api_key_header = api_key_header.strip()
|
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 == "":
|
if not api_key_header or api_key_header == "":
|
||||||
api_key_header = None
|
api_key_header = None
|
||||||
|
|
||||||
# Vérifier si c'est un token JWT Bearer
|
|
||||||
if auth_header and auth_header.startswith("Bearer "):
|
if auth_header and auth_header.startswith("Bearer "):
|
||||||
token = auth_header.split(" ", 1)[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_"):
|
if token.startswith("sdk_live_"):
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"⚠️ API Key envoyée dans Authorization au lieu de X-API-Key"
|
"⚠️ 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
|
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"
|
request.state.authenticated_via = "jwt"
|
||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
|
|
||||||
# CORRECTIF : Si une clé API est présente ET non vide, la traiter
|
|
||||||
if api_key_header:
|
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(
|
return await self._handle_api_key_auth(
|
||||||
request, api_key_header, path, method, call_next
|
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")
|
logger.debug(f"❌ Aucune auth pour {method} {path} → délégation à FastAPI")
|
||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue