Spaces:
Sleeping
Sleeping
File size: 1,557 Bytes
4b81334 6be14c8 4b81334 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | """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.",
)
|