"""Origin validation middleware for state-changing requests.""" import logging from starlette.middleware.base import BaseHTTPMiddleware from starlette.requests import Request from starlette.responses import JSONResponse, Response logger = logging.getLogger("uvicorn") STATE_CHANGING_METHODS = {"POST", "PUT", "DELETE", "PATCH"} class OriginValidatorMiddleware(BaseHTTPMiddleware): """Reject state-changing requests with mismatched Origin header.""" def __init__(self, app, allowed_origins: list[str] | None = None) -> None: super().__init__(app) self._allowed = {o.rstrip("/") for o in (allowed_origins or [])} async def dispatch(self, request: Request, call_next) -> Response: if request.method not in STATE_CHANGING_METHODS: return await call_next(request) origin = request.headers.get("origin") if origin is None: return await call_next(request) if origin.rstrip("/") not in self._allowed: logger.warning(f"Rejected request from origin: {origin}") return JSONResponse( status_code=403, content={"detail": "Origin not allowed"}, ) return await call_next(request)