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:
+13
-88
@@ -2,9 +2,9 @@ import { PrismaClient } from "@prisma/client";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
|
||||
/**
|
||||
* Central database access for the ISMS tool.
|
||||
* Central database access for Craftvia.
|
||||
*
|
||||
* Multi-tenant rule (see docs/SPEC.md §2): no query on tenant-scoped models
|
||||
* Multi-tenant rule (see docs/craftvia/SPEC-CRAFTVIA.md §5): no query on tenant-scoped models
|
||||
* without a tenant context. Application code MUST use `dbForTenant(tenantId)`
|
||||
* for all tenant-scoped models — the raw client is only for global data
|
||||
* (permissions, control catalogs) and platform administration.
|
||||
@@ -18,7 +18,7 @@ import { PrismaPg } from "@prisma/adapter-pg";
|
||||
* ohne FORCE nicht, die alleinige Isolation ist der Tenant-Guard unten.
|
||||
*
|
||||
* Ist `RLS_ENFORCED=true`, verbindet sich die App zusätzlich als eingeschränkte
|
||||
* Rolle `isms_app` über `RLS_DATABASE_URL` (NOBYPASSRLS). Für diese Rolle sind
|
||||
* Rolle `craftvia_app` über `RLS_DATABASE_URL` (NOBYPASSRLS). Für diese Rolle sind
|
||||
* die Policies unter `FORCE ROW LEVEL SECURITY` scharf. `dbForTenant` setzt dann
|
||||
* pro Operation den Mandantenkontext (`app.tenant_id`) TRANSAKTIONSLOKAL und
|
||||
* führt die Operation auf DERSELBEN Transaktions-Connection aus — nur so greift
|
||||
@@ -51,7 +51,7 @@ if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||
export const RLS_ENFORCED = process.env.RLS_ENFORCED === "true";
|
||||
|
||||
/**
|
||||
* Basis-Client der eingeschränkten App-Rolle `isms_app` (nur bei aktivem Flag).
|
||||
* Basis-Client der eingeschränkten App-Rolle `craftvia_app` (nur bei aktivem Flag).
|
||||
* Uneextendiert — wird in `dbForTenant` gezielt für die transaktionslokale
|
||||
* Kontextsetzung genutzt, damit die Guard-Extension nicht rekursiv greift.
|
||||
*
|
||||
@@ -64,8 +64,8 @@ if (RLS_ENFORCED) {
|
||||
if (!rlsUrl) {
|
||||
throw new Error(
|
||||
"RLS_ENFORCED=true, aber RLS_DATABASE_URL fehlt. Setze eine Verbindung als " +
|
||||
"Rolle isms_app (LOGIN, NOBYPASSRLS), z. B. " +
|
||||
"postgresql://isms_app:<pw>@<host>:5432/isms?schema=public — sonst würde die " +
|
||||
"Rolle craftvia_app (LOGIN, NOBYPASSRLS), z. B. " +
|
||||
"postgresql://craftvia_app:<pw>@<host>:5432/craftvia?schema=public — sonst würde die " +
|
||||
"App als Owner ohne scharfe RLS laufen (fail secure).",
|
||||
);
|
||||
}
|
||||
@@ -92,87 +92,12 @@ const TENANT_MODELS = new Set<string>([
|
||||
// davor, dass ein Token versehentlich ohne Mandantenfilter über dbForTenant
|
||||
// gelesen wird.
|
||||
"AuthToken",
|
||||
"Task",
|
||||
"TaskComment",
|
||||
"TaskParticipant",
|
||||
"Evidence",
|
||||
"Audit",
|
||||
"AuditEvidenceItem",
|
||||
"ControlDescription",
|
||||
"ManagedRegister",
|
||||
"RegisterRow",
|
||||
"OnboardingProgress",
|
||||
"ProjectFunctionAssignment",
|
||||
"WizardFact",
|
||||
"WizardScope",
|
||||
"PolicyPackageState",
|
||||
"TenantSettings",
|
||||
"TenantModule",
|
||||
"Asset",
|
||||
"AssetRelation",
|
||||
"Process",
|
||||
"ProcessAsset",
|
||||
"ProcessDependency",
|
||||
"BiaEntry",
|
||||
"Risk",
|
||||
"RiskAsset",
|
||||
"Measure",
|
||||
"RiskMeasure",
|
||||
"SupplierProfile",
|
||||
"ITServiceProfile",
|
||||
"SoftwareProfile",
|
||||
"ProjectProfile",
|
||||
"SupplierAssessment",
|
||||
"Contract",
|
||||
"Nda",
|
||||
"SupplierEvidence",
|
||||
"ServiceControlResponsibility",
|
||||
"Subcontractor",
|
||||
"ManagementDecision",
|
||||
"MaturityAssessment",
|
||||
"ControlAssessment",
|
||||
"ControlImplementation",
|
||||
// Incident-Management (IM-A)
|
||||
"Incident",
|
||||
"IncidentComment",
|
||||
"IncidentAsset",
|
||||
"IncidentProcess",
|
||||
"IncidentRisk",
|
||||
"IncidentControl",
|
||||
"IncidentMeasure",
|
||||
"IncidentEvidence",
|
||||
"IncidentAttachment",
|
||||
// IM-D — mandantengebundene Intake-Konfiguration (RLS). Die Review-Queue
|
||||
// (IncidentInboundReview) ist bewusst plattformweit → NICHT hier.
|
||||
"IncidentIntakeConfig",
|
||||
// WS4b: WebAuthnCredential ist jetzt identitäts-global (kein tenant_id) → NICHT hier.
|
||||
"PolicyDocument",
|
||||
"PolicyRequirement",
|
||||
"PolicyVariable",
|
||||
// AP1: Framework-Zugehörigkeit je Mandant (RLS-Policy in der Migration).
|
||||
"TenantFramework",
|
||||
// AP3: Anwendbarkeitserklärung (SoA) je Mandant/Framework (RLS-Policy in der Migration).
|
||||
"SoaEntry",
|
||||
// AP4: Managementklauseln (Kennzahlen 9.1, Managementbewertung 9.3, CAPA 10.2).
|
||||
"Kpi",
|
||||
"KpiValue",
|
||||
"ManagementReview",
|
||||
"ManagementReviewDecision",
|
||||
"Nonconformity",
|
||||
"CorrectiveAction",
|
||||
// AP5: Dokumentenlenkung (Lesebestätigung + Versionshistorie).
|
||||
"PolicyAcknowledgement",
|
||||
"PolicyDocumentVersion",
|
||||
"PolicyBaselineParam",
|
||||
"PolicyEvidence",
|
||||
"CryptoEntry",
|
||||
"ClassificationClass",
|
||||
"HandlingAspect",
|
||||
"HandlingRule",
|
||||
"RiskMatrixClass",
|
||||
"RiskEwLevel",
|
||||
"RiskDamageDimension",
|
||||
"HandbookTopic",
|
||||
// WebAuthnCredential/Identity sind identitäts-global (kein tenant_id) → NICHT hier.
|
||||
// Craftvia-Fachmodelle hier ergänzen — UND in src/server/backup/topology.ts
|
||||
// (TENANT_MODELS) sowie per `SELECT enable_tenant_rls('<table>')` in der Migration
|
||||
// (docs/craftvia/MIGRATIONS.md).
|
||||
]);
|
||||
|
||||
/**
|
||||
@@ -196,7 +121,7 @@ const TENANT_MODELS = new Set<string>([
|
||||
* Isolations-Exception wirft der Aufrufer UNABHÄNGIG davon weiter.
|
||||
*
|
||||
* Achtung Kontext: Die Funktion läuft mitten in der laufenden DB-Operation
|
||||
* (im RLS-Pfad ggf. innerhalb der F-04-Transaktion des `isms_app`-Basisclients).
|
||||
* (im RLS-Pfad ggf. innerhalb der F-04-Transaktion des `craftvia_app`-Basisclients).
|
||||
* Der Audit-Insert läuft über den separaten Owner-`prisma` auf EIGENER Connection
|
||||
* und ist damit von der Tenant-Transaktion entkoppelt: Er verklemmt sie nicht und
|
||||
* bleibt bestehen, auch wenn die Tenant-Operation durch den anschließenden Throw
|
||||
@@ -243,7 +168,7 @@ type DelegateSource = Record<string, GuardDelegate>;
|
||||
|
||||
/**
|
||||
* F-02-Tenant-Guard, herausgelöst, damit er über zwei Ausführungspfade
|
||||
* (Owner ohne RLS / isms_app mit RLS-Transaktion) identisch wiederverwendet wird.
|
||||
* (Owner ohne RLS / craftvia_app mit RLS-Transaktion) identisch wiederverwendet wird.
|
||||
*
|
||||
* `source` liefert die uneextendierten Modell-Delegates für die Direktaufrufe
|
||||
* (findUnique-Hybrid, Ownership-Vorprüfung) — bei RLS-off der Owner-`prisma`,
|
||||
@@ -371,7 +296,7 @@ async function applyTenantGuard(
|
||||
* reads are filtered by tenantId, creates get the tenantId injected.
|
||||
*
|
||||
* RLS-off (Default): Owner-`prisma` + Guard-Extension, `query(args)` als Ausführung.
|
||||
* RLS-on (`RLS_ENFORCED=true`): `isms_app`-Basisclient; jede Operation eines
|
||||
* RLS-on (`RLS_ENFORCED=true`): `craftvia_app`-Basisclient; jede Operation eines
|
||||
* Tenant-Modells läuft in einer eigenen Transaktion des Basisclients, in der
|
||||
* `app.tenant_id` transaktionslokal gesetzt wird (verworfen bei Commit/Rollback
|
||||
* → kein Leak über den Pool). Die Guard-Logik ist in beiden Pfaden identisch.
|
||||
|
||||
Reference in New Issue
Block a user