diff --git a/src/app/(app)/assets/layout.tsx b/src/app/(app)/assets/layout.tsx new file mode 100644 index 0000000..683b298 --- /dev/null +++ b/src/app/(app)/assets/layout.tsx @@ -0,0 +1,7 @@ +import { requireModule } from "@/server/modules"; + +/** Serverseitige Modul-Durchsetzung für alle Routen dieses Bereichs (§3.4). */ +export default async function ModuleLayout({ children }: { children: React.ReactNode }) { + await requireModule("assets"); + return <>{children}; +} diff --git a/src/app/(app)/dependencies/layout.tsx b/src/app/(app)/dependencies/layout.tsx new file mode 100644 index 0000000..669b89c --- /dev/null +++ b/src/app/(app)/dependencies/layout.tsx @@ -0,0 +1,7 @@ +import { requireModule } from "@/server/modules"; + +/** Serverseitige Modul-Durchsetzung für alle Routen dieses Bereichs (§3.4). */ +export default async function ModuleLayout({ children }: { children: React.ReactNode }) { + await requireModule("dependencies"); + return <>{children}; +} diff --git a/src/app/(app)/measures/layout.tsx b/src/app/(app)/measures/layout.tsx new file mode 100644 index 0000000..3e89260 --- /dev/null +++ b/src/app/(app)/measures/layout.tsx @@ -0,0 +1,7 @@ +import { requireModule } from "@/server/modules"; + +/** Serverseitige Modul-Durchsetzung für alle Routen dieses Bereichs (§3.4). */ +export default async function ModuleLayout({ children }: { children: React.ReactNode }) { + await requireModule("measures"); + return <>{children}; +} diff --git a/src/app/(app)/policies/layout.tsx b/src/app/(app)/policies/layout.tsx new file mode 100644 index 0000000..0bb7129 --- /dev/null +++ b/src/app/(app)/policies/layout.tsx @@ -0,0 +1,7 @@ +import { requireModule } from "@/server/modules"; + +/** Serverseitige Modul-Durchsetzung für alle Routen dieses Bereichs (§3.4). */ +export default async function ModuleLayout({ children }: { children: React.ReactNode }) { + await requireModule("policies"); + return <>{children}; +} diff --git a/src/app/(app)/processes/layout.tsx b/src/app/(app)/processes/layout.tsx new file mode 100644 index 0000000..4599170 --- /dev/null +++ b/src/app/(app)/processes/layout.tsx @@ -0,0 +1,7 @@ +import { requireModule } from "@/server/modules"; + +/** Serverseitige Modul-Durchsetzung für alle Routen dieses Bereichs (§3.4). */ +export default async function ModuleLayout({ children }: { children: React.ReactNode }) { + await requireModule("bia"); + return <>{children}; +} diff --git a/src/app/(app)/risks/layout.tsx b/src/app/(app)/risks/layout.tsx new file mode 100644 index 0000000..d08bde1 --- /dev/null +++ b/src/app/(app)/risks/layout.tsx @@ -0,0 +1,7 @@ +import { requireModule } from "@/server/modules"; + +/** Serverseitige Modul-Durchsetzung für alle Routen dieses Bereichs (§3.4). */ +export default async function ModuleLayout({ children }: { children: React.ReactNode }) { + await requireModule("risk"); + return <>{children}; +} diff --git a/src/app/(app)/suppliers/layout.tsx b/src/app/(app)/suppliers/layout.tsx new file mode 100644 index 0000000..990186c --- /dev/null +++ b/src/app/(app)/suppliers/layout.tsx @@ -0,0 +1,7 @@ +import { requireModule } from "@/server/modules"; + +/** Serverseitige Modul-Durchsetzung für alle Routen dieses Bereichs (§3.4). */ +export default async function ModuleLayout({ children }: { children: React.ReactNode }) { + await requireModule("suppliers"); + return <>{children}; +} diff --git a/src/server/actions/policies.ts b/src/server/actions/policies.ts index f6ad141..826d01a 100644 --- a/src/server/actions/policies.ts +++ b/src/server/actions/policies.ts @@ -6,6 +6,7 @@ import { z } from "zod"; import { requireSession } from "@/server/auth"; import { dbForTenant } from "@/server/db"; import { requirePermission } from "@/server/rbac"; +import { requireModule } from "@/server/modules"; import { writeAuditLog } from "@/server/audit"; const optDate = (v: FormDataEntryValue | null) => (v && String(v) ? new Date(String(v)) : null); @@ -13,6 +14,7 @@ const str = (v: FormDataEntryValue | null) => (v ? String(v).trim() : ""); async function guard() { const session = await requireSession(); + await requireModule("policies"); // API-Ebene: deaktiviertes Modul sperrt auch Actions requirePermission(session, "policy:write"); return { session, db: dbForTenant(session.user.tenantId) }; } diff --git a/src/server/modules.ts b/src/server/modules.ts new file mode 100644 index 0000000..c3d6e2c --- /dev/null +++ b/src/server/modules.ts @@ -0,0 +1,18 @@ +import { redirect } from "next/navigation"; +import { requireSession } from "@/server/auth"; +import { dbForTenant } from "@/server/db"; + +/** + * Serverseitige Modul-Durchsetzung (§3.4): blockiert den Zugriff auf ein für den + * Mandanten deaktiviertes Modul. Wird als Modul-Layout je Routenbereich angewandt + * und deckt damit alle Unterrouten ab. Fehlt eine TenantModule-Zeile, gilt das + * Modul als aktiv (Default an, Rückwärtskompatibilität). + */ +export async function requireModule(moduleKey: string) { + const session = await requireSession(); + const row = await dbForTenant(session.user.tenantId).tenantModule.findUnique({ + where: { tenantId_moduleKey: { tenantId: session.user.tenantId, moduleKey } }, + }); + if (row && !row.enabled) redirect("/dashboard?module=disabled"); + return session; +}