Live bei der Verifikation gefunden: konto_loeschen() lud das Zielkonto
über get_by_email(ziel['email']) nach - der email-Wert kam aus
fetch_benutzer_fuer_organisation()'s sync-Verbindungspfad, wo psycopg
TEXT-Spalten teils als bytes statt str liefert (dasselbe bereits bekannte,
nicht deterministische Verhalten wie in _row_to_user(), siehe dortiger
Kommentar) - die async get_by_email()-Abfrage bekam dadurch einen
bytes-Parameter und scheiterte mit 'operator does not exist: text =
bytea'.
Zwei Fixes: konto_loeschen() nutzt jetzt user_manager.get(id) statt des
E-Mail-Umwegs (nur async-Pfad, umgeht das Problem strukturell).
fetch_benutzer_fuer_organisation() bekommt zusätzlich denselben
defensiven bytes-Decode wie _row_to_user(), damit das nicht an anderer
Stelle erneut zuschlägt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neue Rolle ist_organisationsadmin (auth.py), bewusst getrennt von
is_superuser: is_superuser bleibt exklusiv internes, organisationsuebergreifendes
Tuxflotte-Personal mit Zugriff auf /admin/* - ist_organisationsadmin ist
auf die eigene Organisation beschränkt und kann nie über Self-Service
gesetzt werden (organization_id/is_superuser kommen bei
POST /organisation/konten nie aus dem Formular, sondern immer aus der
Session).
/organisation zeigt jetzt Kontakt-/Stammdaten (bearbeitbar) und die
Kolleg:innen-Liste der eigenen Organisation (für alle sichtbar,
Verwaltung nur für Org-Admins/internes Personal via require_org_admin).
Selbstlöschung des eigenen Kontos wird verweigert.
fetch_benutzer_fuer_organisation() von admin_kunden.py nach auth.py
verschoben (jetzt von beiden Routern importiert, kein Duplikat) -
liefert zusätzlich id + ist_organisationsadmin. admin_kunden.py bekommt
eine neue Organisationsdaten-Sektion (nutzt denselben PATCH-Endpoint)
und die neue Checkbox beim Konto-Anlegen, um den ersten Org-Admin einer
neuen Organisation zu bootstrappen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_row_to_user() dekodierte bislang nur hashed_password von bytes zu str,
obwohl psycopg dieselbe Eigenart (TEXT-Spalte kommt teils als bytes
zurück) auch bei email zeigt. Sichtbar z.B. im Header als
b'name@domain.tld' statt name@domain.tld.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
app.py auf APIRouter-Module umgestellt (db.py, templating.py,
anode_client.py, auth.py, routers/geraete.py + vier Admin-Router als
Platzhalter). benutzer.ist_admin (Migration 0002) + require_admin-
Dependency (403 ohne Admin-Flag). Admin-Nav-Link im Header nur für
Admin-Konten sichtbar. create_user.py um --admin-Flag erweitert.
Die vier Admin-Bereiche (Technik/Datenbank, Geräte, Flows, Kunden) sind
als Platzhalterseiten unter /admin/... verdrahtet und bekommen in den
nächsten Schritten echte Funktionalität.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>