Spaces:
Running
Running
| """ | |
| Shared FastAPI dependencies. | |
| These are intentionally tiny — one concern per dependency — so they can be | |
| composed at the route or router level without surprises. | |
| Currently exposed: | |
| - `require_loopback`: 403 unless the request came from a loopback origin. | |
| """ | |
| import os | |
| from fastapi import HTTPException, Request | |
| # IPv4 + IPv6 loopback literals + the conventional `localhost` hostname. | |
| # `request.client.host` carries an address, not a hostname, so the literal | |
| # "localhost" entry is defensive — some upstream wrappers (TestClient with | |
| # a custom client tuple, certain reverse-proxy headers) may pass strings | |
| # rather than parsed addresses. We accept the broader set without weakening | |
| # the guard: nothing here matches a non-loopback origin. | |
| _LOOPBACK_HOSTS = frozenset({"127.0.0.1", "::1", "localhost"}) | |
| _IS_PUBLIC = os.environ.get("OMNIVOICE_BIND_HOST") == "0.0.0.0" | |
| def require_loopback(request: Request) -> None: | |
| """Reject any request whose `client.host` is not a loopback address. | |
| Use as a router-level dependency to protect every route on the router | |
| in one place: | |
| router = APIRouter(dependencies=[Depends(require_loopback)]) | |
| Or as a per-route dependency for narrower scope: | |
| @router.post("/foo", dependencies=[Depends(require_loopback)]) | |
| Returns None on success (FastAPI dependency convention). Raises 403 | |
| on rejection — the response body is `{"detail": "loopback origin required"}` | |
| so existing tests for `/system/set-env` keep passing without modification. | |
| """ | |
| if _IS_PUBLIC: | |
| return | |
| host = request.client.host if request.client else None | |
| if host not in _LOOPBACK_HOSTS: | |
| raise HTTPException(status_code=403, detail="loopback origin required") | |