""" Security middleware stack. 1. RequestSizeLimiter — reject bodies > 4 KB (waitlist only needs ~100 bytes) 2. OriginValidator — in production, reject requests without a valid Origin/Referer """ from fastapi import Request, Response from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import JSONResponse from app.config import settings class RequestSizeLimiter(BaseHTTPMiddleware): """Reject request bodies larger than max_bytes.""" MAX_BYTES = 4_096 # 4 KB — more than enough for a JSON email payload async def dispatch(self, request: Request, call_next) -> Response: content_length = request.headers.get("content-length") if content_length and int(content_length) > self.MAX_BYTES: return JSONResponse( status_code=413, content={"detail": "Request body too large."}, ) return await call_next(request) class OriginValidator(BaseHTTPMiddleware): """ In production, only allow requests whose Origin or Referer matches the allowed origins list. This mitigates CSRF/cross-origin abuse. Skipped in development so local testing works without custom headers. """ async def dispatch(self, request: Request, call_next) -> Response: if settings.ENVIRONMENT != "production": return await call_next(request) # Only check mutating methods if request.method not in ("POST", "PUT", "PATCH", "DELETE"): return await call_next(request) origin = request.headers.get("origin") or "" referer = request.headers.get("referer") or "" allowed = settings.origins_list origin_ok = any(origin.startswith(o) for o in allowed) if origin else False referer_ok = any(referer.startswith(o) for o in allowed) if referer else False if not origin_ok and not referer_ok: return JSONResponse( status_code=403, content={"detail": "Forbidden."}, ) return await call_next(request)