import hashlib import hmac from fastapi import APIRouter, HTTPException, Depends, Header from pydantic import BaseModel from config import settings router = APIRouter(prefix="/api/auth", tags=["auth"]) class LoginRequest(BaseModel): password: str class VerifyRequest(BaseModel): token: str def hash_password(pw: str) -> str: return hashlib.sha256(f"{pw}:livestream-salt".encode()).hexdigest() ADMIN_HASH = hash_password(settings.admin_password) def _token_for(password: str) -> str: """The admin token for a password. Derived, never stored. It used to live in a module-level set, which only works while there is exactly one process: uvicorn loads this module once per worker, so logging in on one worker left every other worker rejecting the token and the admin appeared to be logged out at random. A value that can be recomputed cannot drift between workers. """ return hashlib.sha256(f"{password}:token".encode()).hexdigest() def _is_valid(token: str) -> bool: # Constant-time compare against the one token this deployment can issue. return hmac.compare_digest(token, _token_for(settings.admin_password)) async def require_auth(authorization: str = Header(None)) -> bool: if not authorization: raise HTTPException(status_code=401, detail="Missing token") token = authorization.replace("Bearer ", "") if not _is_valid(token): raise HTTPException(status_code=401, detail="Invalid token") return True @router.post("/login") async def login(body: LoginRequest): if not hmac.compare_digest(hash_password(body.password), ADMIN_HASH): raise HTTPException(status_code=401, detail="Invalid password") return {"token": _token_for(body.password)} @router.post("/verify") async def verify(body: VerifyRequest): if not _is_valid(body.token): raise HTTPException(status_code=401, detail="Invalid token") return {"valid": True}