from contextlib import asynccontextmanager from fastapi import FastAPI, Request, Response from fastapi.middleware.cors import CORSMiddleware from prometheus_client import CONTENT_TYPE_LATEST, Counter, Histogram, generate_latest from slowapi import Limiter from slowapi.errors import RateLimitExceeded from slowapi.middleware import SlowAPIMiddleware from slowapi.util import get_remote_address from starlette.responses import JSONResponse from app.api import admin, oidc, public from app.core.config import get_settings from app.core.database import create_all from app.core.security import load_or_create_oidc_key settings = get_settings() REQUESTS = Counter("nexamfa_http_requests_total", "HTTP requests", ["method", "path", "status"]) LATENCY = Histogram("nexamfa_http_request_duration_seconds", "HTTP request latency", ["method", "path"]) limiter = Limiter(key_func=get_remote_address, default_limits=[settings.rate_limit_default]) @asynccontextmanager async def lifespan(app: FastAPI): oidc.set_oidc_private_key(load_or_create_oidc_key(settings.oidc_signing_key_pem)) await create_all() yield app = FastAPI(title="NexaMFA", version="0.1.0", lifespan=lifespan) app.state.limiter = limiter app.add_middleware(SlowAPIMiddleware) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origin_list, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) @app.exception_handler(RateLimitExceeded) async def rate_limit_handler(_: Request, exc: RateLimitExceeded): return JSONResponse({"detail": "Rate limit exceeded"}, status_code=429) @app.middleware("http") async def metrics_middleware(request: Request, call_next): with LATENCY.labels(request.method, request.url.path).time(): response = await call_next(request) REQUESTS.labels(request.method, request.url.path, str(response.status_code)).inc() return response @app.get("/health") async def health(): return {"status": "ok", "service": "NexaMFA"} @app.get("/metrics") async def metrics(): return Response(generate_latest(), media_type=CONTENT_TYPE_LATEST) app.include_router(public.router) app.include_router(admin.router) app.include_router(oidc.router)