Verwaltete Register (GAP-Report §5.2) sind nicht mehr nur benannte Dokumente, sondern editierbare Register mit Pflichtspalten und Cross-Links. - Schema: ManagedRegister (code, title, columns JSON, reviewCycle, responsible, supplierLink/assetLink) + RegisterRow (values JSON, supplierRef, assetRef). Migration + RLS; in TENANT_MODELS. - import-managed.ts: 7 generische Register (REG-PROJECTS, -SENS-ROLES, -AUDIT-PLAN, -EXT-SERVICES, -SW-WHITELIST, -CRIT-SERVICES, -NET) mit definierten Pflichtspalten + Cross-Link-Flags; legt Register-Dokument (Bibliothek/Link) UND ManagedRegister- Definition idempotent an (RegisterRow bleibt bei Re-Import erhalten). - actions/register.ts: addRegisterRow/updateRegisterRow/deleteRegisterRow (Modul policies, policy:write; Werte gegen die Pflichtspalten, optional Lieferant/Asset). - components/generic-register.tsx: editierbare Zeilen-Tabelle + Cross-Link-Selects (Lieferant = Asset SUPPLIER/IT_SERVICE, Asset = alle); in /policies/[code] eingebunden. Browser-verifiziert: REG-EXT-SERVICES rendert 7 Spalten + Lieferant/Asset-Selects; Eintrag anlegen persistiert alle Werte. tsc + lint + build + Guard-Check grün. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
96 lines
3.7 KiB
TypeScript
96 lines
3.7 KiB
TypeScript
/**
|
|
* Vollständigkeitscheck der serverseitigen Modul-Durchsetzung (§3.4, Phase-1-Härtung).
|
|
*
|
|
* Jede mutierende Server-Action eines gegateten Moduls MUSS über einen
|
|
* `moduleGuard("<key>")`-Guard laufen (siehe src/server/action-guard.ts), damit ein
|
|
* für den Mandanten deaktiviertes Modul auch Writes serverseitig abweist.
|
|
*
|
|
* Dieses Script erzwingt das statisch: Es kennt die Zuordnung Action-Datei → Modul
|
|
* und schlägt fehl (Exit 1 → Build/Test rot), sobald
|
|
* - eine neue Action-Datei nicht zugeordnet ist ("vergessener Endpoint"),
|
|
* - eine gegatete Datei den erwarteten moduleGuard nicht verwendet, oder
|
|
* - eine exportierte Action nicht über `await guard(...)` läuft.
|
|
*
|
|
* Neue Action-Datei anlegen ⇒ hier eintragen (Modul-Key oder "EXEMPT").
|
|
*/
|
|
import { readdirSync, readFileSync } from "node:fs";
|
|
import { join, dirname } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { MODULE_KEYS } from "../src/lib/modules";
|
|
|
|
const ACTIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "src", "server", "actions");
|
|
|
|
/** Zuordnung Action-Datei → Modul-Key. "EXEMPT" = kein gegatetes Fachmodul (eigene Auth). */
|
|
const ACTION_MODULE: Record<string, string> = {
|
|
"assets.ts": "assets",
|
|
"processes.ts": "bia",
|
|
"risks.ts": "risk",
|
|
"measures.ts": "measures",
|
|
"tasks.ts": "tasks",
|
|
"suppliers.ts": "suppliers",
|
|
"services.ts": "suppliers",
|
|
"policies.ts": "policies",
|
|
"register.ts": "policies",
|
|
// Plattform-Betrieb (eigene Auth) und Kunden-Einstellungen (tenant:manage) sind
|
|
// keine per TenantModule gegateten Fachmodule — eigene Autorisierung, kein moduleGuard.
|
|
"admin.ts": "EXEMPT",
|
|
"platform.ts": "EXEMPT",
|
|
"platform-users.ts": "EXEMPT",
|
|
"tenant-users.ts": "EXEMPT",
|
|
"account.ts": "EXEMPT",
|
|
"tenant-settings.ts": "EXEMPT",
|
|
};
|
|
|
|
const errors: string[] = [];
|
|
const files = readdirSync(ACTIONS_DIR).filter((f) => f.endsWith(".ts"));
|
|
|
|
for (const file of files) {
|
|
const mapped = ACTION_MODULE[file];
|
|
if (!mapped) {
|
|
errors.push(
|
|
`Nicht zugeordnete Action-Datei: ${file} — in scripts/check-module-guards.ts eintragen (Modul-Key oder "EXEMPT").`
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const src = readFileSync(join(ACTIONS_DIR, file), "utf8");
|
|
|
|
if (mapped === "EXEMPT") {
|
|
if (!/require(Session|Platform\w*|Permission)/.test(src)) {
|
|
errors.push(`${file}: als EXEMPT markiert, aber keine erkennbare Auth-Prüfung.`);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (!MODULE_KEYS.includes(mapped)) {
|
|
errors.push(`${file}: unbekannter Modul-Key "${mapped}" (nicht in src/lib/modules.ts).`);
|
|
}
|
|
if (!src.includes(`moduleGuard("${mapped}")`)) {
|
|
errors.push(`${file}: erwartet moduleGuard("${mapped}") — Modul-Gating fehlt oder falscher Key.`);
|
|
}
|
|
|
|
// Jede exportierte Server-Action muss über await guard(...) laufen.
|
|
const exportRe = /export async function (\w+)\s*\(/g;
|
|
const positions: { name: string; index: number }[] = [];
|
|
let m: RegExpExecArray | null;
|
|
while ((m = exportRe.exec(src))) positions.push({ name: m[1], index: m.index });
|
|
for (let i = 0; i < positions.length; i++) {
|
|
const start = positions[i].index;
|
|
const end = i + 1 < positions.length ? positions[i + 1].index : src.length;
|
|
if (!/await guard\(/.test(src.slice(start, end))) {
|
|
errors.push(
|
|
`${file}: Action "${positions[i].name}" läuft nicht über await guard(...) — Modul-/Rechte-Guard fehlt.`
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (errors.length) {
|
|
console.error("✗ Modul-Guard-Vollständigkeitscheck fehlgeschlagen:");
|
|
for (const e of errors) console.error(" - " + e);
|
|
process.exit(1);
|
|
}
|
|
console.log(
|
|
`✓ Modul-Guard-Vollständigkeitscheck: ${files.length} Action-Dateien geprüft — alle mutierenden Actions sind modul- und rechtegegated.`
|
|
);
|