Fundament: ISMS-Module entfernt; Craftvia-Rollen, Module, Navigation, i18n-Split

- ISMS-Routen, Actions, Server-/Lib-Code, Komponenten, Prisma-Modelle, Seeds,
  Importer, Skripte und ISMS-Tests entfernt (Fundament bleibt: Auth, Identity,
  MFA/WebAuthn, RBAC, Audit, Mail, Storage, Backup/DSGVO, Plattform-Admin)
- Schema auf Fundament-Modelle reduziert; TenantSettings generisch (+phone/email)
- TENANT_MODELS (db.ts, backup/topology.ts) und PII-Felder ausgedünnt
- RBAC: Rollen tenant-admin/backoffice/team-lead/technician + Craftvia-Permissions
- Modul-Katalog (customers, sites, teams, work_orders, imports, field, reports,
  emergency, documents, notifications, lotse) + Navigation aus src/lib/nav.ts
- Modul-Routen mit requireModule-Layout und Platzhalterseite
- Message-Katalog je Namespace (messages/<locale>/<namespace>.json), fs-Loader
- check-module-guards: Modul-Key aus src/server/actions/<moduleKey>/
- Provisionierung, Admin-Konsole, Einstellungen, Files-Route, Mail entkoppelt
- Seed minimal (demo/demo2, Nutzer je Rolle); Fundament-Tests auf Role/
  NotificationPreference-Fixtures umgestellt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 11:35:44 +02:00
co-authored by Claude Opus 5
parent c8e6f30a27
commit 8491c7f173
443 changed files with 1325 additions and 87773 deletions
+12 -12
View File
@@ -3,7 +3,7 @@
// F-13: Ein `role:manage`-Inhaber (z. B. tenant-admin) konnte über `createRole`/
// `updateRolePermissions` eine Rolle mit BELIEBIGEN Katalog-Rechten bauen und sie sich
// über `setUserRoles` selbst zuweisen — obwohl der tenant-admin bewusst NICHT über
// `policy:approve`/`risk:accept`/`soa:write` verfügt. Der Fix begrenzt vergebbare Rechte
// `report:approve`/`work_order:release_billing`/`customer:merge` verfügt. Der Fix begrenzt vergebbare Rechte
// auf die eigene effektive Rechtemenge (autoritativ aus der DB) und unterbindet die
// Selbstzuweisung höher privilegierter Rollen.
//
@@ -62,10 +62,10 @@ async function main() {
const tenant = await prisma.tenant.create({ data: { name: "F13 AuthZ Test", slug: SLUG } });
// tenant-admin-typische Rechte (bewusst OHNE policy:approve / risk:accept / soa:write).
// tenant-admin-typische Rechte (bewusst OHNE report:approve / work_order:release_billing / customer:merge).
const adminPermKeys = ["tenant:manage", "user:read", "user:manage", "role:manage", "report:read"];
const adminPerms = await Promise.all(adminPermKeys.map(ensurePerm));
const escalationTargets = await Promise.all(["policy:approve", "risk:accept", "soa:write"].map(ensurePerm));
const escalationTargets = await Promise.all(["report:approve", "work_order:release_billing", "customer:merge"].map(ensurePerm));
const adminRole = await prisma.role.create({
data: {
@@ -84,10 +84,10 @@ async function main() {
},
});
// Höher privilegierte Rolle (ISB-artig), die der Actor sich NICHT selbst geben darf.
// Höher privilegierte Rolle (Backoffice-artig), die der Actor sich NICHT selbst geben darf.
const isbRole = await prisma.role.create({
data: {
tenantId: tenant.id, key: "isb", name: "ISB / CISO",
tenantId: tenant.id, key: "backoffice", name: "Backoffice",
rolePermissions: { create: escalationTargets.map((p) => ({ permissionId: p.id })) },
},
});
@@ -96,29 +96,29 @@ async function main() {
// (1) Effektive Menge stimmt und enthält bewusst NICHT die kritischen Rechte.
ok(effective.has("role:manage"), "(1) Actor hat role:manage");
ok(!effective.has("policy:approve") && !effective.has("risk:accept") && !effective.has("soa:write"),
"(1) Actor hat bewusst KEIN policy:approve/risk:accept/soa:write");
ok(!effective.has("report:approve") && !effective.has("work_order:release_billing") && !effective.has("customer:merge"),
"(1) Actor hat bewusst KEIN report:approve/work_order:release_billing/customer:merge");
// (2) createRole/updateRolePermissions: Delegation ist bewusst ERLAUBT (pragmatische
// F-13-Variante) — ein Admin darf Rollen mit Rechten oberhalb seines Niveaus für ANDERE
// anlegen (z. B. ISB klonen+bearbeiten). Die Escalation-Abwehr sitzt in der Selbst-
// anlegen (z. B. Backoffice klonen+bearbeiten). Die Escalation-Abwehr sitzt in der Selbst-
// zuweisung (4). Hier nur festhalten, dass die kritischen Rechte tatsächlich außerhalb
// des eigenen Niveaus liegen (sonst wäre der Test bedeutungslos).
ok(excessOf(["policy:approve", "risk:accept"], effective).length === 2,
"(2) policy:approve/risk:accept liegen außerhalb des Actor-Niveaus (Delegation dennoch erlaubt)");
ok(excessOf(["report:approve", "work_order:release_billing"], effective).length === 2,
"(2) report:approve/work_order:release_billing liegen außerhalb des Actor-Niveaus (Delegation dennoch erlaubt)");
// (3) Die für ANDERE delegierbaren Rechte sind nicht künstlich beschnitten.
ok(excessOf(["user:read", "report:read"], effective).length === 0,
"(3) Vergabe eigener Rechte (user:read/report:read) ist ohnehin zulässig");
// (4) Selbstzuweisung der höher privilegierten ISB-Rolle → blockiert (Kern-Abwehr).
// (4) Selbstzuweisung der höher privilegierten Backoffice-Rolle → blockiert (Kern-Abwehr).
const isbPerms = new Set(
(await prisma.rolePermission.findMany({ where: { roleId: isbRole.id }, select: { permission: { select: { key: true } } } }))
.map((r) => r.permission.key),
);
const selfAssignExcess = [...isbPerms].filter((p) => !effective.has(p));
ok(selfAssignExcess.length > 0,
"(4) Selbstzuweisung der ISB-Rolle bringt Rechte oberhalb des eigenen Niveaus → blockiert");
"(4) Selbstzuweisung der Backoffice-Rolle bringt Rechte oberhalb des eigenen Niveaus → blockiert");
// (5) Selbstzuweisung der eigenen (bereits gehaltenen) Admin-Rolle → erlaubt (⊆ effektiv).
const adminRolePerms = new Set(