Files
tueit_Transkriptor/auth.py
T

129 lines
4.2 KiB
Python

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")