From 98ad65d2481771b443fca55b701535caf5d287c4 Mon Sep 17 00:00:00 2001 From: Adriano Dal Pastro Date: Mon, 25 May 2026 20:45:21 +0000 Subject: [PATCH] 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) --- src/backend/main.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/backend/main.py b/src/backend/main.py index f1e1923..276e202 100644 --- a/src/backend/main.py +++ b/src/backend/main.py @@ -4,10 +4,14 @@ from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import JSONResponse from slowapi import _rate_limit_exceeded_handler 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.config import settings 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.sessions import router as sessions_router @@ -41,6 +45,19 @@ app = FastAPI( 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.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)