"""CLI: Passwort setzen oder Administrator anlegen. python -m app.scripts.reset_password python -m app.scripts.reset_password --username admin --password geheim123456 Nötig, weil die Erstanlage beim Start nur greift, solange noch kein Benutzer existiert – ein nachträglich geändertes `MONEYFY_ADMIN_PASSWORD` bleibt danach wirkungslos. """ import argparse import asyncio import getpass import os import sys from sqlalchemy import select from app.core.config import settings from app.core.security import hash_password from app.db.session import SessionLocal, engine from app.models import AppUser from app.services.auth import ( MIN_PASSWORD_LENGTH, get_user_by_username, revoke_all_refresh_tokens, ) def _passwort_ermitteln(vorgabe: str | None) -> str: """Reihenfolge: Argument, Umgebungsvariable, interaktive Eingabe.""" if vorgabe: return vorgabe aus_umgebung = os.environ.get("MONEYFY_ADMIN_PASSWORD") if aus_umgebung: print("Verwende MONEYFY_ADMIN_PASSWORD aus der Umgebung.") return aus_umgebung if not sys.stdin.isatty(): raise SystemExit( "Kein Passwort angegeben. Entweder --password nutzen oder " "MONEYFY_ADMIN_PASSWORD setzen." ) erste = getpass.getpass("Neues Passwort: ") zweite = getpass.getpass("Wiederholen: ") if erste != zweite: raise SystemExit("Die Eingaben stimmen nicht überein.") return erste async def main(username: str, passwort: str, force_change: bool) -> int: if len(passwort) < MIN_PASSWORD_LENGTH: print( f"Das Passwort muss mindestens {MIN_PASSWORD_LENGTH} Zeichen lang sein.", file=sys.stderr, ) return 1 async with SessionLocal() as session: benutzer = await get_user_by_username(session, username) if benutzer is None: vorhandene = (await session.execute(select(AppUser.username))).scalars().all() benutzer = AppUser(username=username, must_change_password=force_change) session.add(benutzer) aktion = "angelegt" if vorhandene: print(f"Vorhandene Benutzer: {', '.join(vorhandene)}") else: aktion = "geändert" benutzer.must_change_password = force_change # Ein Passwortwechsel beendet alle offenen Sitzungen. await revoke_all_refresh_tokens(session, benutzer.id) benutzer.password_hash = hash_password(passwort) await session.commit() await engine.dispose() print(f"Passwort für '{username}' {aktion}.") if force_change: print("Beim ersten Anmelden wird ein neues Passwort verlangt.") return 0 if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "--username", default=settings.moneyfy_admin_user, help=f"Benutzername (Vorgabe: {settings.moneyfy_admin_user}).", ) parser.add_argument( "--password", help="Neues Passwort. Ohne Angabe wird MONEYFY_ADMIN_PASSWORD genutzt " "oder interaktiv gefragt.", ) parser.add_argument( "--force-change", action="store_true", help="Verlangt beim nächsten Anmelden erneut einen Passwortwechsel.", ) args = parser.parse_args() raise SystemExit( asyncio.run(main(args.username, _passwort_ermitteln(args.password), args.force_change)) )