Files
docqube_backend/app/middleware/csrf.py
T
2026-09-08 18:32:55 +05:30

60 lines
2.4 KiB
Python

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="/"
)