import getpass import hashlib import os import secrets import tomllib from typing import Optional import tomli_w USERS_PATH = os.path.expanduser("~/.config/tueit-transcriber/users.toml") # In-memory session store: token → username # Users must re-login after server restart — acceptable for a desktop app. _sessions: dict[str, str] = {} def _hash_password(password: str) -> str: salt = secrets.token_hex(16) key = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 200_000).hex() return f"{salt}:{key}" def _verify_password(password: str, stored: str) -> bool: try: salt, key = stored.split(":", 1) except ValueError: return False new_key = hashlib.pbkdf2_hmac("sha256", password.encode(), salt.encode(), 200_000).hex() return secrets.compare_digest(new_key, key) # ── User store ───────────────────────────────────────────────────────────────── def has_users() -> bool: return bool(_load_users()) def _load_users() -> dict: if not os.path.exists(USERS_PATH): return {} with open(USERS_PATH, "rb") as f: return tomllib.load(f).get("users", {}) def _save_users(users: dict): os.makedirs(os.path.dirname(USERS_PATH), exist_ok=True) with open(USERS_PATH, "wb") as f: tomli_w.dump({"users": users}, f) def create_user(username: str, password: str, output_dir: str, is_admin: bool = False): users = _load_users() users[username] = { "password_hash": _hash_password(password), "output_dir": output_dir, "is_admin": is_admin, } _save_users(users) # ── Session management ───────────────────────────────────────────────────────── def authenticate(username: str, password: str) -> Optional[str]: """Verify credentials. Returns a session token on success, None on failure.""" users = _load_users() user = users.get(username) if not user: return None if not _verify_password(password, user["password_hash"]): return None token = secrets.token_urlsafe(32) _sessions[token] = username return token def get_user_for_token(token: str) -> Optional[dict]: """Return user info dict for a valid token, or None.""" username = _sessions.get(token) if not username: return None users = _load_users() user = users.get(username) if not user: return None return { "username": username, "output_dir": user["output_dir"], "is_admin": user.get("is_admin", False), } def invalidate_token(token: str): _sessions.pop(token, None) # ── First-run setup wizard ───────────────────────────────────────────────────── def setup_wizard(): """Interactive console setup. Runs when no users exist yet.""" print("\n=== tüit Transkriptor — Ersteinrichtung ===\n") print("Bitte richte den ersten Nutzer ein (wird Administrator).\n") while True: username = input("Benutzername: ").strip() if username: break print("Benutzername darf nicht leer sein.") while True: password = getpass.getpass("Passwort: ") confirm = getpass.getpass("Passwort bestätigen: ") if password != confirm: print("Passwörter stimmen nicht überein.") continue if len(password) < 6: print("Passwort muss mindestens 6 Zeichen lang sein.") continue break default_dir = os.path.expanduser(f"~/Transkripte/{username}") answer = input(f"Transkripte speichern unter [{default_dir}]: ").strip() output_dir = answer if answer else default_dir create_user(username, password, output_dir, is_admin=True) print(f"\nNutzer '{username}' wurde angelegt.") print(f"Transkripte werden gespeichert unter: {output_dir}") print("\nWeitere Nutzer können später über die Web-Oberfläche hinzugefügt werden.\n")