Zum Inhalt springen

Account-Verwaltung — Design Spec

Datum: 2026-04-13 Status: Umgesetzt (E-Mail-Handler ausstehend) Scope: Stufe 1 — Rudimentaere Account-Verwaltung

Vier zusammenhaengende Features fuer die Account-Verwaltung:

  1. Eigenes Profil bearbeiten — Display-Name, Passwort aendern
  2. Password-Reset durch Admins — Code-basiert (6-stellig per E-Mail), kein Self-Service
  3. Team-Verwaltung — Accounts anlegen, deaktivieren/reaktivieren, Rollen
  4. Sessions verwalten — Aktive Sessions einsehen, einzeln oder alle terminieren
Aktionagency_owneragency_employeetenant_admintenant_member
Eigenes Profil bearbeitenjajajaja
Eigenes Passwort aendernjajajaja
Eigene Sessions verwaltenjajajaja
Accounts anlegenalle Rollentenant_member
Accounts deaktivieren/reaktivierenalle ausser sich selbsteigene tenant_member
Password-Reset ausloesenalle ausser sich selbsteigene tenant_member

Tabelle password_reset (neu):

SpalteTypBeschreibung
idUUID PKUUIDv7
account_idUUID FK → accountBetroffener Account
codeVARCHAR(6)Kryptographisch zufaelliger 6-stelliger Code
initiated_byUUID FK → accountAdmin der den Reset ausgeloest hat
expires_atTIMESTAMPTZ30 Minuten Gueltigkeit
verified_atTIMESTAMPTZ (nullable)Zeitpunkt der Code-Verifikation
used_atTIMESTAMPTZ (nullable)Zeitpunkt der Passwort-Aenderung
Audit-Feldercreated_at, created_by, row_version, row_period

Tabelle account — neue Spalte:

  • locale VARCHAR(5) DEFAULT ‘de’ NOT NULL — Bevorzugte CMS-Sprache (de/en), vom User aenderbar

Bestehende Tabellen — keine strukturellen Aenderungen:

  • account.active — existiert bereits fuer Deaktivierung
  • session.terminated_at — existiert bereits fuer Session-Terminierung

Neue E-Mail-Templates (Seed in Migration):

  • password_reset_code — Reset-Code an betroffenen Account
  • password_changed — Benachrichtigung nach Passwort-Aenderung
  • account_deactivated — Benachrichtigung bei Deaktivierung
  • new_device_login — Benachrichtigung bei Login von unbekanntem Geraet

Neue Uebersetzungen (DE + EN Seeds fuer alle Templates).

  • UpdateProfile(ctx, accountID, displayName, locale) — eigenen Display-Name und Sprache aendern
  • ChangePassword(ctx, accountID, oldPassword, newPassword) — eigenes Passwort aendern (altes Passwort als Bestaetigung), loest password_changed E-Mail aus
  • ListSessions(ctx, accountID) — eigene aktive Sessions mit Geraet, IP, Geolocation, letzter Aktivitaet, erstellt-am
  • TerminateSession(ctx, accountID, sessionID) — einzelne eigene Session beenden
  • TerminateOtherSessions(ctx, accountID, currentSessionID) — alle Sessions ausser der aktuellen beenden
  • AccountRepository — FindByID, UpdateProfile (displayName + locale), UpdatePasswordHash
  • SessionRepository — ListActiveByAccount (JOIN geolocation), TerminateByID, TerminateAllExcept
PUT /api/protected/account/profile
PUT /api/protected/account/password
GET /api/protected/account/sessions
DELETE /api/protected/account/sessions/{id}
DELETE /api/protected/account/sessions
  • ErrInvalidCurrentPassword — altes Passwort falsch
  • ErrSessionNotFound — Session existiert nicht oder gehoert nicht dem User
  • ErrCannotTerminateCurrentSession — bei DELETE /sessions/{id} wenn es die eigene aktuelle Session ist
  • CreateAccount(ctx, adminID, email, displayName, role, tenantID?, locale?) — neuen Account anlegen (ohne Passwort), loest welcome E-Mail aus. Account muss anschliessend per Password-Reset sein Passwort setzen (Admin loest Reset aus oder automatisch nach Erstellung)
    • agency_owner → kann agency_employee, tenant_admin, tenant_member anlegen
    • tenant_admin → kann nur tenant_member im eigenen Tenant anlegen
  • ListAccounts(ctx, adminID) — Accounts auflisten die man verwalten darf
    • agency_owner → alle Accounts
    • tenant_admin → nur eigener Tenant
  • DeactivateAccount(ctx, adminID, targetAccountID) — Account deaktivieren (active=false), alle Sessions terminieren, loest account_deactivated E-Mail aus
  • ReactivateAccount(ctx, adminID, targetAccountID) — Account wieder aktivieren
  • InitiatePasswordReset(ctx, adminID, targetAccountID) — Reset-Code generieren, per E-Mail an Ziel-Account senden
  • VerifyResetCode(ctx, accountID, code) — Code pruefen
  • CompletePasswordReset(ctx, accountID, code, newPassword) — neues Passwort setzen, used_at setzen, alle Sessions terminieren, loest password_changed E-Mail aus

In jedem UseCase:

  • agency_owner → darf Agency-Accounts + alle Tenant-Accounts verwalten
  • tenant_admin → darf nur tenant_member im eigenen Tenant verwalten
  • Niemand kann sich selbst deaktivieren oder eigenen Reset ausloesen

Agency-Routen (agency_owner):

POST /api/agency/team
GET /api/agency/team
PUT /api/agency/team/{id}/deactivate
PUT /api/agency/team/{id}/reactivate
POST /api/agency/team/{id}/reset-password

Client-Routen (tenant_admin):

POST /api/client/team
GET /api/client/team
PUT /api/client/team/{id}/deactivate
PUT /api/client/team/{id}/reactivate
POST /api/client/team/{id}/reset-password

Public-Routen (Password-Reset Completion):

POST /api/public/auth/verify-reset
POST /api/public/auth/complete-reset

Die Verify/Complete-Routen sind public, weil der betroffene User moeglicherweise nicht eingeloggt ist (Admin hat Reset ausgeloest).

  • ErrInsufficientPermission — keine Berechtigung fuer Ziel-Account
  • ErrCannotModifySelf — Selbst-Deaktivierung/Reset nicht erlaubt
  • ErrAccountAlreadyActive / ErrAccountAlreadyInactive
  • ErrResetCodeInvalid / ErrResetCodeExpired / ErrResetCodeUsed
  • ErrEmailAlreadyExists — E-Mail bereits vergeben
/_app/account/profile — Display-Name bearbeiten
/_app/account/password — Passwort aendern
/_app/account/sessions — Aktive Sessions verwalten
/_app/team — Account-Liste (agency oder tenant)
/_app/team/create — Neuen Account anlegen
/_auth/reset-password — Code + neues Passwort eingeben (public)
  • ProfileForm — Display-Name Feld, Sprache (DE/EN Dropdown), Speichern-Button
  • ChangePasswordForm — Altes Passwort, Neues Passwort, Bestaetigung, Zod-Validierung
  • SessionList — Tabelle mit Geraet, IP, Standort (DE), Datum, “Abmelden”-Button, “Alle anderen abmelden”-Button, aktuelle Session markiert
  • TeamTable — Account-Liste mit Name, E-Mail, Rolle, Status (aktiv/inaktiv), Aktionen
  • CreateAccountForm — E-Mail, Display-Name, Rolle (Dropdown je nach eigenem Level), Tenant (nur fuer Agency)
  • ResetPasswordForm — 6-stelliger Code + neues Passwort (aehnlich wie 2FA-Form)

AppShell bekommt Sidebar/Menu mit:

  • “Konto” (Profil, Passwort, Sessions) — fuer alle Rollen
  • “Team” — nur fuer agency_owner und tenant_admin

Alle Strings in de.ts und en.ts, Keys unter account.* und team.*.

  • GET /api/public/auth/session liefert locale im Response mit
  • Frontend setzt i18next.changeLanguage(locale) beim App-Start basierend auf Session-Response
  • Sprachwechsel im Profil aktualisiert sofort die UI-Sprache (optimistic update)
  • sessionResponseSchema bekommt neues Feld locale
TemplateAusloeserPlatzhalter
password_reset_codeAdmin loest Reset aus{{code}}, {{initiated_by}}, {{expires_minutes}}
password_changedPasswort geaendert (Self-Service oder Reset){{display_name}}, {{timestamp}}, {{device}}, {{ip}}
account_deactivatedAdmin deaktiviert Account{{display_name}}, {{initiated_by}}
new_device_loginLogin von unbekanntem Geraet (nach 2FA){{display_name}}, {{device}}, {{location}}, {{ip}}, {{timestamp}}

new_device_login wird im bestehenden auth-Feature nach erfolgreicher 2FA-Verifikation ausgeloest — kleine Ergaenzung, kein neues Feature.

Nutzt das 3-Stufen-System (Handler → Preparation → Dispatch). Neue Handler:

  • PasswordResetCodeHandler — prueft password_reset auf neue Eintraege
  • PasswordChangedHandler — prueft account auf kuerzliche Passwort-Aenderungen
  • AccountDeactivatedHandler — prueft account auf kuerzliche Deaktivierungen
  • NewDeviceLoginHandler — prueft known_device auf neue Eintraege