"""Trivial passcode auth for the educator dashboard. The frontend stores the passcode in an httpOnly cookie set by /api/admin/login. Protected endpoints depend on `require_admin` to validate the cookie. """ from __future__ import annotations import secrets from fastapi import Cookie, HTTPException, status from config import ADMIN_PASSCODE COOKIE_NAME = "admin_session" def _expected_token() -> str: """A deterministic-but-opaque token derived from the passcode. Avoids storing the raw passcode in the cookie. We don't need full session management here — the passcode itself is the credential. """ if not ADMIN_PASSCODE: return "" # 32-char hex digest is plenty for a single-tenant educator template. import hashlib return hashlib.sha256(ADMIN_PASSCODE.encode("utf-8")).hexdigest() def verify_passcode(passcode: str) -> bool: if not ADMIN_PASSCODE: return False return secrets.compare_digest(passcode, ADMIN_PASSCODE) def issue_token() -> str: return _expected_token() def require_admin(admin_session: str | None = Cookie(default=None)) -> None: if not ADMIN_PASSCODE: raise HTTPException( status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="ADMIN_PASSCODE is not configured on the server.", ) if not admin_session or not secrets.compare_digest(admin_session, _expected_token()): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Admin authentication required.", )