import hashlib import hmac import secrets from fastapi import HTTPException, Request from itsdangerous import BadSignature, TimestampSigner from app.config import get_settings COOKIE = "fb_session" MAX_AGE = 60 * 60 * 12 # 12 h def hash_password(pw: str, salt: str | None = None) -> str: salt = salt or secrets.token_hex(16) digest = hashlib.pbkdf2_hmac("sha256", pw.encode(), salt.encode(), 200_000).hex() return f"{salt}${digest}" def verify_password(pw: str, stored: str) -> bool: try: salt, digest = stored.split("$", 1) except ValueError: return False return hmac.compare_digest(hash_password(pw, salt).split("$", 1)[1], digest) def _signer() -> TimestampSigner: return TimestampSigner(get_settings().session_secret) def make_session_token() -> str: return _signer().sign(b"gui").decode() def session_valid(token: str | None) -> bool: if not token: return False try: _signer().unsign(token, max_age=MAX_AGE) return True except BadSignature: return False def require_auth(request: Request) -> None: settings = get_settings() header = request.headers.get("authorization", "") if settings.api_key and hmac.compare_digest(header, f"Bearer {settings.api_key}"): return if session_valid(request.cookies.get(COOKIE)): return raise HTTPException(status_code=401, detail="Nicht angemeldet")