60 lines
2.4 KiB
Python
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="/"
|
|
) |