Files
craftvia/docs/sicherheit/SEC2-Auth-SelfService-Detail.md
T
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

101 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.