Files
craftvia/docs/sicherheit/SEC2-Auth-SelfService-Detail.md
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

7.9 KiB
Raw Permalink Blame History

SEC2 — Passwort-Reset, Passwort ändern, E-Mail-Änderung & Sessions (Detail-Prompt)

Claude-Code-Prompt für einen Entwickler. Basis dev → Branch dev/sec2-auth-selfservice (PR-Ziel dev). Abhängigkeit: SEC1 (Mail-Templates password_reset, password_changed, email_change_verify). Ziel: sicherer Self-Service — Passwort vergessen/zurücksetzen, Passwort selbst ändern, E-Mail-Adresse ändern (verifiziert) — plus ein wiederverwendbarer Session-Invalidierungs-Baustein. Gilt für Mandanten-Nutzer (/login) und Plattform-Admins (/platform/login).

Vorhandenes wiederverwenden

  • src/lib/password-policy.ts (Validierung, client-safe) + src/server/password.ts (Argon2id + Generator). Nicht neu implementieren.
  • Zwei NextAuth-Instanzen: src/server/auth.ts (Mandant) · src/server/platform-auth.ts (Plattform). Force-Change-Gate im (app)-Layout als Muster.
  • Konto-Lockout (Tenant) + User.mustChangePassword vorhanden. Audit-Log (scope=platform für Plattform-Ereignisse). SEC1-Mailservice (enqueueMail).

1. Token-Modell (single-use, gehasht)

AuthToken (eigenes Modell, nicht im JWT): id, principalType (tenant_user | platform_admin), principalId, tenantId? (nur bei tenant_user, für RLS), type (password_reset | email_change), tokenHash (SHA-256 des Rohtokens), newEmail? (nur bei email_change), expiresAt, usedAt?, createdAt, requestIp?.

  • Roh-Token = 32 Byte CSPRNG, base64url; nur im Link (nie in DB/Log). In DB nur der Hash. Lookup per Hash, konstante-Zeit-Vergleich.
  • Gültigkeit kurz (Reset 30–60 Min, E-Mail-Verify 60 Min); single-use (usedAt setzen); alte offene Tokens desselben Typs beim Neuanfordern invalidieren.
  • tenant_user-Zeilen unter RLS (tenantId); platform_admin-Zeilen tenantId=null (scope=platform). Migration + RLS-DO-Block.

2. Passwort-Reset (Self-Service)

2a Anfrage — „Passwort vergessen?" auf /login und /platform/login:

  • Eingabe E-Mail → immer gleiche Antwort („Falls ein Konto existiert, wurde eine E-Mail gesendet."). Kein Rückschluss auf Existenz (Enumeration-Schutz).
  • Rate-Limit: je IP und je Konto (z. B. 5/Stunde); zusätzlich globaler Missbrauchsschutz.
  • Existiert das Konto und ist aktiv/nicht gesperrt: AuthToken(password_reset) erzeugen, Link "{APP_BASE_URL}/reset?token=…" per SEC1 (password_reset) senden. Deaktivierte/gesperrte Konten: keine Mail, keine Fehlermeldung.

2b Einlösung — /reset?token=…:

  • Token per Hash validieren (existiert, nicht abgelaufen, nicht benutzt, Konto aktiv). Ungültig → generische Meldung + Angebot „neu anfordern".
  • Neues Passwort setzen: Policy-Prüfung (password-policy.ts) + Argon2id (password.ts). Bei Wiederverwendung: Breach-Check-Hook vorsehen (Implementierung in SEC5).
  • Danach: usedAt setzen, alle Sessions invalidieren (Abschnitt 5), mustChangePassword=false, Bestätigungs-Mail (password_changed), Audit-Eintrag (ohne Token).
  • Redirect zum passenden Login (/login bzw. /platform/login).

3. Passwort selbst ändern

Profilseite (Mandanten-App und Plattform-Profil):

  • Felder: aktuelles Passwort, neues, Wiederholung. Alt-Passwort verifizieren (Argon2id), Policy-Prüfung.
  • Erfolg: Passwort setzen, andere Sessions abmelden (aktuelle behalten — Abschnitt 5), Bestätigungs-Mail (password_changed), Audit.
  • Fehler generisch; Rate-Limit auf Alt-Passwort-Versuche.

4. E-Mail-Adresse ändern (verifiziert, Double-Opt-in)

  • Nutzer gibt neue Adresse ein → Alt-Passwort bestätigen (Step-up folgt in SEC3). Prüfen, dass die neue Adresse frei ist (mandantenweit bzw. plattformweit), ohne Enumeration nach außen.
  • AuthToken(email_change, newEmail) erzeugen; Verifizierungslink an die NEUE Adresse (email_change_verify).
  • Klick auf Link: Token validieren → E-Mail aktualisieren, usedAt setzen; Benachrichtigung an die ALTE Adresse („Ihre E-Mail wurde geändert"); Audit. Login-Identität konsistent halten (E-Mail ist Login).
  • Kollisionsfall (Adresse zwischenzeitlich vergeben): sauber ablehnen.

5. Session-Invalidierung (wiederverwendbarer Baustein)

NextAuth v5 nutzt JWT (stateless) → globale Invalidierung über eine Versionsmarke:

  • Feld sessionsValidAfter (Timestamp) bzw. tokenVersion an User und PlatformAdmin.
  • JWT trägt iat/Version; im Auth-Callback bzw. Layout-Check (die DB wird ohnehin schon für Kontostatus/mustChangePassword geprüft) zusätzlich sessionsValidAfter vergleichen → ältere Tokens sind ungültig → Abmeldung.
  • Bump-Auslöser: Passwort-Reset (2b), Passwort ändern (3, andere Sessions), Konto-Deaktivierung, MFA-Änderung (SEC3), E-Mail-Änderung.
  • „Aktuelle Session behalten" (bei Self-Change): nach Bump das aktuelle JWT mit neuer Version neu ausstellen.
  • AK: ein Bump macht bestehende Sessions serverseitig ungültig; Self-Change meldet nur die anderen ab.
  • Optionaler Folgeschritt (nicht SEC2-Pflicht): „aktive Sitzungen anzeigen + einzeln abmelden" braucht Session-Records — als P1 vermerken.

6. Sicherheit (verbindlich)

  • Tokens: CSPRNG, nur gehasht gespeichert, single-use, kurzlebig, konstante-Zeit-Vergleich; nie in Logs/MailLog.
  • Enumeration-Schutz überall (Reset, E-Mail-Änderung): generische, einheitliche Antworten und Timing.
  • Rate-Limiting an Reset-Anfrage, Reset-Einlösung, Alt-Passwort-Prüfung (IP + Konto) — bindet an den globalen Limiter aus SEC5, hier aber schon inline scharf.
  • Reset/Änderung an gesperrten/deaktivierten Konten unmöglich.
  • Alle Ereignisse ins Audit (wer/wann/was, ohne Secrets); kompatibel zur SEC5-Audit-Erweiterung.
  • Kein Auto-Login direkt nach Reset (Nutzer meldet sich neu an) — reduziert Token-Missbrauch.

7. Akzeptanzkriterien

  • „Passwort vergessen" auf /login und /platform/login erzeugt (nur bei aktivem Konto) eine Reset-Mail; Antwort ist immer enumeration-neutral; Rate-Limit greift.
  • Reset-Link ist single-use, abgelaufen/benutzt → generische Ablehnung; nach Reset sind alle Sessions ungültig, Bestätigungs-Mail + Audit vorhanden.
  • Passwort-Selbständerung mit Alt-Passwort-Prüfung + Policy; andere Sessions werden abgemeldet, aktuelle bleibt.
  • E-Mail-Änderung erst nach Verifizierung der neuen Adresse wirksam; alte Adresse wird informiert.
  • Token-Hashes in DB (keine Klartext-Tokens), keine Secrets in Logs.
  • Funktioniert für Mandanten-Nutzer und Plattform-Admins.

8. Definition of Done

tsc → lint → build (Guard-Check) grün · neue Actions in check-module-guards.ts · AuthToken + sessionsValidAfter-Felder in Migration (+RLS-DO-Block, AuthToken in TENANT_MODELS für tenant-Zeilen) · Browser-Test aller Flows (Tenant + Plattform) gegen Mailpit · Demo-Umgebung lauffähig.

9. Testplan (Kurz)

  1. Reset happy path (Tenant + Plattform): Anfrage → Mail (Mailpit) → Link → neues Passwort → alte Sessions abgemeldet → Login neu.
  2. Enumeration: unbekannte E-Mail → identische Antwort/Timing, keine Mail.
  3. Token-Missbrauch: abgelaufen / bereits benutzt / manipuliert → generische Ablehnung.
  4. Rate-Limit: viele Anfragen → gedrosselt.
  5. Self-Change: falsches Alt-Passwort → Ablehnung; korrekt → andere Session (zweiter Browser) wird ungültig, aktuelle bleibt.
  6. E-Mail-Änderung: Verify-Link an neue Adresse nötig; alte Adresse erhält Hinweis; unbestätigt → keine Änderung.
  7. Deaktiviertes Konto: kein Reset möglich.

10. Bibliotheken / Bausteine

crypto (CSPRNG/SHA-256, konstante-Zeit) · vorhandene password-policy.ts/password.ts · SEC1-enqueueMail · Rate-Limit-Util (mit SEC5 teilen). Folgepaket SEC3 nutzt den Session-Invalidierungs-Baustein (MFA-Änderung) und ergänzt Step-up-Re-Auth für die E-Mail-Änderung/kritische Aktionen.