Create and configure the FastAPI application.
()
| 52 | |
| 53 | |
| 54 | def create_app() -> FastAPI: |
| 55 | """Create and configure the FastAPI application.""" |
| 56 | api_key = os.environ.get("SECSUITE_API_KEY", "") |
| 57 | |
| 58 | app = FastAPI( |
| 59 | title="Security Suite API", |
| 60 | description=( |
| 61 | "REST API for Security Suite — security scanning, API testing, and analysis.\n\n" |
| 62 | "Set the `SECSUITE_API_KEY` environment variable to require an `X-API-Key` header " |
| 63 | "on all endpoints (except `/health` and `/docs`)." |
| 64 | ), |
| 65 | version="0.2.0", |
| 66 | docs_url=None, # we serve custom docs below |
| 67 | redoc_url=None, |
| 68 | ) |
| 69 | |
| 70 | app.add_middleware( |
| 71 | CORSMiddleware, |
| 72 | allow_origins=["*"], |
| 73 | allow_credentials=True, |
| 74 | allow_methods=["*"], |
| 75 | allow_headers=["*"], |
| 76 | ) |
| 77 | |
| 78 | # Optional API key gate — skips /health, /docs, /openapi.json |
| 79 | if api_key: |
| 80 | _UNPROTECTED = {"/health", "/docs", "/openapi.json", "/redoc"} |
| 81 | |
| 82 | @app.middleware("http") |
| 83 | async def require_api_key(request: Request, call_next): |
| 84 | if request.url.path not in _UNPROTECTED: |
| 85 | provided = request.headers.get("X-API-Key", "") |
| 86 | # Constant-time compare so the key can't be recovered via timing. |
| 87 | if not secrets.compare_digest(provided, api_key): |
| 88 | # Return the response directly: an HTTPException raised inside |
| 89 | # BaseHTTPMiddleware is not handled by FastAPI's exception |
| 90 | # handlers and would surface as an uncaught 500. |
| 91 | return JSONResponse( |
| 92 | status_code=401, |
| 93 | content={"detail": "Invalid or missing X-API-Key"}, |
| 94 | ) |
| 95 | return await call_next(request) |
| 96 | |
| 97 | @app.get("/docs", include_in_schema=False) |
| 98 | async def swagger_ui() -> HTMLResponse: |
| 99 | return HTMLResponse(_SWAGGER_HTML) |
| 100 | |
| 101 | # Routers |
| 102 | app.include_router(health.router, tags=["Health"]) |
| 103 | app.include_router(scans.router, prefix="/api/v1/scans", tags=["Scans"]) |
| 104 | app.include_router(results.router, prefix="/api/v1/results", tags=["Results"]) |
| 105 | app.include_router(modules.router, prefix="/api/v1/modules", tags=["Modules"]) |
| 106 | |
| 107 | # API security testing router (imported lazily so fastapi is optional) |
| 108 | try: |
| 109 | from api.routers import apisec as apisec_router |
| 110 | app.include_router( |
| 111 | apisec_router.router, |