feat(V2): X-Bot-Tag header obbligatorio + endpoint /admin/audit con filtri

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
AdrianoDev
2026-05-01 08:51:40 +02:00
parent bd6b03ce43
commit 69ac878893
10 changed files with 549 additions and 8 deletions
+43 -4
View File
@@ -1,4 +1,8 @@
"""Bearer auth middleware: bearer token → request.state.environment."""
"""Bearer auth middleware: bearer token → request.state.environment.
Inoltre richiede header `X-Bot-Tag` su tutte le chiamate non whitelisted,
così che l'audit log identifichi il bot chiamante.
"""
from __future__ import annotations
import secrets
@@ -9,7 +13,17 @@ from fastapi.responses import JSONResponse
Environment = Literal["testnet", "mainnet"]
WHITELIST_PATHS = frozenset({"/health", "/apidocs", "/openapi.json", "/docs", "/redoc"})
# Path che bypassano sia bearer auth sia bot_tag check.
PATH_WHITELIST_FULL = frozenset(
{"/health", "/apidocs", "/openapi.json", "/docs", "/redoc"}
)
# Path che richiedono bearer ma NON il bot_tag (admin endpoint).
PATH_WHITELIST_BOT_TAG_ONLY = frozenset({"/admin/audit"})
# Backward-compat alias (vecchi import).
WHITELIST_PATHS = PATH_WHITELIST_FULL
MAX_BOT_TAG_LEN = 64
def _extract_bearer(auth_header: str) -> str | None:
@@ -35,13 +49,17 @@ def install_auth_middleware(
testnet_token: str,
mainnet_token: str,
) -> None:
"""Registra middleware di auth bearer sull'app FastAPI."""
"""Registra middleware di auth bearer + bot_tag sull'app FastAPI."""
@app.middleware("http")
async def auth_middleware(request: Request, call_next):
if request.url.path in WHITELIST_PATHS:
path = request.url.path
# 1. Whitelist totale: nessun check.
if path in PATH_WHITELIST_FULL:
return await call_next(request)
# 2. Bearer auth (sempre richiesto).
token = _extract_bearer(request.headers.get("Authorization", ""))
if token is None:
return JSONResponse(
@@ -57,4 +75,25 @@ def install_auth_middleware(
"message": "invalid token"}},
)
request.state.environment = env
# 3. Whitelist parziale (admin): bearer ok, no bot_tag check.
if path in PATH_WHITELIST_BOT_TAG_ONLY:
return await call_next(request)
# 4. X-Bot-Tag obbligatorio.
raw_tag = request.headers.get("X-Bot-Tag", "")
tag = raw_tag.strip() if raw_tag else ""
if not tag:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"error": {"code": "BAD_REQUEST",
"message": "missing X-Bot-Tag header"}},
)
if len(tag) > MAX_BOT_TAG_LEN:
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"error": {"code": "BAD_REQUEST",
"message": "X-Bot-Tag too long"}},
)
request.state.bot_tag = tag
return await call_next(request)