feat: protect /docs and /redoc with API key

Adds middleware requiring X-Api-Key header or ?api_key= query param
to access Swagger UI and ReDoc. OpenAPI schema remains public.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-05-25 20:45:21 +00:00
parent 5d78d865a4
commit 98ad65d248
+17
View File
@@ -4,10 +4,14 @@ from contextlib import asynccontextmanager
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from slowapi import _rate_limit_exceeded_handler from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded from slowapi.errors import RateLimitExceeded
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from src.backend.api.dependencies import limiter from src.backend.api.dependencies import limiter
from src.backend.config import settings
from src.backend.api.routes.health import router as health_router from src.backend.api.routes.health import router as health_router
from src.backend.api.routes.requests import router as requests_router from src.backend.api.routes.requests import router as requests_router
from src.backend.api.routes.sessions import router as sessions_router from src.backend.api.routes.sessions import router as sessions_router
@@ -41,6 +45,19 @@ app = FastAPI(
lifespan=lifespan, lifespan=lifespan,
) )
PROTECTED_DOCS_PATHS = {"/docs", "/redoc"}
class DocsAuthMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
if request.url.path in PROTECTED_DOCS_PATHS:
key = request.headers.get("X-Api-Key") or request.query_params.get("api_key")
if key != settings.api_key:
return JSONResponse(status_code=403, content={"detail": "API key required"})
return await call_next(request)
app.add_middleware(DocsAuthMiddleware)
app.state.limiter = limiter app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)