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>
Zwei neue Menuepunkte fuer eingeloggte Kunden (nicht der
organisationsuebergreifende /admin/-Bereich):
- "Organisation": Name bearbeiten (ruft Phase-3-PATCH auf
provisioning-server auf).
- "Installationsmedium": WLAN-SSID/-Passwort, Vorlagenwahl, Kontingent
und Gueltigkeitsdauer speichern, personalisierte Kunden-ISO per
Klick erstellen (Phase-4-Endpoints), Fortschritt/Status-Anzeige
(Meta-Refresh waehrend pending/running), Download + dd/Rufus-
Anleitung. Getrennte "aufladen"-Aktion erhoeht Kontingent/
Gueltigkeit der bestehenden Enrollment Session (neuer PATCH-
Endpoint), ohne eine neue ISO zu erzeugen - die bereits ausgeteilte
bleibt gueltig.
Neue Tabelle installationsmedium_konfiguration (Migration 0004, eigene
DB, kein Foreign Key auf provisioning-server-Ressourcen, siehe
ADR-0011) haelt SSID/verschluesselten PSK/Vorlage/Kontingent-Defaults
sowie die IDs der aktuell gueltigen Enrollment Session und des
zugehoerigen ISO-Baus.
WLAN-PSK wird Fernet-verschluesselt gespeichert (encryption.py, neuer
KUNDENPLATTFORM_ENCRYPTION_KEY) - echte Zugangsdaten, kein
Passwort-Hash-Fall. Download laeuft ueber einen Streaming-Proxy
(anode_stream() in anode_client.py) statt eines direkten Links, weil
der Browser kein KUNDENPLATTFORM_TOKEN besitzt.
Lokal end-to-end verifiziert (Wegwerf-Postgres fuer beide Services,
echter build_customer_iso.sh-Lauf ueber die Kundenplattform-UI):
Formular -> Enrollment Session + ISO-Bau -> Status-Polling ->
Download-Proxy liefert byte-identische, korrekt personalisierte Datei
(SSID/PSK/Aktivierungscode per xorriso-Extraktion gegengeprueft) ->
Aufladen erhoeht Kontingent/Gueltigkeit der bestehenden Session ohne
neuen Bau -> "neues Installationsmedium erstellen" erzeugt neue
Session + ISO, alte Session bleibt unangetastet nutzbar (alter
ISO-Bau wird laut Phase-4-Cleanup automatisch entfernt), leeres
PSK-Feld uebernimmt das zuvor gespeicherte PSK korrekt.
Organisation-Name-Aenderung ueber PATCH verifiziert. Unauthentifizierter
Zugriff auf beide neuen Routen leitet korrekt zu /login um.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>