Files
certvia/prisma/seed.ts
T
MartinandClaude Fable 5 df634e356d Iteration 3: Risikoanalyse + Menü-Trennung + Prozesskategorie
- Risiko-Modul (SPEC §4.2): Risk/RiskAsset-Schema mit RLS, laufende
  Nummer je Mandant (R-001), Brutto-/Rest-Bewertung (5×5), Behandlung,
  Status, Owner- und Prozess-Bezug, n:m betroffene Assets
- /risks im Mockup-Layout: 5×5-Heatmap (Farb-Bänder + R-Chips, Legende,
  Achsen) und Risikoregister mit Score-Pillen, Behandlungs-Tags, Status
- Risiko-Popups nach App-Muster: Read-only-Detail (Bewertungs-Band
  Brutto vs. Rest, betroffene Assets, Maßnahmen-Platzhalter für
  Iteration 4), Bearbeiten im Popup (Bewertung, Assets verknüpfen,
  Löschen), Anlegen; Server-Actions mit Zod, RBAC und Audit-Log
- Menü aufgetrennt: eigene Punkte "Asset-Inventar", "Business Impact
  Analyse" und "Risikoanalyse" (Modul-Tabs entfernt)
- Prozesskategorie (Kernprozess/Managementprozess/Unterstützender
  Prozess): Schema-Feld, Formulare, Listen-Spalte, Popup-Tag
- Asset-Detail-Popup optisch ans Prozess-Popup angeglichen (Stammdaten-
  Karte mit violettem Rand, Abhängigkeiten-Tabelle, Risiken-Band)
- "Zugeordnete Risiken" jetzt echt: im Asset-Popup (verknüpfte Risiken)
  und im Prozess-Popup (direkt + über Assets aggregiert); Dashboard-KPI
  "Offene Risiken" mit echten Zahlen; Seed mit 4 Beispiel-Risiken

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 15:43:25 +02:00

315 lines
11 KiB
TypeScript

import "dotenv/config";
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import { hash } from "@node-rs/argon2";
import { PERMISSIONS, ROLE_DEFS } from "../src/server/rbac";
/**
* Seed: global permission catalog + demo tenant with roles and users.
* Idempotent (upserts) — safe to run repeatedly.
*/
const prisma = new PrismaClient({
adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }),
});
const DEMO_PASSWORD = "Demo1234!";
async function main() {
// 1. Global permission catalog
for (const key of PERMISSIONS) {
await prisma.permission.upsert({ where: { key }, update: {}, create: { key } });
}
console.log(`✔ ${PERMISSIONS.length} Permissions`);
// 2. Demo tenant
const tenant = await prisma.tenant.upsert({
where: { slug: "demo" },
update: {},
create: { name: "Demo GmbH", slug: "demo" },
});
// 3. Roles from blueprints, with permission bundles
for (const [key, def] of Object.entries(ROLE_DEFS)) {
const role = await prisma.role.upsert({
where: { tenantId_key: { tenantId: tenant.id, key } },
update: { name: def.name },
create: { tenantId: tenant.id, key, name: def.name },
});
const perms = await prisma.permission.findMany({
where: { key: { in: [...def.permissions] } },
});
for (const p of perms) {
await prisma.rolePermission.upsert({
where: { roleId_permissionId: { roleId: role.id, permissionId: p.id } },
update: {},
create: { roleId: role.id, permissionId: p.id },
});
}
}
console.log(`✔ Mandant "demo" mit ${Object.keys(ROLE_DEFS).length} Rollen`);
// 4. Demo users
const passwordHash = await hash(DEMO_PASSWORD);
const demoUsers: { email: string; name: string; roles: string[] }[] = [
{ email: "admin@demo.example", name: "Anna Admin", roles: ["tenant-admin", "isb"] },
{ email: "auditor@demo.example", name: "Axel Auditor", roles: ["auditor"] },
{ email: "owner@demo.example", name: "Oskar Owner", roles: ["owner"] },
{ email: "user@demo.example", name: "Ulla User", roles: ["user"] },
];
for (const u of demoUsers) {
const user = await prisma.user.upsert({
where: { tenantId_email: { tenantId: tenant.id, email: u.email } },
update: {},
create: {
tenantId: tenant.id,
email: u.email,
name: u.name,
passwordHash,
},
});
const roles = await prisma.role.findMany({
where: { tenantId: tenant.id, key: { in: u.roles } },
});
for (const r of roles) {
await prisma.userRole.upsert({
where: { userId_roleId: { userId: user.id, roleId: r.id } },
update: {},
create: { userId: user.id, roleId: r.id },
});
}
}
console.log(`✔ ${demoUsers.length} Demo-Nutzer (Passwort: ${DEMO_PASSWORD})`);
// 5. Beispiel-Assets, -Prozesse und BIA (nur wenn noch keine Assets existieren)
const assetCount = await prisma.asset.count({ where: { tenantId: tenant.id } });
if (assetCount === 0) {
const isb = await prisma.user.findUniqueOrThrow({
where: { tenantId_email: { tenantId: tenant.id, email: "admin@demo.example" } },
});
const owner = await prisma.user.findUniqueOrThrow({
where: { tenantId_email: { tenantId: tenant.id, email: "owner@demo.example" } },
});
const mk = (data: {
name: string;
type: "INFORMATION" | "SYSTEM" | "APPLICATION" | "LOCATION" | "SUPPLIER" | "PERSON" | "DATA";
c: number;
i: number;
a: number;
ownerId?: string;
location?: string;
tags?: string[];
description?: string;
}) =>
prisma.asset.create({
data: {
tenantId: tenant.id,
name: data.name,
type: data.type,
confidentiality: data.c,
integrity: data.i,
availability: data.a,
ownerId: data.ownerId,
location: data.location,
tags: data.tags ?? [],
description: data.description,
},
});
const erp = await mk({ name: "ERP-System", type: "SYSTEM", c: 3, i: 4, a: 3, ownerId: owner.id, location: "RZ Frankfurt", tags: ["kritisch", "kern"] });
const crm = await mk({ name: "Kundendatenbank", type: "DATA", c: 4, i: 3, a: 2, ownerId: isb.id, tags: ["dsgvo"] });
const auftraege = await mk({ name: "Auftragsdaten", type: "INFORMATION", c: 3, i: 4, a: 3, ownerId: owner.id });
const hoster = await mk({ name: "Cloud-Hoster (IaaS)", type: "SUPPLIER", c: 2, i: 3, a: 4, description: "Betreibt das Rechenzentrum für ERP und CRM." });
const buero = await mk({ name: "Bürostandort München", type: "LOCATION", c: 2, i: 2, a: 2 });
const itTeam = await mk({ name: "IT-Administration", type: "PERSON", c: 3, i: 3, a: 3 });
await prisma.assetRelation.createMany({
data: [
{ tenantId: tenant.id, assetId: erp.id, relatedAssetId: hoster.id, type: "depends_on" },
{ tenantId: tenant.id, assetId: crm.id, relatedAssetId: hoster.id, type: "depends_on" },
{ tenantId: tenant.id, assetId: erp.id, relatedAssetId: itTeam.id, type: "depends_on" },
],
});
const auftrag = await prisma.process.create({
data: {
tenantId: tenant.id,
name: "Auftragsabwicklung",
description: "Vom Kundenauftrag bis zur Auslieferung.",
category: "CORE",
ownerId: owner.id,
},
});
await prisma.processAsset.createMany({
data: [
{ tenantId: tenant.id, processId: auftrag.id, assetId: auftraege.id, role: "PRIMARY" },
{ tenantId: tenant.id, processId: auftrag.id, assetId: erp.id, role: "SECONDARY" },
{ tenantId: tenant.id, processId: auftrag.id, assetId: crm.id, role: "SECONDARY" },
{ tenantId: tenant.id, processId: auftrag.id, assetId: itTeam.id, role: "SECONDARY" },
],
});
await prisma.biaEntry.create({
data: {
tenantId: tenant.id,
processId: auftrag.id,
rtoHours: 8,
rpoHours: 4,
mtdHours: 24,
impactC: 2,
impactI: 4,
impactA: 3,
criticality: 4,
notes: "Ausfall > 1 Tag führt zu Lieferverzug und Vertragsstrafen.",
},
});
const vertrieb = await prisma.process.create({
data: {
tenantId: tenant.id,
name: "Kundenbetreuung",
description: "Anfragen, Angebote, Support.",
category: "SUPPORT",
ownerId: isb.id,
},
});
await prisma.processAsset.createMany({
data: [
{ tenantId: tenant.id, processId: vertrieb.id, assetId: crm.id, role: "PRIMARY" },
{ tenantId: tenant.id, processId: vertrieb.id, assetId: buero.id, role: "SECONDARY" },
],
});
await prisma.biaEntry.create({
data: {
tenantId: tenant.id,
processId: vertrieb.id,
rtoHours: 24,
rpoHours: 24,
mtdHours: 72,
impactC: 3,
impactI: 2,
impactA: 2,
criticality: 3,
notes: "Kundenkommunikation kann kurzfristig über Ausweichkanäle laufen.",
},
});
console.log("✔ Beispiel-Assets, -Prozesse und BIA angelegt");
}
// 6. Beispiel-Risiken (nur wenn noch keine existieren)
const riskCount = await prisma.risk.count({ where: { tenantId: tenant.id } });
if (riskCount === 0) {
const isb = await prisma.user.findUniqueOrThrow({
where: { tenantId_email: { tenantId: tenant.id, email: "admin@demo.example" } },
});
const byName = async (name: string) =>
prisma.asset.findFirstOrThrow({ where: { tenantId: tenant.id, name } });
const erp = await byName("ERP-System");
const crm = await byName("Kundendatenbank");
const hoster = await byName("Cloud-Hoster (IaaS)");
const auftrag = await prisma.process.findFirstOrThrow({
where: { tenantId: tenant.id, name: "Auftragsabwicklung" },
});
const risks: {
title: string;
threat: string;
vulnerability: string;
likelihood: number;
impact: number;
residual?: [number, number];
treatment: "AVOID" | "MITIGATE" | "TRANSFER" | "ACCEPT";
status: "OPEN" | "IN_TREATMENT" | "ACCEPTED" | "CLOSED";
assets: string[];
processId?: string;
description?: string;
}[] = [
{
title: "Ransomware auf ERP",
threat: "Schadsoftware / Verschlüsselungstrojaner",
vulnerability: "Fehlende Offline-Backups, Makro-Ausführung erlaubt",
likelihood: 4,
impact: 5,
residual: [2, 4],
treatment: "MITIGATE",
status: "IN_TREATMENT",
assets: [erp.id, crm.id],
processId: auftrag.id,
description: "Verschlüsselung der ERP-Datenbank würde die Auftragsabwicklung stoppen.",
},
{
title: "Ausfall Cloud-Hoster",
threat: "Ausfall des Rechenzentrums / Insolvenz Dienstleister",
vulnerability: "Kein Ausweich-Standort, Single Provider",
likelihood: 4,
impact: 4,
treatment: "TRANSFER",
status: "OPEN",
assets: [hoster.id, erp.id],
processId: auftrag.id,
},
{
title: "Phishing / Social Engineering",
threat: "Gezielte Phishing-Kampagnen",
vulnerability: "Fehlende Awareness-Schulungen",
likelihood: 3,
impact: 3,
residual: [2, 3],
treatment: "MITIGATE",
status: "IN_TREATMENT",
assets: [crm.id],
},
{
title: "Verlust mobiler Geräte",
threat: "Diebstahl/Verlust von Notebooks",
vulnerability: "Unvollständige Festplattenverschlüsselung",
likelihood: 2,
impact: 3,
treatment: "ACCEPT",
status: "ACCEPTED",
assets: [],
},
];
let refNo = 1;
for (const r of risks) {
const risk = await prisma.risk.create({
data: {
tenantId: tenant.id,
refNo: refNo++,
title: r.title,
description: r.description,
threat: r.threat,
vulnerability: r.vulnerability,
likelihood: r.likelihood,
impact: r.impact,
score: r.likelihood * r.impact,
residualLikelihood: r.residual?.[0],
residualImpact: r.residual?.[1],
residualScore: r.residual ? r.residual[0] * r.residual[1] : undefined,
treatment: r.treatment,
status: r.status,
ownerId: isb.id,
processId: r.processId,
},
});
for (const assetId of r.assets) {
await prisma.riskAsset.create({
data: { tenantId: tenant.id, riskId: risk.id, assetId },
});
}
}
console.log(`✔ ${risks.length} Beispiel-Risiken angelegt`);
}
}
main()
.then(() => prisma.$disconnect())
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});