ERP-DocIQ / backend /app /auth.py
kenmandal's picture
Deploy latest: ERP DocIQ NLQ chatbot + reasoning models (MiniCPM3-4B/Command R7B) + ERP fine-tuning + extreme OCR docs
082d661 verified
Raw
History Blame Contribute Delete
1.85 kB
"""HTTP Basic auth for the API.
Gate is a single ASGI middleware that protects `/api/*` (the data + functionality).
`/health`, the static SPA shell, and `/portal` stay open so the login screen can
load and Vercel health checks pass. Credentials come from APERTURE_USER /
APERTURE_PASS (defaults: demo / aperture2026 for the demo deployment).
Stateless: the SPA stores base64(user:pass) and sends it on every request; there
is no server-side session to manage on serverless.
"""
from __future__ import annotations
import base64
import secrets
from starlette.requests import Request
from starlette.responses import JSONResponse
def _check(authorization: str | None, user: str, pwd: str) -> bool:
if not authorization or not authorization.lower().startswith("basic "):
return False
try:
decoded = base64.b64decode(authorization.split(" ", 1)[1]).decode("utf-8")
u, _, p = decoded.partition(":")
except Exception:
return False
return secrets.compare_digest(u, user) and secrets.compare_digest(p, pwd)
def make_auth_middleware(user: str, pwd: str):
async def auth_middleware(request: Request, call_next):
path = request.url.path
# Only guard the API; allow preflight, health, SPA shell, and portal.
if request.method == "OPTIONS" or not path.startswith("/api/"):
return await call_next(request)
if _check(request.headers.get("authorization"), user, pwd):
return await call_next(request)
# NOTE: deliberately NO `WWW-Authenticate: Basic` header — that triggers the
# browser's native credential popup. The React SPA handles 401 itself and
# shows its own login screen, so we return a plain JSON 401.
return JSONResponse({"detail": "Authentication required"}, status_code=401)
return auth_middleware