kundenplattform/scripts/create_user.py
Thomas Stallinger 576613ed0f feat: Auth auf fastapi-users umstellen
Ersetzt die handgestrickte bcrypt+Session-Auth durch fastapi-users
(CookieTransport+JWTStrategy statt Starlette-Session, custom
psycopg3-async-Adapter statt SQLAlchemy). Bestehende bcrypt-Hashes
bleiben gültig und werden beim nächsten Login automatisch auf Argon2
angehoben (pwdlib-Default-PasswordHelper). ist_admin wird zu
is_superuser, damit fastapi-users' eingebaute Semantik direkt nutzbar
ist - require_admin/get_current_user bleiben als Wrapper unter
gleichem Namen, alle Router-Dateien bis auf geraete.py (dict- zu
attribut-Zugriff auf user.organization_id) unverändert.

Lokal end-to-end gegen eine Wegwerf-Postgres-Instanz (podman) verifiziert:
Migration, Bestandskonto-Login mit altem Passwort samt Argon2-Upgrade,
Admin-Kontoanlage über beide Aufrufstellen (create_user.py und
admin_kunden.py), 403 für Nicht-Admins, Logout.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-06 08:29:51 +02:00

61 lines
1.9 KiB
Python

"""
Manuelle Konto-Anlage für die Kundenplattform (kein Self-Service in v1, siehe
ADR-0011). Beispiel:
KUNDENPLATTFORM_DATABASE_URL=... .venv/bin/python scripts/create_user.py \\
--organization-id 11111111-1111-4111-8111-111111111111 \\
--email admin@schule-beispiel.de
"""
import argparse
import asyncio
import getpass
import os
import sys
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from auth import UserCreate, UserDatabase, UserManager # noqa: E402
from db import get_async_database_connection # noqa: E402
async def konto_anlegen(organization_id: str, email: str, password: str, admin: bool):
async with await get_async_database_connection() as connection:
user_manager = UserManager(UserDatabase(connection))
await user_manager.create(
UserCreate(
email=email,
password=password,
organization_id=organization_id,
is_superuser=admin,
)
)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--organization-id", required=True)
parser.add_argument("--email", required=True)
parser.add_argument(
"--admin", action="store_true",
help="Konto bekommt organisationsübergreifenden Admin-Zugriff (/admin/...).",
)
args = parser.parse_args()
if not os.environ.get("KUNDENPLATTFORM_DATABASE_URL"):
sys.exit("KUNDENPLATTFORM_DATABASE_URL ist nicht gesetzt.")
password = getpass.getpass("Passwort: ")
password_confirm = getpass.getpass("Passwort (Wiederholung): ")
if password != password_confirm:
sys.exit("Passwörter stimmen nicht überein.")
asyncio.run(konto_anlegen(args.organization_id, args.email, password, args.admin))
print(f"Konto für {args.email} angelegt{' (Admin)' if args.admin else ''}.")
if __name__ == "__main__":
main()