import base64 import hashlib import hmac import json import os import secrets from pathlib import Path from typing import Optional USERS_FILE = Path(os.getenv("DOCUMENT_PROCESSOR_USERS_FILE", "data/users.json")) def _b64e(b: bytes) -> str: return base64.b64encode(b).decode("utf-8") def _b64d(s: str) -> bytes: return base64.b64decode(s.encode("utf-8")) def hash_password(password: str) -> str: salt = secrets.token_bytes(16) digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 200_000) return f"pbkdf2_sha256$200000${_b64e(salt)}${_b64e(digest)}" def verify_password(password: str, stored: str) -> bool: try: scheme, iterations, salt_b64, digest_b64 = stored.split("$", 3) if scheme != "pbkdf2_sha256": return False digest = hashlib.pbkdf2_hmac( "sha256", password.encode("utf-8"), _b64d(salt_b64), int(iterations), ) return hmac.compare_digest(digest, _b64d(digest_b64)) except Exception: return False def load_users() -> list[dict]: if not USERS_FILE.exists(): return [] return json.loads(USERS_FILE.read_text()) def get_user_by_username(username: str) -> Optional[dict]: username = username.strip().lower() for user in load_users(): if user.get("username", "").lower() == username: return user return None def authenticate_user(username: str, password: str) -> Optional[dict]: user = get_user_by_username(username) if not user: return None if not verify_password(password, user.get("password_hash", "")): return None return { "username": user["username"], "display_name": user.get("display_name") or user["username"], "is_admin": bool(user.get("is_admin", False)), }