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>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
# Umsetzungspaket — Sicherheit & Administration (1 Entwickler)
|
||||
|
||||
> Claude-Code-Prompt. Basis-Branch **`dev`**; je Story ein Feature-Branch `dev/sec<n>-…` (PR-Ziel `dev`). Grundlage: `Sicherheit-und-Administration-Konzept.md` + Ist-Stand `STAND-dev-branch.md`.
|
||||
> **Entscheidungen (fix):** Mail via **SMTP** · **1 Entwickler** (sequenziell) · **2FA bleibt optional**, aber **pro Tenant im Adminportal als Pflicht** einstellbar · **Passkeys** dabei · **DSGVO-Funktionen** dabei.
|
||||
|
||||
## Vorhandenes wiederverwenden (nicht neu bauen)
|
||||
- **MFA-Kern** (TOTP + Recovery-Codes) für Plattform- und Mandanten-Nutzer: `src/server/mfa.ts`; Policy-Flag `securityPolicy.mfaRequired` existiert.
|
||||
- **Passwort**: `src/lib/password-policy.ts` (Validierung) + `src/server/password.ts` (Argon2id + Generator); **Force-Change**-Gate im `(app)`-Layout.
|
||||
- **Auth**: zwei NextAuth-Instanzen — Mandant `src/server/auth.ts` (`/login`), Plattform `src/server/platform-auth.ts` (`/platform/login`). **`PlatformAdmin`**-Store getrennt.
|
||||
- **Queue**: BullMQ + Redis im Stack. **Audit-Log** mit `tenantId` nullable (`scope=platform`). **Task/TaskComment** für Ticket-/Freigabe-Ereignisse.
|
||||
- **Modul-Guard**: neue Server-Actions in `scripts/check-module-guards.ts` eintragen; tenant-Modelle in `TENANT_MODELS`+RLS.
|
||||
|
||||
## Betriebs-/DNS-Voraussetzungen (kein Code, aber Vorbedingung)
|
||||
Dedizierte Absenderdomain (`no-reply@certvia.de`) mit **SPF, DKIM, DMARC**; TLS/HSTS; SMTP-Zugangsdaten + alle Secrets aus **Env/Secret-Store** (nicht im Repo).
|
||||
|
||||
---
|
||||
|
||||
## Reihenfolge (sequenziell, 1 Entwickler)
|
||||
SEC1 → SEC2 → SEC3 → SEC4 → SEC5 → SEC6. Einzelne P0-Härtungen (Rate-Limit an Login/Reset/MFA, Token-Hashing) landen **inline** in SEC2/SEC3; die globalen Härtungen bündelt SEC5.
|
||||
|
||||
---
|
||||
|
||||
## SEC1 — SMTP-Mail-Fundament (`dev/sec1-mail-smtp`) [L]
|
||||
**Ziel:** zuverlässiger, gebrandeter, asynchroner Mail-Versand als Fundament für Reset/Einladung/Benachrichtigung.
|
||||
- **Mail-Service** `src/server/mail/**` mit **SMTP-Transport** (nodemailer, TLS) hinter einem Interface `sendMail(template, to, vars, locale)` — Provider später austauschbar.
|
||||
- **Queue/Worker** über BullMQ/Redis: asynchron, **Retry mit Backoff**, Dead-Letter, Rate-Limit; Fehler/Bounce ins Log.
|
||||
- **Templates** (Certvia-gebrandet, **de/en**, HTML + Text-Alternative): Einladung, Passwort-Reset, „Passwort geändert", E-Mail-Änderung bestätigen, „MFA geändert", **Ticket-/Freigabe-/Fristen-Benachrichtigung**.
|
||||
- **Benachrichtigungsregeln**: je Nutzer/Ereignis opt-in/opt-out + Sprache; strikt mandantengetrennt. Trigger aus Task-Ereignissen (Zuweisung, Freigabe-Anfrage, Entscheidung, Fälligkeit).
|
||||
- **Admin-Testversand** (eine Aktion „Test-Mail senden") zur Zustellprüfung.
|
||||
- **AK:** Mail wird asynchron mit Retry versendet; Templates gebrandet + zweisprachig; Bounces/Fehler geloggt; Ticket-Benachrichtigung wird bei Task-Zuweisung/Freigabe ausgelöst; keine Secrets im Repo.
|
||||
|
||||
## SEC2 — Passwort-Self-Service & Sessions (`dev/sec2-auth-selfservice`) [M–L] · Abh. SEC1
|
||||
- **Passwort-Reset**: „Passwort vergessen" → **Single-Use-Token**, kurzlebig (30–60 Min), **serverseitig gehasht** gespeichert, an E-Mail gebunden. **Enumeration-Schutz** (immer gleiche Antwort), **Rate-Limit** je IP/Konto. Nach Reset: **alle Sessions invalidieren** + Bestätigungs-Mail + Audit; deaktivierte/gesperrte Konten erhalten keinen Reset.
|
||||
- **Passwort selbst ändern**: Profilseite mit **Alt-Passwort-Bestätigung**, Policy-Prüfung, danach **andere Sessions abmelden** (aktuelle behalten), Bestätigungs-Mail + Audit.
|
||||
- **E-Mail-Änderung**: **Double-Opt-in** an neue Adresse + Benachrichtigung an alte; Audit.
|
||||
- **Session-Invalidierung** als wiederverwendbarer Baustein (bei Reset/Änderung/Deaktivierung/MFA-Änderung).
|
||||
- **AK:** Reset-Token single-use/gehasht/rate-limited/enumeration-safe; Self-Change funktioniert; E-Mail-Änderung verifiziert; Sessions werden korrekt invalidiert; alle Ereignisse im Audit.
|
||||
|
||||
## SEC3 — MFA pro Tenant erzwingen + Passkeys (`dev/sec3-mfa-passkeys`) [L] · Abh. SEC1
|
||||
- **Optional bleibt Default.** Im **Adminportal** kann pro Mandant `securityPolicy.mfaRequired` gesetzt werden (Superadmin) → **Enrollment-Gate**: Nutzer dieses Mandanten werden beim nächsten Login zur MFA-Einrichtung gezwungen (analog Force-Change-Gate im `(app)`-Layout). Ebenso erzwingbar für **alle Plattform-Admins**.
|
||||
- **Passkeys/WebAuthn** als zusätzlicher 2. Faktor (`@simplewebauthn/server` + Browser-API): Registrierung + Login; Nutzer kann **TOTP oder Passkey** verwenden; Credentials verwaltbar (anlegen/entfernen).
|
||||
- **Step-up-Re-Auth** für sensible Aktionen: MFA deaktivieren, Plattform-Admin anlegen, Datenexport, Mandant löschen.
|
||||
- **TOTP-Secret at rest verschlüsselt** (falls noch Klartext) + **Rate-Limit** auf Code-Eingabe; Recovery-Codes-Flow (Anzeige/Regenerierung, Verbrauch protokolliert) abrunden.
|
||||
- **AK:** Admin setzt `mfaRequired` je Tenant → betroffene Nutzer müssen beim Login enrollen; Passkey-Registrierung + -Login funktionieren; Step-up greift bei sensiblen Aktionen; TOTP-Secret verschlüsselt; MFA global weiterhin optional, wenn Policy es nicht verlangt.
|
||||
|
||||
## SEC4 — Plattform-Admin-Verwaltung (`dev/sec4-platform-admins`) [M] · Abh. SEC1, SEC3
|
||||
- **Verwaltungs-UI** im Adminportal (`src/app/(platform)/admin/**`, Actions `platform*.ts`): weitere **`PlatformAdmin`** anlegen (Einladung via SEC1 oder Initial-Passwort), **Rollen** (z. B. „Voll-Admin" vs. „Support/Read-only"), **sperren/reaktivieren**, Passwort-Reset, **MFA-Pflicht** für Plattform-Admins.
|
||||
- **Selbst-Aussperr-Schutz** auf Plattform-Ebene (letzter Voll-Admin nicht entfernbar/deaktivierbar); kritische Aktionen mit **Step-up** (SEC3).
|
||||
- **Vollständiges Plattform-Audit** (`scope=platform`) jeder Aktion.
|
||||
- **AK:** ein zweiter Voll-Admin ist anlegbar und kann verwalten; Rollen greifen (Read-only kann nichts ändern); Last-Admin-Schutz aktiv; jede Aktion protokolliert.
|
||||
|
||||
## SEC5 — Härtung P0 (quer) (`dev/sec5-hardening`) [M]
|
||||
- **HTTP-Security-Header** (zentrale Middleware): CSP, HSTS, `X-Frame-Options`/`frame-ancestors`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`.
|
||||
- **Rate-Limiting/Brute-Force** global an Login/Reset/MFA/Admin-Aktionen (IP **und** kontobezogen), ergänzt den bestehenden Konto-Lockout.
|
||||
- **Security-Audit erweitern**: Logins (Erfolg/Fehlschlag), Rechte-/Rollenänderungen, MFA-Änderungen, Exporte, Admin-Aktionen; **append-only**/manipulationssicher; Aufbewahrung definiert.
|
||||
- **Passwort-Breach-Check** (HaveIBeenPwned k-Anonymity) beim Setzen/Ändern zusätzlich zur Policy.
|
||||
- **Token-/Secret-Hygiene**: alle Tokens single-use + gehasht (Konsistenz zu SEC2/SEC3); **Secret-Scanning** in CI.
|
||||
- **AK:** Header messbar gesetzt (z. B. securityheaders-Check); Rate-Limits greifen; Audit-Ereignisse vorhanden; bekannte kompromittierte Passwörter werden abgelehnt.
|
||||
|
||||
## SEC6 — DSGVO-Funktionen (`dev/sec6-dsgvo`) [L] · Abh. SEC1, SEC3 (Step-up)
|
||||
- **Mandanten-Datenexport** (vollständig, maschinenlesbar, z. B. JSON/ZIP), Admin-getriggert, **asynchron** via Queue, Download-Link mit Ablauf.
|
||||
- **Löschung/Retention**: Mandanten-Löschung (Soft→Hard mit Karenz), **Aufbewahrungsfristen**/Löschkonzept, RLS-bewusste Kaskaden; Audit; Step-up-Bestätigung.
|
||||
- **Betroffenenrechte**: Export/Löschung nutzerbezogener Daten, soweit anwendbar.
|
||||
- **Doku-Bausteine** (Inhalt, kein Code): AVV/DPA + TOMs — als offener fachlicher Punkt markieren.
|
||||
- **AK:** vollständiger Tenant-Export erzeugbar; Löschung respektiert Retention + Audit + Step-up; DSGVO-Aktionen protokolliert.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done (jede Story)
|
||||
`npx tsc --noEmit` → `npm run lint` → `npm run build` (Guard-Check) grün · neue Actions in `check-module-guards.ts` · neue tenant-Modelle in `TENANT_MODELS`+RLS+Migration (RLS-DO-Block) · Security-Verifikation (Header/Rate-Limit/Token) · Browser-Test der Flows · Demo-Umgebung lauffähig · **Secrets nur aus Env**.
|
||||
|
||||
## Hot Files (koordiniert, da 1 Entwickler → nur Reihenfolge beachten)
|
||||
`prisma/schema.prisma` + Migrationsreihenfolge · zentrale **Middleware** (Header/Rate-Limit) · `(app)`- und `(platform)`-Layouts (Enrollment-/Force-Gates) · `src/server/auth.ts` / `platform-auth.ts` / `mfa.ts` · `scripts/check-module-guards.ts`.
|
||||
|
||||
## Offene fachliche Punkte
|
||||
- DNS: SPF/DKIM/DMARC vor Produktivversand aktiv.
|
||||
- AVV/DPA-Texte + TOMs (Datenschutz/ISB).
|
||||
- Aufbewahrungsfristen je Datenart festlegen (Grundlage für SEC6-Retention).
|
||||
@@ -0,0 +1,19 @@
|
||||
# Sicherheit & Administration — Übergabepaket
|
||||
|
||||
## Inhalt / Reihenfolge
|
||||
1. **Sicherheit-und-Administration-Konzept.md** — PO-Konzept: Ist-Abgleich, Empfehlungen, Roadmap.
|
||||
2. **Aufgabenpaket-Sicherheit-Administration.md** — Ein-Entwickler-Backlog SEC1–SEC6 (Branches `dev/sec<n>-…`).
|
||||
3. **SEC1-Mail-Fundament-Detail.md** — ausgearbeiteter Prompt: SMTP-Mail (Fundament).
|
||||
4. **SEC2-Auth-SelfService-Detail.md** — ausgearbeiteter Prompt: Passwort-Reset, Passwort ändern, E-Mail-Änderung, Session-Invalidierung.
|
||||
|
||||
## Fixierte Entscheidungen
|
||||
- Mail via **SMTP** (nodemailer) im ersten Schritt.
|
||||
- **1 Entwickler**, sequenziell: SEC1 → SEC2 → SEC3 → SEC4 → SEC5 → SEC6.
|
||||
- **2FA optional**, aber pro Tenant im **Adminportal** als Pflicht (`mfaRequired`) erzwingbar; **Passkeys** dabei.
|
||||
- **DSGVO-Funktionen** enthalten.
|
||||
|
||||
## Naht SEC1 ↔ SEC2
|
||||
SEC1 liefert Versand + Templates; **SEC2 erzeugt die Tokens** (single-use, gehasht) und übergibt SEC1 nur die fertige `actionUrl` — keine Klartext-Secrets im MailLog.
|
||||
|
||||
## Start
|
||||
Mit **SEC1** beginnen, dann **SEC2**. DNS-Vorbedingung: SPF/DKIM/DMARC vor Produktivversand. Secrets nur aus Env/Secret-Store.
|
||||
@@ -0,0 +1,125 @@
|
||||
# SEC1 — SMTP-Mail-Fundament (Detail-Prompt)
|
||||
|
||||
> Claude-Code-Prompt für **einen** Entwickler. Basis `dev` → Branch **`dev/sec1-mail-smtp`** (PR-Ziel `dev`).
|
||||
> Ziel: zuverlässiger, gebrandeter, **asynchroner** Mail-Versand über **SMTP** — Fundament für Passwort-Reset (SEC2), Einladung, MFA-Hinweise (SEC3) und Ticket-/Freigabe-/Fristen-Benachrichtigungen. Stack vorhanden: **BullMQ + Redis**, Next.js/TS, Prisma, i18n (de/en), Task/TaskComment, Audit-Log, `TenantSettings`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architektur (Zielbild)
|
||||
|
||||
```
|
||||
Aufrufer (Action/Event) Worker-Prozess
|
||||
│ enqueueMail({template,to, ┌───────────────────────────┐
|
||||
│ locale,vars,tenantId}) │ BullMQ "mail"-Queue │
|
||||
▼ │ → render(template,locale) │
|
||||
MailService ──push job──► Redis ───────► │ → MailProvider.send() │
|
||||
│ │ → MailLog(status) │
|
||||
└─ MailLog(pending) │ → Retry/Backoff/DLQ │
|
||||
└───────────────────────────┘
|
||||
Provider-Interface ← SMTP-Impl (nodemailer) [später austauschbar]
|
||||
```
|
||||
|
||||
Trennung in vier Schichten: **Provider** (SMTP), **Templates** (Render), **Queue/Worker** (Zustellung + Retry), **Service/Trigger** (Aufruf + Regeln + Log).
|
||||
|
||||
---
|
||||
|
||||
## 2. Datenmodelle (Prisma, mit Migration + RLS)
|
||||
|
||||
- **`MailLog`** (Auditierbarkeit + Idempotenz): `id`, `tenantId` (nullable → Plattform-Mails), `to`, `template`, `locale`, `status` (`pending|sent|failed|bounced|suppressed`), `providerMessageId?`, `error?`, `dedupeKey?` (unique, für Idempotenz), `createdAt`, `sentAt?`. → in `TENANT_MODELS`+RLS (Plattform-Zeilen mit `tenantId=null`, `scope=platform`).
|
||||
- **`NotificationPreference`**: `userId`, `eventType`, `email` (bool, default true), `locale?`. Default = opt-in; UI kommt später — jetzt Modell + Default-Auflösung.
|
||||
- Migrationen im Prisma-7-Flow (RLS-DO-Block manuell anhängen).
|
||||
|
||||
---
|
||||
|
||||
## 3. Konfiguration (Env / Secret-Store — nichts ins Repo)
|
||||
|
||||
`SMTP_HOST`, `SMTP_PORT`, `SMTP_SECURE` (true=TLS/465, false=STARTTLS/587), `SMTP_USER`, `SMTP_PASS`, `MAIL_FROM` (`no-reply@certvia.de`), `MAIL_FROM_NAME` (`Certvia`), `APP_BASE_URL`, `MAIL_REPLY_TO?`.
|
||||
- **Boot-Validierung** (z. B. zod): fehlt Konfig → klarer Fehler, Versand deaktiviert (Jobs bleiben `pending`, Warnung im Log). Kein stiller Fehlversand.
|
||||
|
||||
---
|
||||
|
||||
## 4. Provider & Service
|
||||
|
||||
- **Interface** `src/server/mail/provider.ts`: `send(msg: {from,to,replyTo?,subject,html,text,headers?}): Promise<{messageId}>`. So bleibt der Provider austauschbar (heute SMTP, später API).
|
||||
- **SMTP-Impl** `src/server/mail/provider-smtp.ts`: **nodemailer** (Pool, TLS, Timeouts). Verbindung wiederverwenden.
|
||||
- **Service** `src/server/mail/service.ts`:
|
||||
- `enqueueMail(input)` → `dedupeKey` bilden (z. B. `template:to:refId`), `MailLog(pending)` anlegen (unique verhindert Doppelversand), Job in Queue.
|
||||
- `renderMail(template, locale, vars)` → `{subject, html, text}` (Abschnitt 5).
|
||||
- Action-Datei in `scripts/check-module-guards.ts` als **`EXEMPT`** eintragen (Infrastruktur, kein gegatetes Modul).
|
||||
|
||||
---
|
||||
|
||||
## 5. Templates (gebrandet, de/en, HTML + Text)
|
||||
|
||||
- **Basis-Layout** `src/server/mail/templates/_layout.*`: Certvia-Kopf (Logo/Wortmarke), Markenfarben (Violett `#5d52a3` / Magenta-Akzent `#812d80`), **Inline-CSS** (E-Mail-Client-tauglich; MJML oder handgepflegtes Table-Layout), Fußzeile „**Certvia — ein Produkt von GEFIM**" + Impressum/Abmelde-Hinweis. Immer **Text-Alternative** mitliefern.
|
||||
- **i18n:** je Template `de`/`en` über die bestehenden Message-Kataloge; `locale` aus Nutzer/`NotificationPreference`, Fallback `de`.
|
||||
- **Template-Set (Erststufe):**
|
||||
|
||||
| Template-Key | Anlass | Kern-Variablen | Auslöser |
|
||||
|---|---|---|---|
|
||||
| `invitation` | Nutzer-Onboarding | `name, tenantName, actionUrl, expires` | SEC1/SEC4 (Einladung) |
|
||||
| `password_reset` | Reset angefordert | `name, actionUrl, expires` | **SEC2** (Token wird dort erzeugt) |
|
||||
| `password_changed` | Passwort geändert | `name, when, ip?` | SEC2 |
|
||||
| `email_change_verify` | E-Mail-Änderung bestätigen | `name, actionUrl, expires` | SEC2 |
|
||||
| `mfa_changed` | MFA aktiviert/deaktiviert/Recovery neu | `name, change, when` | SEC3 |
|
||||
| `notification` | Ticket/Freigabe/Fälligkeit | `name, subject, body, actionUrl, taskType` | Task-Events (Abschnitt 6) |
|
||||
|
||||
> **Wichtig:** Reset-/Verify-/Einladungs-**Tokens** werden von SEC2/SEC3/SEC4 erzeugt (single-use, gehasht). SEC1 liefert nur Template + Versand und bekommt die fertige `actionUrl` als Variable — **keine** Klartext-Secrets ins MailLog/Log schreiben.
|
||||
|
||||
---
|
||||
|
||||
## 6. Benachrichtigungs-Trigger (Ticket-/Freigabe-/Fristen)
|
||||
|
||||
- **Task-Ereignisse** (bestehendes `Task`/`TaskComment` + `submitForApproval`): bei **Zuweisung**, **Freigabe-Anfrage**, **Freigabe-Entscheidung** (angenommen/abgelehnt) → `notification`-Mail an den jeweils Zuständigen, sofern `NotificationPreference.email` aktiv.
|
||||
- **Fristen-Erinnerung:** **BullMQ Repeatable Job** (z. B. täglich) prüft fällige/überfällige Aufgaben (`dueDate`) und versendet gebündelte Erinnerungen (keine Spam-Schleifen — je Aufgabe max. definierte Frequenz, `dedupeKey`).
|
||||
- Alle Trigger **mandantenisoliert**; Sprache je Empfänger.
|
||||
|
||||
---
|
||||
|
||||
## 7. Queue / Worker / Robustheit
|
||||
|
||||
- **Queue** `mail` (BullMQ). **Worker** `src/server/mail/worker.ts`: `render → provider.send → MailLog(sent|failed)`.
|
||||
- **Retry:** z. B. 5 Versuche, exponentielles Backoff; nach Ausschöpfung `status=failed` + **Dead-Letter** (separate Queue/Flag) + Alarm-Logeintrag.
|
||||
- **Rate-Limit** je Empfänger/Domain (BullMQ Limiter), **Concurrency** begrenzt, **Graceful Shutdown**.
|
||||
- **Idempotenz:** `dedupeKey`-Unique + „bereits gesendet"-Kurzschluss.
|
||||
- **Betriebsmodus dokumentieren:** Worker als eigener Prozess (Coolify) **oder** im App-Container gestartet — entscheiden und im README festhalten.
|
||||
|
||||
---
|
||||
|
||||
## 8. Sicherheit / Datenschutz
|
||||
|
||||
- **TLS erzwingen** (STARTTLS/implicit), Zertifikatsprüfung an. Secrets nur aus Env/Secret-Store.
|
||||
- **Keine sensiblen Inhalte** in Mails über das Nötige hinaus; **keine Tokens** in Logs/MailLog (nur `dedupeKey`/`providerMessageId`).
|
||||
- **Abmelde-/Präferenz-Hinweis** in Benachrichtigungs-Mails (nicht in sicherheitskritischen Transaktionsmails wie Reset).
|
||||
- **Enumeration-Schutz** ist Sache von SEC2 (Reset) — SEC1 versendet nur, was ihm übergeben wird.
|
||||
- **Audit:** Versandereignisse (ohne Inhalt) ins Security-Audit (Erweiterung in SEC5 kompatibel halten).
|
||||
- **DNS-Vorbedingung:** SPF, DKIM, DMARC für die Absenderdomain (Ops; vor Produktivversand).
|
||||
|
||||
---
|
||||
|
||||
## 9. Admin-Testversand
|
||||
- Aktion „Test-Mail senden" im Adminportal (an eigene Adresse), zeigt Ergebnis/MailLog-Status → schnelle Zustell-/DKIM-Prüfung.
|
||||
|
||||
---
|
||||
|
||||
## 10. Akzeptanzkriterien
|
||||
- `enqueueMail(...)` legt `MailLog(pending)` an und stellt asynchron über SMTP zu; bei Erfolg `sent` + `providerMessageId`, bei Fehler Retry→`failed`/DLQ.
|
||||
- Templates rendern **de und en**, HTML **und** Text, mit Certvia-Branding und „ein Produkt von GEFIM".
|
||||
- **Ticket-Benachrichtigung** wird bei Task-Zuweisung/Freigabe ausgelöst und respektiert `NotificationPreference` + Mandantenisolation.
|
||||
- **Fristen-Erinnerung** läuft als wiederkehrender Job ohne Doppelversand.
|
||||
- Fehlende SMTP-Konfig → klarer Fehler, kein stiller Fehlversand; keine Secrets/Tokens im Repo oder Log.
|
||||
- Lokaler Test gegen **Mailpit/Mailhog** dokumentiert; Admin-Testversand funktioniert.
|
||||
|
||||
## 11. Definition of Done
|
||||
`npx tsc --noEmit` → `npm run lint` → `npm run build` (Guard-Check) grün · Mail-Action als `EXEMPT` registriert · `MailLog`/`NotificationPreference` in `TENANT_MODELS`+RLS+Migration (RLS-DO-Block) · Worker-Betriebsmodus im README · lokaler Mailpit-Testnachweis (Screenshots) · Demo-Umgebung lauffähig.
|
||||
|
||||
## 12. Testplan (Kurz)
|
||||
1. **Lokal Mailpit** (`docker run mailpit`) als SMTP-Ziel; Env setzen; Test-Mail senden → in Mailpit sichtbar (HTML+Text, de/en).
|
||||
2. **Retry:** Provider künstlich fehlschlagen lassen → Job retryt, landet nach N Versuchen in DLQ, `MailLog=failed`.
|
||||
3. **Idempotenz:** zweimal gleicher `dedupeKey` → nur eine Mail.
|
||||
4. **Trigger:** Aufgabe zuweisen / Freigabe anfragen → `notification`-Mail; `NotificationPreference.email=false` → keine Mail.
|
||||
5. **Fristen-Job:** überfällige Aufgabe → genau eine Erinnerung je Zyklus.
|
||||
|
||||
## 13. Bibliotheken
|
||||
`nodemailer` (SMTP) · `bullmq` (vorhanden) · optional `mjml` für Template-Rendering · `zod` (Env-Validierung, i. d. R. vorhanden).
|
||||
Nächste Pakete bauen darauf auf: **SEC2** (Reset/Change nutzen `password_reset`/`password_changed`/`email_change_verify`), **SEC3** (`mfa_changed`), **SEC4** (`invitation`).
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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.
|
||||
@@ -0,0 +1,105 @@
|
||||
# Sicherheit & Administration — Konzept & Empfehlung (PO)
|
||||
|
||||
Grundlage: `STAND-dev-branch.md` (Ist-Stand `dev`). Ziel: die genannten Punkte umsetzen **und** — weil Certvia selbst ein ISMS-Produkt ist — ein Sicherheitsniveau erreichen, das man dem Kunden vorlebt („eat your own dog food"). Status-Legende: ✅ vorhanden · 🟡 teilweise · 🟥 neu.
|
||||
|
||||
---
|
||||
|
||||
## 1. Eure Punkte im Ist-Abgleich (wichtig: nicht doppelt bauen)
|
||||
|
||||
| Anforderung | Status heute | Was fehlt / zu tun |
|
||||
|---|---|---|
|
||||
| **E-Mail-Versand** (Onboarding, Reset, Ticket-/Fristen-Benachrichtigungen) | 🟥 offen | „Paket 4" bewusst zurückgestellt; Aktivierung ist bereits **gekapselt**. Kompletter SMTP-/Mail-Layer + Templates neu. |
|
||||
| **Passwort-Reset** | 🟥 offen | Hängt am Mail-Versand; aktuell nur Initial-/Einmal-Passwort. Self-Service-Reset via signiertem, kurzlebigem Token neu. |
|
||||
| **Passwort ändern (selbst)** | 🟡 teilweise | **Force-Change** (`/change-password`) + Passwort-Policy (Argon2id) vorhanden. **Freiwillige** Änderung im Profil ergänzen (mit Alt-Passwort-Bestätigung). |
|
||||
| **2FA / MFA** | 🟡 teilweise | **TOTP optional** (Plattform-Admins *und* Mandanten-Nutzer) + Recovery-Codes vorhanden; Policy-Flag `mfaRequired` da. **Fehlt:** Enrollment-**Erzwingung** beim Login (Gate), optional WebAuthn/Passkeys. |
|
||||
| **Weiterer übergreifender Admin im Adminportal** | 🟡 teilweise | Getrennter **`PlatformAdmin`-Store** + eigener Login (`/platform/login`) + MFA existiert. **Fehlt:** CRUD-UI im Adminportal, um **weitere Plattform-Admins** anzulegen/zu verwalten (Rollen, Sperren, Reset, MFA-Pflicht). |
|
||||
|
||||
**Kernbotschaft:** 2FA und der getrennte Superadmin-Store sind **im Kern schon da** — hier geht es um *Erzwingung* bzw. *Verwaltungs-UI*, nicht um Neubau. Der echte Neubau ist der **Mail-Layer** (und alles, was daran hängt: Reset, Einladung, Benachrichtigungen).
|
||||
|
||||
---
|
||||
|
||||
## 2. Bausteine für die genannten Punkte
|
||||
|
||||
### 2.1 E-Mail-Infrastruktur (Fundament — schaltet Reset/Einladung/Benachrichtigung frei) 🟥
|
||||
- **Transaktionaler Mail-Provider** (SMTP oder API): z. B. Postmark/SendGrid/Mailgun/Amazon SES **oder** eigener SMTP. Auswahl = offene Entscheidung (§5).
|
||||
- **Domänen-Authentifizierung**: **SPF, DKIM, DMARC** einrichten (sonst Spam/Spoofing). Dediziert Absender-Domain (z. B. `no-reply@certvia.de`).
|
||||
- **Template-Engine** (gebrandet, Certvia): Layout-Basis + Bausteine für Einladung, Passwort-Reset, Passwort-geändert-Bestätigung, MFA-Änderung, Ticket-/Freigabe-/Fristen-Benachrichtigung.
|
||||
- **Queue + Retry** (BullMQ/Redis ist im Stack) für zuverlässigen, asynchronen Versand; Fehler-/Bounce-Handling; Rate-Limit.
|
||||
- **Benachrichtigungsregeln** je Nutzer/Ereignis (opt-in/opt-out, Sprache de/en), respektiert Mandanten-Isolation.
|
||||
|
||||
### 2.2 Passwort-Reset (Self-Service) 🟥
|
||||
- **Single-Use-Token**, **kurzlebig** (z. B. 30–60 Min), **serverseitig gehasht** gespeichert (nicht im Klartext), an E-Mail gebunden.
|
||||
- **Enumeration-Schutz**: „Falls ein Konto existiert, wurde eine E-Mail gesendet." (immer gleiche Antwort), **Rate-Limit** pro IP/Konto.
|
||||
- Nach Reset: **alle Sessions invalidieren**, Bestätigungs-Mail „Passwort geändert", Audit-Log-Eintrag. Deaktivierte/geblockte Konten: kein Reset.
|
||||
|
||||
### 2.3 Passwort selbst ändern 🟡
|
||||
- Profilseite „Passwort ändern": **Alt-Passwort-Bestätigung**, Policy-Prüfung (bestehende `password-policy.ts`), danach **andere Sessions abmelden** (aktuelle behalten), Bestätigungs-Mail + Audit.
|
||||
|
||||
### 2.4 2FA scharfschalten & härten 🟡→
|
||||
- **MFA-Enrollment-Gate**: bei `securityPolicy.mfaRequired` (tenant-weit) oder für Plattform-Admins Enrollment beim Login **erzwingen** (analog Force-Change-Gate).
|
||||
- **Recovery-Codes**: Anzeige/Regenerierung, Verbrauch protokolliert (vorhanden — Flow abrunden).
|
||||
- **Optional/Ausbau**: **WebAuthn/Passkeys** (phishing-resistent) als 2. Faktor; **Re-Auth** (Step-up) für sensible Aktionen (MFA deaktivieren, Admin anlegen, Export).
|
||||
- **TOTP-Secret** at rest **verschlüsselt** speichern (falls noch Klartext) + Rate-Limit auf Code-Eingabe.
|
||||
|
||||
### 2.5 Weiterer übergreifender Plattform-Admin 🟡→
|
||||
- **Plattform-Admin-Verwaltung** im Adminportal (`/platform/...`): Liste, **anlegen** (Initial-Passwort/Einladung), Rollen/Rechte (z. B. „Voll-Admin" vs. „Support/Read-only"), **sperren/reaktivieren**, Passwort-Reset, **MFA-Pflicht für alle Plattform-Admins**.
|
||||
- **Vier-Augen für kritische Plattform-Aktionen** (mind. 2 Admins, kein Self-Lockout — der Lockout-Schutz existiert bereits für Tenant-Rollen, hier fürs Plattform-Level ergänzen).
|
||||
- **Vollständiges Audit** jeder Plattform-Admin-Aktion (`scope=platform`, ist im Log-Modell vorgesehen).
|
||||
|
||||
---
|
||||
|
||||
## 3. Weitere sinnvolle Sicherheitsmaßnahmen (Empfehlung, priorisiert)
|
||||
|
||||
**P0 — sollte mit diesem Block kommen (hoher Nutzen, moderat):**
|
||||
- **HTTP-Security-Header**: CSP, HSTS, `X-Frame-Options`/`frame-ancestors`, `X-Content-Type-Options`, `Referrer-Policy`, `Permissions-Policy`. (Zentraler Middleware-Layer.)
|
||||
- **Rate-Limiting/Brute-Force** an Login, Reset, MFA, API — ergänzt den vorhandenen Konto-Lockout (IP- **und** kontobezogen).
|
||||
- **Session-Härtung**: kurze Idle-/Absolute-Timeouts, **Session-Invalidierung** bei Passwort-Reset/Deaktivierung/MFA-Änderung, **„aktive Sitzungen" anzeigen + einzeln abmelden**.
|
||||
- **Sichere Token-/Secret-Behandlung**: alle Tokens single-use + gehasht; Secrets nur aus Env/Secret-Store, nie im Repo; **Secret-Scanning** in CI.
|
||||
- **Audit-/Security-Event-Log erweitern**: Logins (Erfolg/Fehlschlag), Rechteänderungen, MFA-Änderungen, Exporte, Admin-Aktionen; **append-only**/manipulationssicher; Aufbewahrung definiert.
|
||||
- **Passwort-Breach-Check** beim Setzen (HaveIBeenPwned k-Anonymity) zusätzlich zur Policy.
|
||||
|
||||
**P1 — kurz danach (Betrieb/Compliance):**
|
||||
- **Tenant-Isolationstests** automatisiert (RLS-Regression) — Kernrisiko bei Multi-Tenant.
|
||||
- **Verschlüsselung at rest** (DB + **Backups**), **TLS/HSTS** überall, Key-Rotation; TOTP-/sensible Felder verschlüsselt.
|
||||
- **Backups + regelmäßige Restore-Tests** (dogfooding: das fordert ihr selbst als Control ein), DR-Konzept, RTO/RPO.
|
||||
- **Dependency-/Container-Scanning** (SCA), **SAST**, `npm audit`/Renovate; Patch-SLA.
|
||||
- **DSGVO-Funktionen** (Admin Phase 2): mandantenvollständiger **Export**, **Löschkonzept/Retention**, AVV/DPA-Bausteine, TOMs dokumentiert.
|
||||
- **Impersonation** (Admin Phase 2) **nur** zeitlich begrenzt, protokolliert, mit „Support-Sitzung aktiv"-Banner (Support-Zugriff sicher machen).
|
||||
- **E-Mail-Change-Verifizierung** (Double-Opt-in bei Adressänderung) + Benachrichtigung an alte Adresse.
|
||||
|
||||
**P2 — mittelfristig / Reifegrad:**
|
||||
- **WebAuthn/Passkeys**, **SSO (OIDC/SAML)** für Enterprise-Kunden (steht im Backlog).
|
||||
- **Datei-Upload-Sicherheit** (sobald Storage kommt): AV-Scan, MIME/Größen-Limits, kein HTML-Serving, signierte URLs.
|
||||
- **WAF/Reverse-Proxy** + DDoS-Schutz vor der App; least-privilege DB-User; Netzsegmentierung.
|
||||
- **Responsible-Disclosure**: `security.txt`, Kontaktpfad, ggf. Bug-Bounty.
|
||||
- **Externer Pen-Test** vor „Go-Live/Skalierung"; Ergebnisse als Maßnahmen ins eigene ISMS.
|
||||
- **Incident-Response-Prozess** für Certvia selbst (passt zum NIS2-/Vorfälle-Modul, das ihr ohnehin baut).
|
||||
|
||||
---
|
||||
|
||||
## 4. Vorschlag: Priorisierte Roadmap (integriert eure Punkte + P0)
|
||||
|
||||
| Reihe | Paket | Inhalt |
|
||||
|---|---|---|
|
||||
| **1** | **Mail-Fundament** | Provider + SPF/DKIM/DMARC, Queue/Retry, Certvia-Templates, Benachrichtigungsregeln |
|
||||
| **2** | **Auth-Self-Service** | Passwort-Reset (Token), Passwort selbst ändern, E-Mail-Change-Verifizierung, Session-Invalidierung |
|
||||
| **3** | **MFA scharf** | Enrollment-Gate erzwingen, Recovery-Codes-Flow, TOTP-Secret verschlüsselt, Step-up für sensible Aktionen |
|
||||
| **4** | **Plattform-Admin-Verwaltung** | Weitere Superadmins anlegen/verwalten, Rollen, Vier-Augen, Plattform-Audit |
|
||||
| **5** | **Härtung P0** | Security-Header, Rate-Limiting, Audit-Erweiterung, Breach-Check, Secret-Scanning |
|
||||
| **6+** | **P1/P2** | RLS-Tests, at-rest-Verschlüsselung, Backups/Restore, DSGVO, Impersonation, WebAuthn/SSO, Pen-Test |
|
||||
|
||||
Pakete 1–4 sind stark verzahnt (alles hängt am Mail-Fundament); 5 läuft quer und sollte **mit** 1–4 kommen, nicht danach.
|
||||
|
||||
---
|
||||
|
||||
## 5. Offene Entscheidungen (bevor wir Tasks schneiden)
|
||||
1. **Mail-Provider**: API-Dienst (Postmark/SendGrid/SES …) **oder** eigener SMTP? (beeinflusst Zustellbarkeit, DSGVO-Ort, Aufwand)
|
||||
2. **2FA-Ausbau**: reicht TOTP scharfschalten, oder direkt **WebAuthn/Passkeys** mitnehmen?
|
||||
3. **Plattform-Admin-Rollen**: nur „Voll-Admin", oder abgestufte Rollen (Support/Read-only)?
|
||||
4. **DSGVO-Umfang** jetzt (Export/Löschung/Retention) oder in eigener Runde?
|
||||
5. **Team-Setup**: 1, 2 oder 3 Entwickler parallel — dann schneide ich analog zum Wizard vertikale Lanes.
|
||||
|
||||
---
|
||||
|
||||
## 6. Nächster Schritt
|
||||
Auf dieser Basis schneide ich — wie beim Wizard — **Entwickler-Aufgabenpakete** (mit Branch-Konvention unter `dev`, Akzeptanzkriterien, DoD). Sag mir die Antworten zu §5 (v. a. Mail-Provider und Team-Größe), dann lege ich los. Reihenfolge-Empfehlung: **Mail-Fundament zuerst**, weil Reset/Einladung/Benachrichtigung daran hängen.
|
||||
Reference in New Issue
Block a user