import uuid from starlette.middleware.base import BaseHTTPMiddleware from fastapi import Request from fastapi.responses import JSONResponse from app.core.settings import settings class CSRFMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): csrf_cookie = request.cookies.get("csrf_token") csrf_header = request.headers.get("x-csrf-token") or request.headers.get("X-CSRF-Token") needs_validation = ( request.method in ["POST", "PUT", "PATCH", "DELETE"] and not request.url.path.startswith("/api/auth/") and not request.url.path.startswith("/api/sso/") and not request.url.path == "/api/me/tutorial/finish" and not "/signing/ocr" in request.url.path and not "webhook" in request.url.path ) if needs_validation and not settings.DISABLE_CSRF: if not csrf_cookie or not csrf_header or csrf_cookie != csrf_header: import logging logger = logging.getLogger(__name__) logger.warning( f"CSRF Failure on {request.url.path}: " f"cookie_present={bool(csrf_cookie)}, " f"header_present={bool(csrf_header)}, " f"match={csrf_cookie == csrf_header}" ) response = JSONResponse( status_code=403, content={"detail": "CSRF verification failed"} ) if not csrf_cookie: self._set_csrf_cookie(response, request) return response response = await call_next(request) if not csrf_cookie: self._set_csrf_cookie(response, request) return response def _set_csrf_cookie(self, response, request: Request = None): """Helper to set the CSRF cookie with appropriate security flags.""" is_secure = settings.APP_ENV in ["production", "test", "testing"] or (request and request.url.scheme == "https") samesite_mode = "none" if is_secure else "lax" cookie_domain = getattr(settings, "COOKIE_DOMAIN", None) or None response.set_cookie( key="csrf_token", value=str(uuid.uuid4()), httponly=False, samesite=samesite_mode, secure=is_secure, domain=cookie_domain, path="/" )