L10b Betrieb & Aufräumen: Deploy – craftvia-worker, CI-Testjob, DEPLOY.md, Certvia-Doku archiviert
- docker-compose.coolify(.prebuilt).yml: Service craftvia-worker (Target worker, Chromium, shm_size 1gb, gleiche Härtung), Craftvia-Variablen für app und worker. - Dockerfile: worker-Stage mit HOME=/home/app (Chromium-Profil für non-root); lokaler docker build der Targets runner und worker erfolgreich, PDF-Erzeugung im Image geprüft. - .env.example/.env.prod.example/.env.coolify.example: alle Craftvia-Variablen inkl. RLS, KI-Provider, PDF_CHROMIUM_PATH, OFFLINE_MAX_DAYS, API_RATE_LIMIT_*, AI_GENERATION_RETENTION_DAYS, AI_MONTHLY_TOKEN_LIMIT. - CI (.github, .gitea): Job gate mit Postgres (pgvector) und Redis als Service: migrate deploy, seed, Passwort für craftvia_app, tsc, lint, build, npm run test. - docs/craftvia/DEPLOY.md (aus den Certvia-Deploy-Docs abgeleitet): Architektur, Domains, Secrets, Worker, Migrationen, RLS-Aktivierung, Backup/Restore, KI, Rate Limits, Aufbewahrung, Smoke, Update/Rollback. build-and-push-images.sh baut craftvia-worker. - Certvia-/ISMS-Dokumente aus docs/ nach docs/_certvia-archiv/ (mit README); Verweise in README.md und Skript-Kommentaren angepasst. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user