Richtlinien Phase 2a: verwaltete Register-Tabellen + Anwender-Handbuch (§7b, §9.7)
Vier neue, in die Bibliothek integrierte Dokumente (öffnen als interaktive Popups):
- Krypto-Register (VA-07): editierbare Tabelle der eingesetzten Verschlüsselung
mit Ablaufüberwachung (läuft-ab/abgelaufen-Markierung), Baseline-Bezug
(BL-CRY-…), voller CRUD (add/edit/delete).
- Risiko-Bewertungsmatrix (R03/VA-09): zentrale Pflegestelle mit FB-80-04-Defaults
— 4×4-Heatmap (Schadensausmaß × EW), Risikoklassen + Akzeptanzinstanz
(Niedrig≤3 / Mittel≤6 / Hoch≤9 / Kritisch≥12, editierbar), EW-Skala,
7 Schadenskategorien. (Rückkopplung in die 5×5-Heatmap der Risikoanalyse folgt.)
- Klassifizierungs-Handhabungsmatrix (R02/VA-08): AA-80-20-Defaults, 4 Schutzklassen
× 14 Aspekte mit eskalierenden Regeln; Zellen inline editierbar, Aspekt/Klasse
hinzufügbar.
- Anwender-Handbuch (§9.7): kuratierte Themen (Passwörter/MFA, Zugriffsrechte,
Klassifizierung, mobiles Arbeiten, E-Mail, Vorfälle) in verständlicher Sprache;
Werte via {{VARIABLE}}-Templating → automatisch synchron zur Baseline; Deep-Links
in die Quellabschnitte (z. B. R08 §4.1.2).
Zusätzlich: Richtlinien/Verfahren, die eine verwaltete Tabelle einbetten
(R09/VA-07, R03/VA-09, R02/VA-08), zeigen im Lesemodus einen „Vollständige Tabelle
öffnen"-Callout auf das jeweilige Register (§7b).
Datenmodell: CryptoEntry, ClassificationClass/HandlingAspect/HandlingRule,
RiskMatrixClass/RiskEwLevel/RiskDamageDimension, HandbookTopic (+ RLS).
Server-Actions mit RBAC (policy:write) + Audit-Log. Seed aus FB-80-04/AA-80-20.
Verifiziert im Browser: alle vier Register gerendert, Ablauf-/Farb-/Deep-Link-Logik,
Krypto-CRUD (add + delete) end-to-end.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
import type { PrismaClient } from "@prisma/client";
|
||||
|
||||
/**
|
||||
* Seed der verwalteten Register-Tabellen (§7b) und des Anwender-Handbuchs (§9.7).
|
||||
* Defaults aus den Auftraggeber-Vorgaben FB-80-04 (Risikomatrix) und
|
||||
* AA-80-20 (Klassifizierung/Handhabung). Idempotent pro Mandant.
|
||||
*/
|
||||
|
||||
const NOW = "2026-07-07T00:00:00.000Z";
|
||||
const daysFrom = (base: string, d: number) => new Date(new Date(base).getTime() + d * 86400000);
|
||||
|
||||
// Klassifizierungs-Handhabungsmatrix (AA-80-20) — Reihenfolge: Offen · Intern · Vertraulich · Streng vertraulich
|
||||
const CLASSES = [
|
||||
{ name: "Offen", description: "Öffentlich; keine Schutzanforderungen." },
|
||||
{ name: "Intern", description: "Nur für Mitarbeitende; geringer Schutzbedarf." },
|
||||
{ name: "Vertraulich", description: "Begrenzter Personenkreis; hoher Schutzbedarf." },
|
||||
{ name: "Streng vertraulich", description: "Namentlich Berechtigte; sehr hoher Schutzbedarf." },
|
||||
];
|
||||
|
||||
// [Offen, Intern, Vertraulich, Streng vertraulich]
|
||||
const ASPECTS: { name: string; category: string; rules: [string, string, string, string] }[] = [
|
||||
{ name: "Kennzeichnung (Papier/elektronisch)", category: "Kennzeichnung", rules: ["nicht nötig", "„Intern“ empfohlen", "„Vertraulich“ verpflichtend", "„Streng vertraulich“ + Bearbeiter"] },
|
||||
{ name: "Vervielfältigung", category: "Handhabung", rules: ["frei", "im Bedarfsfall", "nur mit Freigabe", "nur mit Freigabe, protokolliert"] },
|
||||
{ name: "Weitergabe intern", category: "Weitergabe", rules: ["frei", "an Mitarbeitende", "an Berechtigte, need-to-know", "namentlich Berechtigte, dokumentiert"] },
|
||||
{ name: "Postweg / Versand", category: "Weitergabe", rules: ["Standard", "Standard", "verschlossen, persönlich/Einschreiben", "Einschreiben eigenhändig, Empfangsbestätigung"] },
|
||||
{ name: "E-Mail", category: "Weitergabe", rules: ["ohne Auflagen", "intern ohne Auflagen", "extern verschlüsselt (BL-CRY-04)", "immer verschlüsselt, nur an Berechtigte"] },
|
||||
{ name: "Fax intern/extern", category: "Weitergabe", rules: ["zulässig", "zulässig", "nur mit Ankündigung/Abholung", "unzulässig"] },
|
||||
{ name: "Mobile Datenträger", category: "Speicherung", rules: ["zulässig", "nur freigegebene", "nur verschlüsselt (BL-CRY-03)", "nur verschlüsselt, freigegeben, protokolliert"] },
|
||||
{ name: "Internet / Blogs / Social Media", category: "Weitergabe", rules: ["zulässig", "kein interner Bezug", "unzulässig", "unzulässig"] },
|
||||
{ name: "Entsorgung Papier", category: "Entsorgung", rules: ["Altpapier", "Datentonne", "Aktenvernichter ≥ P-4", "Aktenvernichter ≥ P-5, protokolliert"] },
|
||||
{ name: "Löschung elektronisch / Hardware", category: "Entsorgung", rules: ["normal löschen", "normal löschen", "sicheres Löschen (BL-DEL-01)", "sicheres Löschen/Vernichten, Nachweis"] },
|
||||
{ name: "Speicherung (IT-Systeme)", category: "Speicherung", rules: ["beliebig", "interne Systeme", "zugriffsbeschränkte Ablagen", "verschlüsselt, streng zugriffsbeschränkt"] },
|
||||
{ name: "Homeoffice / mobiles Arbeiten", category: "Nutzung", rules: ["zulässig", "zulässig", "nur mit Sichtschutz, gesperrt bei Abwesenheit", "nur nach Freigabe, keine Papierform"] },
|
||||
{ name: "Öffentlicher Bereich / Dienstreise", category: "Nutzung", rules: ["zulässig", "diskret", "Sichtschutz, nicht unbeaufsichtigt", "unzulässig (kritische Länder)"] },
|
||||
{ name: "Verbale Weitergabe", category: "Weitergabe", rules: ["frei", "unter Kollegen", "nur in geschützter Umgebung", "nur namentlich Berechtigte, abhörsicher"] },
|
||||
];
|
||||
|
||||
// Risiko-Bewertungsmatrix (FB-80-04): Schadensausmaß(1–4) × EW(1–4) = 1–16
|
||||
const RISK_CLASSES = [
|
||||
{ name: "Niedrig", maxScore: 3, acceptance: "wird akzeptiert", tone: "ok" },
|
||||
{ name: "Mittel", maxScore: 6, acceptance: "Maßnahmen; Akzeptanz durch Risk Owner", tone: "warn" },
|
||||
{ name: "Hoch", maxScore: 9, acceptance: "Akzeptanz durch Risikomanagement-Gremium", tone: "orange" },
|
||||
{ name: "Kritisch", maxScore: 16, acceptance: "Akzeptanz durch Geschäftsführung", tone: "risk" },
|
||||
];
|
||||
const EW_LEVELS = [
|
||||
{ level: 1, label: "Unwahrscheinlich", definition: "Ereignis in absehbarer Zeit kaum zu erwarten (seltener als alle 5 Jahre)." },
|
||||
{ level: 2, label: "Gelegentlich", definition: "Ereignis denkbar, aber nicht häufig (ca. alle 1–5 Jahre)." },
|
||||
{ level: 3, label: "Wahrscheinlich", definition: "Ereignis tritt voraussichtlich ein (mehrmals pro Jahr)." },
|
||||
{ level: 4, label: "Sehr wahrscheinlich", definition: "Ereignis tritt regelmäßig/häufig ein (monatlich oder öfter)." },
|
||||
];
|
||||
const DAMAGE_DIMS: { name: string; levels: Record<string, string> }[] = [
|
||||
{ name: "Gesetzes-/Vertragsverstöße", levels: { "1": "unerheblich", "2": "geringfügige Verstöße", "3": "erhebliche Verstöße/Bußgelder", "4": "gravierende Rechtsfolgen/Straftatbestand" } },
|
||||
{ name: "Datenschutz", levels: { "1": "kein Personenbezug", "2": "geringe Betroffenheit", "3": "sensible Daten betroffen", "4": "massenhaft sensible Daten / hohe Betroffenheit" } },
|
||||
{ name: "Persönliche Unversehrtheit", levels: { "1": "keine", "2": "leichte Beeinträchtigung", "3": "Gesundheitsgefahr", "4": "Gefahr für Leib und Leben" } },
|
||||
{ name: "Aufgabenerfüllung", levels: { "1": "keine Einschränkung", "2": "geringe Einschränkung", "3": "erhebliche Einschränkung", "4": "Kernaufgaben nicht erfüllbar" } },
|
||||
{ name: "Innen-/Außenwirkung (Reputation)", levels: { "1": "keine", "2": "intern begrenzt", "3": "öffentlich wahrnehmbar", "4": "nachhaltiger Reputationsschaden" } },
|
||||
{ name: "Störungs-/Ausfallzeit", levels: { "1": "< 1 Std.", "2": "bis 1 Tag", "3": "bis 1 Woche", "4": "> 1 Woche" } },
|
||||
{ name: "Finanzielle Auswirkungen", levels: { "1": "bis 10 T€", "2": "bis 50 T€", "3": "bis 200 T€", "4": "über 500 T€" } },
|
||||
];
|
||||
|
||||
// Krypto-Register (VA-07) — Demo-Datensätze inkl. Ablaufüberwachung
|
||||
const CRYPTO = [
|
||||
{ dienst: "TLS Web-Portal (portal.gefim.example)", schluessel: "RSA 3072 · Let's Encrypt", algorithmus: "TLS 1.3 / AES-256", ablaufOffset: 45, verantwortlich: "IT-Leitung", speicherort: "Reverse Proxy", baselineRef: "BL-CRY-01" },
|
||||
{ dienst: "VPN-Gateway", schluessel: "ECDSA P-256 · interne CA", algorithmus: "IKEv2 / AES-256-GCM", ablaufOffset: 12, verantwortlich: "IT-Leitung", speicherort: "Firewall/HSM", baselineRef: "BL-CRY-02" },
|
||||
{ dienst: "Festplattenverschlüsselung Notebooks", schluessel: "BitLocker · TPM 2.0", algorithmus: "AES-256-XTS", ablaufOffset: null, verantwortlich: "IT-Betrieb", speicherort: "MDM-Recovery", baselineRef: "BL-CRY-03" },
|
||||
{ dienst: "S/MIME Zertifikate Geschäftsführung", schluessel: "RSA 2048 · öffentliche CA", algorithmus: "S/MIME", ablaufOffset: -8, verantwortlich: "IT-Leitung", speicherort: "Smartcard", baselineRef: "BL-CRY-04" },
|
||||
];
|
||||
|
||||
// Anwender-Handbuch (§9.7) — kuratierte Themen, Werte via {{VARIABLE}} synchron zur Baseline
|
||||
const HANDBOOK: { category: string; title: string; bodyMd: string; sourceRefs: string[] }[] = [
|
||||
{
|
||||
category: "Zugang & Passwörter", title: "Passwörter & Mehr-Faktor-Anmeldung",
|
||||
bodyMd: "**Das Wichtigste kurz:**\n\n- Nutze Passwörter mit **mindestens {{PW_MIN_LENGTH}} Zeichen** ({{PW_COMPLEXITY}}).\n- Aktiviere **Mehr-Faktor-Authentifizierung (MFA)** über {{TECH_MFA}} für {{MFA_SCOPE}}.\n- Gib Passwörter **nie** weiter und nutze für jeden Dienst ein eigenes.\n- Bei Verdacht auf Kompromittierung: Passwort sofort ändern und melden.",
|
||||
sourceRefs: ["R08#4.1.2"],
|
||||
},
|
||||
{
|
||||
category: "Zugang & Passwörter", title: "Zugriffsrechte – nur was du brauchst",
|
||||
bodyMd: "- Zugriff erhältst du **nach dem Minimalprinzip** (need-to-know) über {{TOOL_TICKET}}.\n- Nicht mehr benötigte Rechte werden entzogen; deine Rechte werden regelmäßig **überprüft (Rezertifizierung, {{RECERT_FREQ}})**.\n- Sperre deinen Bildschirm bei jedem Verlassen des Arbeitsplatzes.",
|
||||
sourceRefs: ["R08#4.2.1"],
|
||||
},
|
||||
{
|
||||
category: "Umgang mit Informationen", title: "Daten richtig klassifizieren & handhaben",
|
||||
bodyMd: "- Behandle Informationen gemäß ihrer **Schutzklasse** (Offen · Intern · Vertraulich · Streng vertraulich).\n- **Vertrauliche** Inhalte extern nur **verschlüsselt** versenden.\n- Dokumente nicht offen liegen lassen (Clean Desk); vertrauliche Ausdrucke sicher vernichten.",
|
||||
sourceRefs: ["R02"],
|
||||
},
|
||||
{
|
||||
category: "Sicheres Arbeiten", title: "Mobiles Arbeiten & Homeoffice",
|
||||
bodyMd: "- Nutze nur **freigegebene Geräte** (verwaltet über {{TECH_MDM}}).\n- Achte auf **Sichtschutz** in der Öffentlichkeit; keine vertraulichen Gespräche in Bahn/Café.\n- Verbinde dich extern nur über {{TECH_VPN}} mit MFA.",
|
||||
sourceRefs: ["R06"],
|
||||
},
|
||||
{
|
||||
category: "Sicheres Arbeiten", title: "E-Mail, Internet & Schadsoftware",
|
||||
bodyMd: "- Öffne **keine unerwarteten Anhänge/Links**; im Zweifel nachfragen.\n- Melde verdächtige E-Mails (Phishing) über den Meldeweg.\n- {{TECH_MALWARE}} schützt deine Geräte – deaktiviere den Schutz nie.",
|
||||
sourceRefs: ["R10", "R09"],
|
||||
},
|
||||
{
|
||||
category: "Vorfälle", title: "Sicherheitsvorfall? So meldest du ihn",
|
||||
bodyMd: "**Sofort melden** bei Verdacht auf Vorfall, Datenverlust oder verlorenem Gerät:\n\n- an die {{ROLE_ISB}} bzw. den definierten Meldeweg.\n- Lieber einmal zu viel melden – schnelle Meldung begrenzt den Schaden.\n- Nichts vertuschen, nichts eigenmächtig „reparieren“.",
|
||||
sourceRefs: ["R04"],
|
||||
},
|
||||
];
|
||||
|
||||
export async function importManaged(prisma: PrismaClient, tenantId: string) {
|
||||
// Idempotent: alte Datensätze pro Mandant entfernen
|
||||
await prisma.handlingRule.deleteMany({ where: { tenantId } });
|
||||
await prisma.handlingAspect.deleteMany({ where: { tenantId } });
|
||||
await prisma.classificationClass.deleteMany({ where: { tenantId } });
|
||||
await prisma.cryptoEntry.deleteMany({ where: { tenantId } });
|
||||
await prisma.riskMatrixClass.deleteMany({ where: { tenantId } });
|
||||
await prisma.riskEwLevel.deleteMany({ where: { tenantId } });
|
||||
await prisma.riskDamageDimension.deleteMany({ where: { tenantId } });
|
||||
await prisma.handbookTopic.deleteMany({ where: { tenantId } });
|
||||
|
||||
// Register-/Handbuch-Dokumente in der Bibliothek (interaktiv gerendert)
|
||||
const registerDocs = [
|
||||
{ code: "CRYPTO", type: "REGISTER" as const, title: "Verschlüsselungsmechanismen-Register", orderIdx: 902, intro: "Verwaltetes Register der eingesetzten Verschlüsselung (VA-07). Ablaufüberwachung aktiv." },
|
||||
{ code: "RISKMATRIX", type: "REGISTER" as const, title: "Risiko-Bewertungsmatrix", orderIdx: 903, intro: "Zentrale Bewertungsmatrix Schadensausmaß × Eintrittswahrscheinlichkeit (R03 / VA-09), Defaults nach FB-80-04." },
|
||||
{ code: "CLASSIFICATION", type: "REGISTER" as const, title: "Klassifizierung & Handhabungsmatrix", orderIdx: 904, intro: "Schutzklassen und Handhabungsregeln (R02 / VA-08), Defaults nach AA-80-20." },
|
||||
{ code: "HANDBUCH", type: "HANDBUCH" as const, title: "Anwender-Handbuch Informationssicherheit", orderIdx: 950, intro: "Kompakte Zusammenfassung der wichtigsten Regeln für Mitarbeitende – mit Deep-Links in die Quelldokumente." },
|
||||
];
|
||||
for (const d of registerDocs) {
|
||||
await prisma.policyDocument.upsert({
|
||||
where: { tenantId_code: { tenantId, code: d.code } },
|
||||
update: { title: d.title, type: d.type, orderIdx: d.orderIdx, rawMarkdown: `# ${d.title}\n\n${d.intro}` },
|
||||
create: { tenantId, code: d.code, type: d.type, title: d.title, version: "1.0", status: "FREIGEGEBEN", orderIdx: d.orderIdx, rawMarkdown: `# ${d.title}\n\n${d.intro}` },
|
||||
});
|
||||
}
|
||||
|
||||
// Krypto-Register
|
||||
for (let i = 0; i < CRYPTO.length; i++) {
|
||||
const c = CRYPTO[i];
|
||||
await prisma.cryptoEntry.create({
|
||||
data: {
|
||||
tenantId, dienst: c.dienst, schluessel: c.schluessel, algorithmus: c.algorithmus,
|
||||
ablaufdatum: c.ablaufOffset === null ? null : daysFrom(NOW, c.ablaufOffset),
|
||||
verantwortlich: c.verantwortlich, speicherort: c.speicherort, baselineRef: c.baselineRef, orderIdx: i,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Klassifizierung + Handhabungsmatrix
|
||||
const classIds: string[] = [];
|
||||
for (let i = 0; i < CLASSES.length; i++) {
|
||||
const cl = await prisma.classificationClass.create({ data: { tenantId, name: CLASSES[i].name, description: CLASSES[i].description, orderIdx: i } });
|
||||
classIds.push(cl.id);
|
||||
}
|
||||
for (let a = 0; a < ASPECTS.length; a++) {
|
||||
const asp = await prisma.handlingAspect.create({ data: { tenantId, name: ASPECTS[a].name, category: ASPECTS[a].category, orderIdx: a } });
|
||||
for (let c = 0; c < classIds.length; c++) {
|
||||
await prisma.handlingRule.create({ data: { tenantId, classId: classIds[c], aspectId: asp.id, text: ASPECTS[a].rules[c] } });
|
||||
}
|
||||
}
|
||||
|
||||
// Risiko-Bewertungsmatrix
|
||||
for (let i = 0; i < RISK_CLASSES.length; i++) {
|
||||
const r = RISK_CLASSES[i];
|
||||
await prisma.riskMatrixClass.create({ data: { tenantId, name: r.name, maxScore: r.maxScore, acceptance: r.acceptance, tone: r.tone, orderIdx: i } });
|
||||
}
|
||||
for (const ew of EW_LEVELS) await prisma.riskEwLevel.create({ data: { tenantId, level: ew.level, label: ew.label, definition: ew.definition } });
|
||||
for (let i = 0; i < DAMAGE_DIMS.length; i++) {
|
||||
await prisma.riskDamageDimension.create({ data: { tenantId, name: DAMAGE_DIMS[i].name, levels: DAMAGE_DIMS[i].levels, orderIdx: i } });
|
||||
}
|
||||
|
||||
// Anwender-Handbuch
|
||||
for (let i = 0; i < HANDBOOK.length; i++) {
|
||||
const h = HANDBOOK[i];
|
||||
await prisma.handbookTopic.create({ data: { tenantId, category: h.category, title: h.title, bodyMd: h.bodyMd, sourceRefs: h.sourceRefs, orderIdx: i } });
|
||||
}
|
||||
|
||||
return { crypto: CRYPTO.length, classes: CLASSES.length, aspects: ASPECTS.length, damage: DAMAGE_DIMS.length, handbook: HANDBOOK.length };
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "crypto_entries" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"dienst" TEXT NOT NULL,
|
||||
"schluessel" TEXT NOT NULL,
|
||||
"algorithmus" TEXT,
|
||||
"ablauf_datum" TIMESTAMP(3),
|
||||
"verantwortlich" TEXT NOT NULL,
|
||||
"speicherort" TEXT,
|
||||
"baseline_ref" TEXT,
|
||||
"notes" TEXT,
|
||||
"order_idx" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "crypto_entries_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "classification_classes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"description" TEXT,
|
||||
"order_idx" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "classification_classes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "handling_aspects" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"category" TEXT,
|
||||
"order_idx" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "handling_aspects_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "handling_rules" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"class_id" TEXT NOT NULL,
|
||||
"aspect_id" TEXT NOT NULL,
|
||||
"text" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "handling_rules_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "risk_matrix_classes" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"max_score" INTEGER NOT NULL,
|
||||
"acceptance" TEXT NOT NULL,
|
||||
"tone" TEXT NOT NULL,
|
||||
"order_idx" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "risk_matrix_classes_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "risk_ew_levels" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"level" INTEGER NOT NULL,
|
||||
"label" TEXT NOT NULL,
|
||||
"definition" TEXT NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "risk_ew_levels_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "risk_damage_dimensions" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"levels" JSONB NOT NULL,
|
||||
"order_idx" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "risk_damage_dimensions_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "handbook_topics" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"title" TEXT NOT NULL,
|
||||
"body_md" TEXT NOT NULL,
|
||||
"source_refs" TEXT[] DEFAULT ARRAY[]::TEXT[],
|
||||
"order_idx" INTEGER NOT NULL DEFAULT 0,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "handbook_topics_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "crypto_entries_tenant_id_idx" ON "crypto_entries"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "classification_classes_tenant_id_idx" ON "classification_classes"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "handling_aspects_tenant_id_idx" ON "handling_aspects"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "handling_rules_tenant_id_idx" ON "handling_rules"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "handling_rules_class_id_aspect_id_key" ON "handling_rules"("class_id", "aspect_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "risk_matrix_classes_tenant_id_idx" ON "risk_matrix_classes"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "risk_ew_levels_tenant_id_idx" ON "risk_ew_levels"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "risk_damage_dimensions_tenant_id_idx" ON "risk_damage_dimensions"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "handbook_topics_tenant_id_idx" ON "handbook_topics"("tenant_id");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "handling_rules" ADD CONSTRAINT "handling_rules_class_id_fkey" FOREIGN KEY ("class_id") REFERENCES "classification_classes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "handling_rules" ADD CONSTRAINT "handling_rules_aspect_id_fkey" FOREIGN KEY ("aspect_id") REFERENCES "handling_aspects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- RLS-Policies für die verwalteten Register-Tabellen
|
||||
DO $$
|
||||
DECLARE t text;
|
||||
BEGIN
|
||||
FOREACH t IN ARRAY ARRAY[
|
||||
'crypto_entries','classification_classes','handling_aspects','handling_rules',
|
||||
'risk_matrix_classes','risk_ew_levels','risk_damage_dimensions','handbook_topics'
|
||||
] LOOP
|
||||
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', t);
|
||||
EXECUTE format(
|
||||
'CREATE POLICY tenant_isolation ON %I USING (tenant_id = current_setting(''app.tenant_id'', true))',
|
||||
t
|
||||
);
|
||||
END LOOP;
|
||||
END $$;
|
||||
@@ -765,3 +765,124 @@ model PolicyEvidence {
|
||||
@@index([tenantId])
|
||||
@@map("policy_evidence")
|
||||
}
|
||||
|
||||
// ── Verwaltete Register-Tabellen (§7b) ───────────────────────────────────────
|
||||
|
||||
// Verschlüsselungsmechanismen-Register (VA-07 Kryptokonzept & Schlüsselverwaltung)
|
||||
model CryptoEntry {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
dienst String
|
||||
schluessel String
|
||||
algorithmus String?
|
||||
ablaufdatum DateTime? @map("ablauf_datum")
|
||||
verantwortlich String
|
||||
speicherort String?
|
||||
baselineRef String? @map("baseline_ref") // z. B. BL-CRY-02
|
||||
notes String?
|
||||
orderIdx Int @default(0) @map("order_idx")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([tenantId])
|
||||
@@map("crypto_entries")
|
||||
}
|
||||
|
||||
// Klassifizierungs-Handhabungsmatrix (R02 / VA-08) — Schutzklassen × Aspekte
|
||||
model ClassificationClass {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
name String
|
||||
description String?
|
||||
orderIdx Int @default(0) @map("order_idx")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
rules HandlingRule[]
|
||||
|
||||
@@index([tenantId])
|
||||
@@map("classification_classes")
|
||||
}
|
||||
|
||||
model HandlingAspect {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
name String
|
||||
category String?
|
||||
orderIdx Int @default(0) @map("order_idx")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
rules HandlingRule[]
|
||||
|
||||
@@index([tenantId])
|
||||
@@map("handling_aspects")
|
||||
}
|
||||
|
||||
model HandlingRule {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
classId String @map("class_id")
|
||||
aspectId String @map("aspect_id")
|
||||
text String
|
||||
|
||||
class ClassificationClass @relation(fields: [classId], references: [id], onDelete: Cascade)
|
||||
aspect HandlingAspect @relation(fields: [aspectId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([classId, aspectId])
|
||||
@@index([tenantId])
|
||||
@@map("handling_rules")
|
||||
}
|
||||
|
||||
// Risiko-Bewertungsmatrix (R03 / VA-09) — zentrale Pflegestelle (Defaults FB-80-04)
|
||||
model RiskMatrixClass {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
name String // Niedrig | Mittel | Hoch | Kritisch
|
||||
maxScore Int @map("max_score") // obere Schwelle des Risikowerts
|
||||
acceptance String // Akzeptanzinstanz
|
||||
tone String // ok | warn | orange | risk
|
||||
orderIdx Int @default(0) @map("order_idx")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([tenantId])
|
||||
@@map("risk_matrix_classes")
|
||||
}
|
||||
|
||||
model RiskEwLevel {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
level Int
|
||||
label String
|
||||
definition String
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([tenantId])
|
||||
@@map("risk_ew_levels")
|
||||
}
|
||||
|
||||
model RiskDamageDimension {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
name String
|
||||
levels Json // { "1": "…", "2": "…", "3": "…", "4": "…" }
|
||||
orderIdx Int @default(0) @map("order_idx")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([tenantId])
|
||||
@@map("risk_damage_dimensions")
|
||||
}
|
||||
|
||||
// Anwender-Handbuch (§9.7) — kuratierte Themen mit Deep-Links; Werte via Templating
|
||||
model HandbookTopic {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
category String
|
||||
title String
|
||||
bodyMd String @map("body_md") // Markdown mit {{VARIABLE}} (bleibt via Baseline synchron)
|
||||
sourceRefs String[] @default([]) @map("source_refs") // Deep-Links, z. B. R08#4.1.2
|
||||
orderIdx Int @default(0) @map("order_idx")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@index([tenantId])
|
||||
@@map("handbook_topics")
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { hash } from "@node-rs/argon2";
|
||||
import { join } from "node:path";
|
||||
import { PERMISSIONS, ROLE_DEFS } from "../src/server/rbac";
|
||||
import { importPolicies } from "./import-policies";
|
||||
import { importManaged } from "./import-managed";
|
||||
|
||||
/**
|
||||
* Seed: global permission catalog + demo tenant with roles and users.
|
||||
@@ -522,6 +523,9 @@ async function main() {
|
||||
await prisma.policyVariable.updateMany({ where: { tenantId: tenant.id, key }, data: { value } });
|
||||
}
|
||||
console.log(`✔ Richtlinienpaket importiert: ${pc.documents} Dokumente, ${pc.requirements} Anforderungen, ${pc.variables} Variablen, ${pc.baseline} Baseline-Parameter`);
|
||||
|
||||
const mc = await importManaged(prisma, tenant.id);
|
||||
console.log(`✔ Verwaltete Register: Krypto ${mc.crypto}, Klassifizierung ${mc.classes}×${mc.aspects}, Risikomatrix (${mc.damage} Schadensdim.), Handbuch ${mc.handbook} Themen`);
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user