# Umsetzungskonzept — Zentrale Identität mit Mandanten-Mitgliedschaften (Option C) > Status: **Entscheidungsvorlage** (ohne Code). Ziel: eine Person meldet sich mit **einem** Login/Passwort und **einer** MFA an und wählt anschließend, in **welchem Mandanten** sie arbeitet — mit je Mandant unterschiedlichen Rollen. Die mandantengetrennte Datenhaltung (RLS, Ownership) bleibt **unangetastet**. ## 1. Grundmodell in einem Satz Eine **globale `Identity`** (E-Mail + ein Passwort + eine MFA) verweist auf **N per-Mandant-`User`-Zeilen** („Mitgliedschaften", jede mit eigenen Rollen). Zentralisiert werden nur **Anmeldung + MFA + Mandantenwechsel**; alles Fachliche bleibt pro Mandant. ## 2. Getroffene Entscheidungen | # | Entscheidung | Festlegung | |---|---|---| | A | MFA-Zeitpunkt | **Beim Login**, als getrennter zweiter Schritt (Two-Step, „identifier-first") | | B | Reset-Hoheit | Mandanten-Admin verliert MFA-/Passwort-Reset; Reset via **Self-Service (Recovery-Codes) + Plattform-Ebene**. Mandanten-Admin entzieht nur die **Mitgliedschaft**. | | C | Nutzeranlage | **Einladung/Bestätigung** als Standard (Kunden-Dashboard zwingend); Plattform-Admin darf zusätzlich direkt verknüpfen | | D | Migration Altbestand | **Entfällt** — bisher nur Testdaten. Neuanlage/Seed statt Zusammenführung. | | E | Plattform-Admins | **Getrennter Store** (`platform_admins`) bleibt — eigene Sicherheitsdomäne | | + | Login-Fluss | MFA-Abfrage **erst nach** Benutzername + Passwort (siehe §5) | | + | Passwort-Policy | **Eine globale Baseline**, passphrasen-freundlich (NIST 800-63B), mind. so streng wie der strengste Mandant | **Phase 2 (bewusst zurückgestellt):** per-Mandant-Schalter „bei jedem Betreten Step-up erzwingen"; E-Mail-Änderung als Identity-Operation. ## 3. Datenmodell-Skizze (konzeptuell) **Neu: `Identity` (global)** — die Anmelde-Identität einer Person. - `email` (global eindeutig, Verknüpfungsschlüssel) · `passwordHash` · MFA (`mfaSecret`, `mfaEnrolledAt`, `recoveryCodes`) · `status` · `sessionsValidAfter` (globaler Kill-Switch) · Lockout-Felder. **`User` (bestehend, bleibt pro Mandant) = „Mitgliedschaft"** — bekommt nur ein neues Feld `identityId → Identity`. - Behält: `tenantId`, `name`, `status`, **Rollen** (`user_roles`), **alle Ownership-FKs** (Asset-/Risk-/Prozess-Eigentümer …). - Gibt ab (wandert auf `Identity`): `passwordHash`, MFA-Felder, Recovery-Codes → künftig **nicht mehr** für Auth genutzt. **Verknüpfung:** `Identity 1 —— N User(=Membership)`. Eine Person mit drei Mandanten = **eine** Identity + **drei** User-Zeilen. **Unverändert:** `Role`/`UserRole`/`Permission` (pro Mandant), `Tenant`, `TenantSettings` (inkl. MFA-Pflicht-Flag), `platform_admins` (separat). ## 4. Was liegt wo? | Aspekt | Identity (global) | Membership = User (pro Mandant) | |---|---|---| | Passwort / Passphrase | ✅ (eins) | — | | MFA / Recovery-Codes | ✅ (eine Einrichtung) | — | | Rollen & Rechte | — | ✅ (je Mandant frei verschieden) | | Ownership (Assets, Risiken …) | — | ✅ | | Sperre der Person (global) | ✅ `status`/`sessionsValidAfter` | — | | Entzug des Zugangs zu **einem** Mandanten | — | ✅ Mitgliedschaft deaktivieren/löschen | ## 5. Login-Fluss (Two-Step, MFA beim Login) 1. **Schritt 1 — E-Mail + Passwort** → Credential der `Identity` prüfen (Lockout + konstante Laufzeit gegen Enumeration wie heute). 2. **Zwischenzustand:** kurzlebiger, einzweckiger **„MFA-pending"-Token** serverseitig — erlaubt **ausschließlich** den MFA-Abschluss, **keine** App-Session. (Verhindert „halb angemeldet"-Bypass.) 3. **Schritt 2 — MFA** (eigene Seite), angezeigt wenn: Identity hat MFA **oder** ≥ 1 Mitgliedschaft in einem MFA-Pflicht-Mandanten. - MFA vorhanden → TOTP/Passkey verifizieren. - MFA nötig, aber nicht eingerichtet → **jetzt** einrichten (Enroll-Gate). - Keine MFA nötig → überspringen. 4. **Volle Session** ausstellen (Identity authentifiziert + MFA erfüllt). 5. **Mandanten-Auswahl:** aktive Mitgliedschaften in aktiven Mandanten. Auswahl → Server setzt `tenantId` + Rollen. **Nur eine** Mitgliedschaft → Auswahl überspringen. - **Sicherheitsnetz:** verlangt der gewählte Mandant MFA und ist sie (noch) nicht erfüllt → MFA vor Betreten nachziehen. ## 6. Mandantenwechsel (ohne erneuten Login) — Sicherheitskontrollen Zulässig und Standard, **aber nur mit diesen drei Pflicht-Kontrollen:** 1. **Server-autoritativer Kontext:** aktiver `tenantId` wird bei **jedem** Wechsel aus einer **frisch validierten Mitgliedschaft** neu abgeleitet — nie aus Client-Input. RLS `app.tenant_id` pro Transaktion daraus. 2. **Re-Validierung bei jedem Wechsel/Request:** Mitgliedschaft + Mandant + Nutzerstatus + `sessionsValidAfter` prüfen → Entzug/Sperre wirkt sofort. 3. **MFA-Netz beim Betreten** eines Pflicht-Mandanten (Backstop zu §5.5). - Zusätzlich: **Wechsel wird auditiert** („Identity X → Mandant Y"), Folgeaktionen werden der aktiven Mitgliedschaft/Mandant zugeordnet. - Restrisiko bewusst: breiterer Blast-Radius eines kompromittierten Credentials → beherrscht über starke Identität + MFA + kurze Session-Laufzeit + globalen Kill-Switch. ## 7. MFA-Matrix (Person × Mandant) | Identity hat MFA? | Zielmandant verlangt MFA? | Ergebnis beim Login/Betreten | |---|---|---| | ja | ja | Faktor verifizieren | | ja | nein | kein Zwang (Faktor liegt vor, wird nicht abgefragt) | | nein | ja | **Einrichtung erzwingen** | | nein | nein | keine MFA | > Grenze: Ein Mandant kann **kein** eigenes, isoliertes MFA-Gerät erzwingen — es gibt **einen** Faktor pro Person. (Preis der zentralen Identität.) ## 8. Nutzeranlage **Über das Plattform-(Admin-)Dashboard:** - E-Mail unbekannt → neue `Identity` (Initialpasswort/Einladung, `mustChangePassword`) + Mitgliedschaft im Zielmandanten mit Rollen. - E-Mail bekannt → **kein** neues Passwort/MFA, **nur** Mitgliedschaft ergänzen. Person sieht den Mandanten künftig in der Auswahl. (Betreiber darf direkt verknüpfen.) **Über das Kunden-(Mandanten-)Dashboard — immer per Einladung:** - Mandanten-Admin sieht **neutral** „Einladung an *E-Mail* gesendet" — unabhängig davon, ob die Identity schon existierte (**kein** Cross-Tenant-Leak / keine Konten-Enumeration). - Identity existiert → Person **bestätigt** den Beitritt in ihrem bestehenden Konto (Einwilligung durch den Menschen, nicht durch den fremden Admin). - Identity existiert nicht → normaler „Konto einrichten + beitreten"-Flow. - Rollen werden bei der Einladung je Mandant vergeben (frei verschieden pro Mandant). ## 9. Passwort-/Passphrasen-Policy - **Eine globale Baseline** (weil ein Credential), mind. so streng wie der strengste Mandant. - Passphrasen voll unterstützt: lange Obergrenze (≥ 64 Zeichen), Leer-/Unicode-Zeichen erlaubt, **keine** erzwungene Zusammensetzung, **kein** Zwangswechsel, **Abgleich gegen Leak-Listen**. Hashing wie bisher Argon2id. ## 10. Auswirkungen auf Bestehendes - **Zwei Auth-Instanzen bleiben** (Mandant vs. Plattform). Die Mandanten-Instanz authentifiziert künftig gegen **`Identity`** statt `User`; die Session trägt zusätzlich die **Mitgliedschaftsliste** und den **aktiven** `tenantId`. - **RLS/Ownership/Audit unverändert** — hängen weiter an der per-Mandant-`User`-Zeile. - **Login-UI** wird zweistufig (Passwort-Seite → MFA-Seite → Mandanten-Auswahl); die heutige „alles in einem Formular"-Maske entfällt. - **MFA-/Passwort-Reset** in der Mandanten-Nutzerverwaltung entfällt (→ Self-Service/Plattform); dort bleibt „Mitgliedschaft deaktivieren/Rollen ändern". - **Bootstrap/Seed** legt künftig `Identity` + Mitgliedschaft(en) an. ## 11. Offene Detailpunkte fürs Implementierungs-Feindesign (kein Blocker) - Genaue Lebensdauer/Signierung des „MFA-pending"-Zustands. - Darstellung & Default der Mandanten-Auswahl (zuletzt genutzt merken?). - Wortlaut der neutralen Einladungs-Rückmeldung + Ablauf/Frist der Einladung. - Recovery-Code-Fluss als alleiniger Selbst-Reset-Weg (Anzahl, Nachgenerierung).