Serverseitige Modul-Durchsetzung (§3.4)
Deaktivierte Module werden nicht nur in der Navigation ausgeblendet, sondern serverseitig gesperrt: - requireModule-Guard (server/modules.ts): prüft TenantModule und leitet bei deaktiviertem Modul auf /dashboard?module=disabled um (fehlende Zeile = aktiv). - Modul-Layout je Routenbereich (assets, processes, risks, measures, policies, suppliers, dependencies) — deckt alle Unterrouten mit ab (GET-Zugriff). - API-Ebene: requireModule zusätzlich im gemeinsamen guard() der Policy-Actions (Layout-Guard läuft bei Server-Actions erst nach der Mutation). Verifiziert im Browser: bei deaktiviertem Policies-Modul werden /policies und /policies/R08 auf /dashboard?module=disabled umgeleitet; Nav blendet den Punkt aus. Hinweis: Das gleiche guard()-Einzeiler-Muster kann auf die Actions der übrigen Module übertragen werden (bislang exemplarisch für Policies); die Route-Layouts sichern bereits alle Modul-Seiten. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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}</>;
|
||||||
|
}
|
||||||
@@ -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}</>;
|
||||||
|
}
|
||||||
@@ -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}</>;
|
||||||
|
}
|
||||||
@@ -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}</>;
|
||||||
|
}
|
||||||
@@ -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}</>;
|
||||||
|
}
|
||||||
@@ -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}</>;
|
||||||
|
}
|
||||||
@@ -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}</>;
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ import { z } from "zod";
|
|||||||
import { requireSession } from "@/server/auth";
|
import { requireSession } from "@/server/auth";
|
||||||
import { dbForTenant } from "@/server/db";
|
import { dbForTenant } from "@/server/db";
|
||||||
import { requirePermission } from "@/server/rbac";
|
import { requirePermission } from "@/server/rbac";
|
||||||
|
import { requireModule } from "@/server/modules";
|
||||||
import { writeAuditLog } from "@/server/audit";
|
import { writeAuditLog } from "@/server/audit";
|
||||||
|
|
||||||
const optDate = (v: FormDataEntryValue | null) => (v && String(v) ? new Date(String(v)) : null);
|
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() {
|
async function guard() {
|
||||||
const session = await requireSession();
|
const session = await requireSession();
|
||||||
|
await requireModule("policies"); // API-Ebene: deaktiviertes Modul sperrt auch Actions
|
||||||
requirePermission(session, "policy:write");
|
requirePermission(session, "policy:write");
|
||||||
return { session, db: dbForTenant(session.user.tenantId) };
|
return { session, db: dbForTenant(session.user.tenantId) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user