- scripts/bootstrap-admin.ts: legt ersten Plattform-Admin + Mandant via
provisionTenant(isPlatformAdmin) an; idempotent, per Env gesteuert. Ersetzt in
Prod den Demo-Seed. tsx-@/-Auflösung lokal verifiziert.
- docker-compose.coolify.yml: migrate-Job führt bei BOOTSTRAP_ADMIN=true das Skript
nach der Migration aus; BOOTSTRAP_*-Vars durchgereicht.
- .env.prod.example: Prod-Env-Referenz (HTTPS, kein Demo-Seed, Bootstrap-Vars).
- docs/DEPLOY-PROD-CONTABO.md: Runbook (VPS-Härtung/Swap, Coolify+Gitea, DNS/TLS
app.certvia.de, Deploy, Bootstrap, Backups, Go-Live-Checkliste).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Software (Lieferantenmodul):
- Neuer Tab „Software" mit Popups (Detail/Anlegen/Bearbeiten) analog IT-Services,
Anbieter-Verknüpfung, Freigabestatus, Version, Review; Server-Actions
createSoftware/updateSoftware/deleteSoftware.
Projekte (Assetinventar):
- AssetType PROJECT als Filter, Popup mit Kritikalität (C/I/A), Projektleitung,
IS-Klassifizierung, Status, ISB-Einbindung; Risiko-Verknüpfung über die
bestehende RiskAsset-Logik; Server-Actions createProject/updateProject/
deleteProject. Kontextabhängiger „Neu"-Button je Filter.
Kritische IT-Dienste:
- Schreibgeschützte Auto-Sicht (/assets?view=critical), abgeleitet aus
Verfügbarkeit und BIA; RTO/RPO aus den verknüpften Prozessen (strengster
Wert), Abhängigkeiten aus Asset-Relationen.
Software-/Projekt-Cockpits werden auch auf der Asset-Seite gerendert
(backHref=/assets). i18n (de/en) und Modul-Guard-Registrierung ergänzt;
alle Editierfunktionen als Popup.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Konzept (abgestimmt): Beim Einreichen wählt der Einreicher einen konkreten
Freigeber; die Freigabe/Ablehnung erfolgt im neuen Aufgaben-Modul (Vier-Augen).
- Modell: generisches Task + TaskComment (Migration inkl. RLS; Task/TaskComment
in TENANT_MODELS). Erster Typ policy_approval. Neues Modul „tasks" (lib/modules,
Nav, i18n) — für Bestands-Tenants per Default aktiv.
- policies.ts: submitForApproval(code, formData) verlangt einen Freigeber (aktiver
Nutzer mit policy:approve, ≠ Einreicher), setzt IN_FREIGABE und erzeugt eine
Aufgabe; Resubmit schließt alte offene Aufgaben. Alte approvePolicy/rejectPolicy
entfernt (wandern ins Aufgaben-Modul).
- actions/tasks.ts: approveTask/rejectTask (nur zugewiesener Freigeber, ≠ Einreicher;
Richtlinie → FREIGEGEBEN bzw. zurück auf ENTWURF) und commentTask (nur Beteiligte);
Kommentare/Statuswechsel historisiert, Audit.
- /tasks: Aufgaben des Nutzers (offen/erledigt) mit Freigeben/Ablehnen (Grund
erforderlich)/Kommentieren + Verlauf. Richtlinien-Editor: Freigeber-Dropdown beim
Einreichen, „Zur Freigabe bei …" + Link zur Aufgabe (keine Inline-Freigabe mehr).
- Dashboard: Kachel „N offene Aufgabe(n)" verlinkt auf /tasks.
- Seed: zweiter ISB „Bea Approver" als Freigeber (ermöglicht den Vier-Augen-Flow).
Browser-verifiziert: R08 eingereicht (Freigeber Bea) → Aufgabe; Dashboard-Kachel
bei Bea; Freigeben in /tasks → R08 FREIGEGEBEN, Task DONE, Kommentar-Historie
(submit+approve). tsc + lint + build + Guard-Check grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Eigener Bereich /settings/users (verlinkt aus /settings), sichtbar nur mit
user:manage bzw. role:manage. Strikt mandantenintern (dbForTenant über die eigene
Session) — kein Cross-Tenant-Zugriff. Audit-Scope: tenant.
- actions/tenant-users.ts:
- Benutzer (user:manage): createUser (Initial-/Einmal-Passwort, mustChangePassword),
resetUserPassword, setUserStatus (deaktivieren/reaktivieren), setUserRoles.
- Rollen (role:manage): createRole (+ Permissions), updateRolePermissions
(Standardrollen schreibgeschützt), cloneRole (Standard klonbar), deleteRole
(nur eigene, nur ohne zugewiesene Nutzer — sonst Umzug-Hinweis).
- Lockout-Schutz: letzter aktiver Mandanten-Admin kann nicht deaktiviert werden
und ihm kann die Admin-Rolle nicht entzogen werden.
- Komponenten tenant-users-manager.tsx (Benutzer) und role-manager.tsx (Rollen +
gruppierter Permission-Katalog); Bestands-UI/Dark-Theme wiederverwendet.
Browser-verifiziert: Mandanten-Admin sieht Benutzer + Rollen des eigenen Mandanten,
legt eine eigene Rolle „Externer Prüfer" mit ausgewählten Rechten an (asset:read,
audit:read, policy:read → korrekt gespeichert); Standardrollen als schreibgeschützt
markiert. tsc + lint + build + Guard-Check grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In der Mandanten-Detailansicht (/admin/[id]) kann der Plattform-Admin je Kunde
Benutzer anlegen und verwalten — ohne E-Mail-Flow, über Initial-/Einmal-Passwort.
- actions/platform-users.ts (Platform-Session-Guard, dbForTenant(zielTenant),
Audit scope=platform):
- createTenantUser: Name, E-Mail (eindeutig je Mandant), ≥1 Rolle; Initialpasswort
selbst setzen ODER policy-konformes Einmal-Passwort erzeugen (einmalig angezeigt);
Nutzer mit mustChangePassword=true.
- resetTenantUserPassword: neues Einmal-Passwort + erzwungener Wechsel.
- setTenantUserStatus: deaktivieren/reaktivieren.
- setTenantUserRoles: Mehrfach-Rollenzuweisung.
- Lockout-Schutz: letzter aktiver Mandanten-Admin kann nicht deaktiviert werden
und ihm kann die Admin-Rolle nicht entzogen werden.
- components/platform-tenant-users.tsx: Anlege-Formular (useActionState, zeigt das
Einmal-Passwort), Benutzerkarten mit Rollen-Checkboxen, Status-Toggle und Reset.
- /admin/[id] lädt Nutzer inkl. Rollen + Mandanten-Rollen und rendert die Verwaltung.
- Passwort-Generierung/-Prüfung gegen die Mandanten-Passwort-Policy.
- Zukunftssicher gekapselt: Einladungs-Flow (Paket 4) als alternative Aktivierung
(Token statt Initialpasswort) später einhängbar.
tsc + lint + build + Guard-Check grün. Browser-E2E folgt zusammen mit Paket C.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Plattform-Administratoren sind nicht länger isPlatformAdmin-Nutzer innerhalb eines
Mandanten, sondern ein getrennter Store mit eigener Auth-Domäne — Voraussetzung
für den sicheren Betrieb beim ersten echten Kunden.
Store & Migration
- Neues Modell PlatformAdmin (kein tenant_id): Argon2id-Hash, TOTP-Secret,
Recovery-Codes (nur SHA-256-Hashes), Fehlversuchszähler + Sperre, lastLogin.
- AuditLog.tenant_id nullable → mandantenlose Plattform-Ereignisse (scope=platform).
- Datenmigration: bestehende isPlatformAdmin-Nutzer in den neuen Store übernommen
(gleicher Hash → Login sofort möglich), Flag mandantenweit auf false gesetzt.
Getrennter Login + MFA
- Zweite NextAuth-Instanz (server/platform-auth.ts) mit eigenem Cookie und
eigenem basePath /api/platform-auth; Session trägt bewusst KEINEN tenantId.
- TOTP-MFA (otplib): Enrollment beim ersten Login (/platform/enroll-mfa, QR +
Klartext-Secret), danach bei jedem Login erzwungen; 10 einmalige Recovery-Codes.
- Härtung: Konto-Sperre nach 5 Fehlversuchen (15 min), Audit aller Anmeldungen,
Fehlversuche und Sperren (scope=platform).
Autorisierung / Trennung
- Admin-Konsole nach (platform)/admin verschoben; (platform)/layout.tsx erzwingt
Plattform-Session + aktivierte MFA. Mandanten-Session hat KEINEN Zugriff auf /admin.
- Mandanten-Shell zeigt keinen /admin-Link mehr; admin-Actions prüfen die
Plattform-Session statt des abgelösten Flags.
- provision/seed setzen isPlatformAdmin nicht mehr; Seed legt den Demo-Plattform-
Admin (admin@demo.example) im getrennten Store an.
Browser-verifiziert: Plattform-Login → erzwungenes MFA-Enrollment → Recovery-Codes →
/admin; Login ohne Code scheitert (?error=1); Mandanten-Session auf /admin wird auf
/platform/login umgeleitet. tsc + lint + build + Guard-Check grün.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der requireModule-Guard war bislang nur exemplarisch in den Policy-Actions
gesetzt. Jetzt läuft jede mutierende Server-Action eines gegateten Moduls über
einen zentralen, modul- und rechteprüfenden Guard — ein für den Mandanten
deaktiviertes Modul weist damit auch Writes serverseitig ab (nicht nur Reads
über die Route-Layouts).
- action-guard.ts: moduleGuard("<key>") erzeugt je Modul einen guard(...perms),
der Session → Modul-Aktivierung → RBAC prüft und {session, db} zurückgibt.
- modules.ts: assertModuleEnabled(session, key) wirft (statt Redirect) bei
Deaktivierung und protokolliert die Verweigerung (Audit-Aktion "denied").
- audit.ts: Audit-Aktion "denied" + optionaler scope-Parameter.
- Alle gegateten Action-Dateien umgestellt: assets→assets, processes→bia,
risks→risk, measures→measures, suppliers/services→suppliers, policies→policies.
Freigabe/Ablehnung der Richtlinien laufen jetzt ebenfalls über den Modul-Guard.
- Vollständigkeitscheck scripts/check-module-guards.ts (Registry Action→Modul):
schlägt fehl bei nicht zugeordneter Datei, fehlendem moduleGuard oder einer
Action ohne guard(...). Als prebuild verdrahtet ⇒ Build failt bei vergessenem
Endpoint. admin/tenant-settings sind als EXEMPT (eigene Auth) markiert.
Verifiziert: tsc sauber, lint sauber, Guard-Check grün (9 Dateien), Negativ-Probe
(ungemappte Action-Datei) failt den Check wie erwartet.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>