Files
craftvia/prisma/seed.ts
T
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

740 lines
32 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import "dotenv/config";
import { PrismaClient } from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import { hashPassword } from "../src/server/password";
import { join } from "node:path";
import { PERMISSIONS, ROLE_DEFS } from "../src/server/rbac";
import { importPolicies, stampPackageState } from "./import-policies";
import { importImplementationHints } from "./import-hints";
import { importRiskCatalog } from "./import-risks";
import { seedGlobalContent } from "./seed-content";
import { importManaged } from "./import-managed";
import { MODULE_KEYS } from "../src/lib/modules";
import { normalizeAssetName } from "../src/lib/normalize-asset";
import { provisionTenant } from "../src/server/provision";
/**
* 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!";
const THREATS = [
"Schadsoftware / Ransomware",
"Phishing / Social Engineering",
"Innentäter / Missbrauch von Berechtigungen",
"Diebstahl oder Verlust von Geräten",
"Ausfall von IT-Systemen oder Diensten",
"Ausfall eines Dienstleisters / Lieferanten",
"Stromausfall / Infrastrukturausfall",
"Feuer / Wasser / Elementarschäden",
"Unbefugter physischer Zutritt",
"Denial-of-Service-Angriff",
"Datenabfluss / Industriespionage",
"Fehlbedienung durch Mitarbeitende",
"Softwarefehler / fehlerhafte Updates",
"Kompromittierte Zugangsdaten",
"Rechtliche / regulatorische Verstöße",
];
const VULNERABILITIES = [
"Fehlende oder veraltete Backups",
"Fehlende Netzwerksegmentierung",
"Unzureichendes Patch-Management",
"Schwache oder wiederverwendete Passwörter",
"Fehlende Multi-Faktor-Authentifizierung",
"Übermäßige Berechtigungen / fehlendes Least-Privilege",
"Fehlende Awareness / Schulungen",
"Unverschlüsselte Datenträger oder Übertragungen",
"Kein Notfallkonzept / ungetesteter Wiederanlauf",
"Single Point of Failure (Technik oder Person)",
"Unzureichende Protokollierung / Überwachung",
"Veraltete oder nicht mehr unterstützte Software",
"Fehlende Vertrags-/AV-Regelungen mit Dienstleistern",
"Offene, ungenutzte Dienste und Ports",
"Unklare Verantwortlichkeiten",
];
async function main() {
// 0. Globale Kataloge Bedrohungen/Schwachstellen (Vorschläge, Freitext bleibt möglich)
for (const name of THREATS) {
await prisma.threat.upsert({ where: { name }, update: {}, create: { name } });
}
for (const name of VULNERABILITIES) {
await prisma.vulnerability.upsert({ where: { name }, update: {}, create: { name } });
}
console.log(`✔ Kataloge: ${THREATS.length} Bedrohungen, ${VULNERABILITIES.length} Schwachstellen`);
// 1. Global permission catalog
for (const key of PERMISSIONS) {
await prisma.permission.upsert({ where: { key }, update: {}, create: { key } });
}
console.log(`✔ ${PERMISSIONS.length} Permissions`);
// 1b. Globaler Content (M2/M3): Control→Domain/RACI-Defaults + Prozess-Katalog.
// Ausgelagert in den prod-tauglichen, idempotenten Content-Seed (seed-content.ts),
// damit DevOps ihn auch ohne Demo-Seed in jeder Umgebung fahren kann (npm run seed:content).
const globalContent = await seedGlobalContent(prisma);
console.log(`✔ ${globalContent.controlDomains} Control→Domain-Defaults (M3)`);
// 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 hashPassword(DEMO_PASSWORD);
const demoUsers: { email: string; name: string; roles: string[] }[] = [
{ email: "admin@demo.example", name: "Anna Admin", roles: ["tenant-admin", "isb"] },
// Zweiter ISB als Freigeber — ermöglicht den Vier-Augen-Freigabe-Workflow (≠ Einreicher).
{ email: "bea.approver@demo.example", name: "Bea Approver", roles: ["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) {
// Option C: globale Identity (Anmeldung) + Mitgliedschaft (User) im Mandanten.
// Diese 5 Standard-Logins bleiben bewusst SINGLE-Membership, damit der
// (bis WS1) einstufige Login-Lookup (auth.ts: findMany by email) eindeutig
// bleibt und dev lauffähig ist. Der Multi-Membership-Fall wird über einen
// separaten Fixture-Nutzer (multi@demo.example) abgebildet (siehe unten).
const idn = await prisma.identity.upsert({
where: { email: u.email },
update: {},
create: { email: u.email, passwordHash },
});
const user = await prisma.user.upsert({
where: { tenantId_email: { tenantId: tenant.id, email: u.email } },
update: { identityId: idn.id },
create: {
tenantId: tenant.id,
identityId: idn.id,
email: u.email,
name: u.name,
},
});
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})`);
// 4b. Zweiter Mandant + Multi-Membership-Fixture (Option C, WS0/WS2/WS7).
// Eine Identity (multi@demo.example) ist Mitglied in ZWEI Mandanten mit je
// unterschiedlichen Rollen — Grundlage für Tenant-Switch- und Isolationstests.
// `demo2` wird schlank provisioniert (Rollen/Module/Einstellungen, ohne
// Richtlinienpaket). Berührt die 5 Standard-Logins nicht.
const tenant2 = await provisionTenant(prisma, {
name: "Demo Zwei GmbH",
slug: "demo2",
short: "Demo2",
sector: "Automotive",
admin: { email: "admin2@demo.example", name: "Zoe Zweitadmin", password: DEMO_PASSWORD },
});
const multiIdentity = await prisma.identity.upsert({
where: { email: "multi@demo.example" },
update: {},
create: { email: "multi@demo.example", passwordHash },
});
// Mitgliedschaft je Mandant mit je eigener Rolle (Rechte je aktivem Mandant verschieden).
const multiMemberships: { tenantId: string; roleKey: string }[] = [
{ tenantId: tenant.id, roleKey: "user" }, // in "demo": nur Standardnutzer
{ tenantId: tenant2.id, roleKey: "tenant-admin" }, // in "demo2": Mandanten-Admin
];
for (const m of multiMemberships) {
const membership = await prisma.user.upsert({
where: { tenantId_email: { tenantId: m.tenantId, email: "multi@demo.example" } },
update: { identityId: multiIdentity.id },
create: {
tenantId: m.tenantId,
identityId: multiIdentity.id,
email: "multi@demo.example",
name: "Mika Multi",
},
});
const role = await prisma.role.findFirst({ where: { tenantId: m.tenantId, key: m.roleKey } });
if (role) {
await prisma.userRole.upsert({
where: { userId_roleId: { userId: membership.id, roleId: role.id } },
update: {},
create: { userId: membership.id, roleId: role.id },
});
}
}
console.log(`✔ Multi-Membership-Fixture multi@demo.example (Mandanten: demo, demo2)`);
// 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,
// M2: Dedup-Schlüssel setzen (nur INFORMATION/DATA sind primäre Werte;
// für die übrigen Typen dient er nur der Exakt-Duplikat-Vermeidung).
normalizedName: normalizeAssetName(data.name),
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`);
}
// 7. Beispiel-Maßnahmen (nur wenn noch keine existieren) — bestimmen das Rest-Risiko
const measureCount = await prisma.measure.count({ where: { tenantId: tenant.id } });
if (measureCount === 0) {
const owner = await prisma.user.findUniqueOrThrow({
where: { tenantId_email: { tenantId: tenant.id, email: "owner@demo.example" } },
});
const riskByTitle = (title: string) =>
prisma.risk.findFirst({ where: { tenantId: tenant.id, title } });
const measures: {
title: string;
status: "OPEN" | "IN_PROGRESS" | "DONE";
priority: "LOW" | "MEDIUM" | "HIGH";
dueInDays?: number;
riskTitle?: string;
reduction?: [number, number]; // [Wahrscheinlichkeit, Schaden]
}[] = [
{
title: "Offline-Backups einführen (3-2-1-Regel)",
status: "IN_PROGRESS",
priority: "HIGH",
dueInDays: 14,
riskTitle: "Ransomware auf ERP",
reduction: [1, 1],
},
{
title: "Makro-Ausführung per GPO einschränken",
status: "OPEN",
priority: "HIGH",
dueInDays: 30,
riskTitle: "Ransomware auf ERP",
reduction: [1, 0],
},
{
title: "Awareness-Schulung Phishing (alle Mitarbeitenden)",
status: "IN_PROGRESS",
priority: "MEDIUM",
dueInDays: 45,
riskTitle: "Phishing / Social Engineering",
reduction: [1, 0],
},
{
title: "Notfallhandbuch aktualisieren",
status: "DONE",
priority: "MEDIUM",
},
];
let mRef = 1;
for (const m of measures) {
const measure = await prisma.measure.create({
data: {
tenantId: tenant.id,
refNo: mRef++,
title: m.title,
status: m.status,
priority: m.priority,
ownerId: owner.id,
dueDate: m.dueInDays
? new Date(Date.now() + m.dueInDays * 24 * 3600 * 1000)
: undefined,
},
});
if (m.riskTitle && m.reduction) {
const risk = await riskByTitle(m.riskTitle);
if (risk) {
await prisma.riskMeasure.create({
data: {
tenantId: tenant.id,
riskId: risk.id,
measureId: measure.id,
reductionLikelihood: m.reduction[0],
reductionImpact: m.reduction[1],
},
});
}
}
}
// Rest-Risiken aus den Maßnahmen ableiten (manuelle Alt-Werte ersetzen)
const allRisks = await prisma.risk.findMany({
where: { tenantId: tenant.id },
include: { riskMeasures: true },
});
for (const r of allRisks) {
if (r.riskMeasures.length === 0) {
await prisma.risk.update({
where: { id: r.id },
data: { residualLikelihood: null, residualImpact: null, residualScore: null },
});
} else {
const redL = r.riskMeasures.reduce((s, x) => s + x.reductionLikelihood, 0);
const redI = r.riskMeasures.reduce((s, x) => s + x.reductionImpact, 0);
const rl = Math.max(1, r.likelihood - redL);
const ri = Math.max(1, r.impact - redI);
await prisma.risk.update({
where: { id: r.id },
data: { residualLikelihood: rl, residualImpact: ri, residualScore: rl * ri },
});
}
}
console.log(`✔ ${measures.length} Beispiel-Maßnahmen angelegt, Rest-Risiken berechnet`);
}
// 8. Demo-Lieferanten als Assets (type SUPPLIER) mit Profil, Nachweisen, Verträgen
const supCount = await prisma.supplierProfile.count({ where: { tenantId: tenant.id } });
if (supCount === 0) {
const hoster = await prisma.asset.findFirst({ where: { tenantId: tenant.id, name: "Cloud-Hoster (IaaS)" } });
const a1 = await prisma.asset.create({
data: { tenantId: tenant.id, name: "Cloud-Hoster GmbH", type: "SUPPLIER", confidentiality: 3, integrity: 3, availability: 4 },
});
await prisma.supplierProfile.create({
data: { tenantId: tenant.id, assetId: a1.id, refNo: 1, sector: "IT-Dienstleistung / IaaS", serviceDesc: "Rechenzentrum, Virtualisierung, Backup", criticality: 4, dataCategories: ["Kundendaten", "Auftragsdaten"], nis2Relevant: true, lifecycle: "ACTIVE", contact: "security@cloud-hoster.example", nextReview: new Date(Date.now() + 90 * 24 * 3600 * 1000) },
});
if (hoster) await prisma.assetRelation.create({ data: { tenantId: tenant.id, assetId: a1.id, relatedAssetId: hoster.id, type: "provides" } }).catch(() => {});
await prisma.supplierEvidence.create({ data: { tenantId: tenant.id, assetId: a1.id, kind: "TISAX_LABEL", name: "TISAX AL3 (info high)", protectsCia: "C,I,A", validTo: new Date(Date.now() + 200 * 24 * 3600 * 1000), adequacyChecked: true } });
await prisma.contract.create({ data: { tenantId: tenant.id, assetId: a1.id, type: "av_dpa", avDpa: true, securityClauses: true, flowdown: true, customerRequirementsPassed: true, validTo: new Date(Date.now() + 400 * 24 * 3600 * 1000), reference: "AV-2025-014" } });
await prisma.nda.create({ data: { tenantId: tenant.id, assetId: a1.id, subject: "Betrieb der ERP-/CRM-Infrastruktur", parties: "Demo GmbH / Cloud-Hoster GmbH", validTo: new Date(Date.now() + 60 * 24 * 3600 * 1000), extensionStatus: "offen" } });
await prisma.supplierAssessment.create({ data: { tenantId: tenant.id, assetId: a1.id, type: "SELF_ASSESSMENT", score: 82, date: new Date(Date.now() - 120 * 24 * 3600 * 1000), nextReview: new Date(Date.now() + 245 * 24 * 3600 * 1000), result: "angemessen" } });
const a2 = await prisma.asset.create({
data: { tenantId: tenant.id, name: "WebAgentur X", type: "SUPPLIER", confidentiality: 2, integrity: 2, availability: 2 },
});
await prisma.supplierProfile.create({
data: { tenantId: tenant.id, assetId: a2.id, refNo: 2, sector: "Webentwicklung", serviceDesc: "Website-Betrieb", criticality: 2, dataCategories: ["Marketingdaten"], nis2Relevant: false, lifecycle: "UNDER_REVIEW" },
});
console.log("✔ 2 Demo-Lieferanten als Assets angelegt");
}
// 9. Demo-IT-Service (type IT_SERVICE) mit RACI-Matrix über den ISA-Katalog
const svcCount = await prisma.iTServiceProfile.count({ where: { tenantId: tenant.id } });
if (svcCount === 0) {
const provider = await prisma.asset.findFirst({ where: { tenantId: tenant.id, name: "Cloud-Hoster GmbH", type: "SUPPLIER" } });
const svc = await prisma.asset.create({
data: { tenantId: tenant.id, name: "Managed ERP-Hosting", type: "IT_SERVICE", confidentiality: 3, integrity: 3, availability: 4 },
});
await prisma.iTServiceProfile.create({
data: { tenantId: tenant.id, assetId: svc.id, refNo: 1, criticality: 4, internal: false, providerAssetId: provider?.id ?? null, notes: "Betrieb & Wartung der ERP-Plattform beim Cloud-Hoster (Shared Responsibility)." },
});
const raci: { controlRef: string; title: string; responsibility: "PROVIDER" | "US" | "SHARED"; evidenceRef?: string }[] = [
{ controlRef: "4.1.2", title: "Authentisierung / MFA", responsibility: "SHARED", evidenceRef: "IAM-Konzept v2" },
{ controlRef: "5.2.4", title: "Schwachstellen-/Patch-Management", responsibility: "PROVIDER", evidenceRef: "Patch-SLA" },
{ controlRef: "5.2.5", title: "Datensicherung / Backup", responsibility: "PROVIDER", evidenceRef: "Backup-Report Q2" },
{ controlRef: "4.1.3", title: "Zugriffsrechte-Verwaltung", responsibility: "US" },
{ controlRef: "1.6.1", title: "Informationssicherheits-Vorfälle", responsibility: "SHARED", evidenceRef: "IR-Runbook" },
];
for (const r of raci) {
await prisma.serviceControlResponsibility.create({
data: { tenantId: tenant.id, assetId: svc.id, controlRef: r.controlRef, title: r.title, applicable: true, responsibility: r.responsibility, evidenceRef: r.evidenceRef ?? null },
});
}
console.log("✔ 1 Demo-IT-Service mit RACI-Matrix angelegt");
}
// 9b. Demo-Vorfälle (Modul „Vorfälle", IM-A) — 1–2 Beispiele im Demo-Mandanten
const incidentCount = await prisma.incident.count({ where: { tenantId: tenant.id } });
if (incidentCount === 0) {
const isb = await prisma.user.findUnique({
where: { tenantId_email: { tenantId: tenant.id, email: "admin@demo.example" } },
});
const owner = await prisma.user.findUnique({
where: { tenantId_email: { tenantId: tenant.id, email: "owner@demo.example" } },
});
const crm = await prisma.asset.findFirst({ where: { tenantId: tenant.id, name: "Kundendatenbank" } });
const erp = await prisma.asset.findFirst({ where: { tenantId: tenant.id, name: "ERP-System" } });
const vertrieb = await prisma.process.findFirst({ where: { tenantId: tenant.id, name: "Kundenbetreuung" } });
const year = new Date().getFullYear();
// Vorfall 1: Phishing (mittlerer Schweregrad), in Bearbeitung, mit betroffenem Asset/Prozess.
const inc1 = await prisma.incident.create({
data: {
tenantId: tenant.id,
refNo: `INC-${year}-0001`,
title: "Phishing-Welle gegen Vertriebsmitarbeitende",
description: "Mehrere Mitarbeitende meldeten gefälschte E-Mails mit Login-Aufforderung. Ein Konto könnte kompromittiert sein.",
source: "manual",
reporterName: "Helpdesk",
reporterContact: "helpdesk@demo.example",
detectedAt: new Date(Date.now() - 2 * 24 * 3600 * 1000),
occurredAt: new Date(Date.now() - 3 * 24 * 3600 * 1000),
reportedAt: new Date(Date.now() - 2 * 24 * 3600 * 1000),
category: "phishing",
impactC: 3,
impactI: 1,
impactA: 0,
urgency: 3,
severity: "hoch",
priority: "hoch",
personalData: true,
dsgvoRelevant: true,
nis2Relevant: false,
status: "in_bearbeitung",
ownerId: isb?.id ?? null,
assigneeId: owner?.id ?? null,
createdBy: isb?.id ?? null,
},
});
if (crm) await prisma.incidentAsset.create({ data: { tenantId: tenant.id, incidentId: inc1.id, assetId: crm.id } });
if (vertrieb) await prisma.incidentProcess.create({ data: { tenantId: tenant.id, incidentId: inc1.id, processId: vertrieb.id } });
await prisma.incidentComment.create({
data: { tenantId: tenant.id, incidentId: inc1.id, authorId: isb?.id ?? null, body: "Betroffene Konten gesperrt, Passwort-Reset veranlasst.", internal: false },
});
// Vorfall 2: Kurzer ERP-Ausfall, bereits abgeschlossen (mit Ursache/Lösung/Lessons Learned).
const inc2 = await prisma.incident.create({
data: {
tenantId: tenant.id,
refNo: `INC-${year}-0002`,
title: "Kurzzeitiger Ausfall des ERP-Systems",
description: "Das ERP-System war rund 40 Minuten nicht erreichbar (Speicherengpass auf dem Applikationsserver).",
source: "manual",
reporterName: "IT-Betrieb",
category: "outage",
impactC: 0,
impactI: 0,
impactA: 3,
urgency: 2,
severity: "mittel",
priority: "mittel",
status: "abgeschlossen",
rootCause: "Fehlende Überwachung des Speicherverbrauchs führte zu einem OOM-Neustart.",
resolution: "Dienst neu gestartet, Speicherlimits angehoben.",
closingNote: "Betrieb nach 40 Minuten wiederhergestellt, kein Datenverlust.",
lessonsLearned: "Monitoring der Ressourcen-Auslastung mit Schwellwert-Alarm einrichten.",
ownerId: isb?.id ?? null,
createdBy: isb?.id ?? null,
},
});
if (erp) await prisma.incidentAsset.create({ data: { tenantId: tenant.id, incidentId: inc2.id, assetId: erp.id } });
console.log("✔ 2 Demo-Vorfälle angelegt");
}
// 10. Richtlinien & Verfahren aus dem VDA-ISA-2027-Vorlagenpaket importieren
// AP1: Framework-Zuordnung des Demo-Mandanten (er wird direkt per upsert angelegt,
// nicht über provisionTenant) — TISAX als Primär-Framework.
await prisma.tenantFramework.upsert({
where: { tenantId_framework: { tenantId: tenant.id, framework: "TISAX" } },
update: {},
create: { tenantId: tenant.id, framework: "TISAX", isPrimary: true },
});
const seedDir = join(__dirname, "..", "seed", "isms-vorlagenpaket-v2");
const pc = await importPolicies(prisma, tenant.id, seedDir);
await stampPackageState(prisma, tenant.id, seedDir); // Story B6: Paket-Version stempeln
// Umsetzungshinweise (C6, Story B5) — globaler Katalog, einmalig/idempotent.
const hintCount = await importImplementationHints(prisma);
// Standard-Risikokatalog (C4, Story A6) — globaler Katalog, einmalig/idempotent.
const riskCatCount = await importRiskCatalog(prisma);
// Standard-Prozess-Katalog (M2) wird bereits oben über seedGlobalContent geseedet.
const processCatCount = globalContent.processCatalog;
// Demo-Werte für Variablen ohne Schema-Default (damit die Doku vollständig wirkt)
const demoVars: Record<string, string> = {
ORG_NAME: "GEFIM Demo GmbH",
ORG_SHORT: "GEFIM",
ISMS_SCOPE: "IT-Betrieb & Softwareentwicklung",
ISMS_SCOPE_DESCRIPTION: "IT-Betrieb, Softwareentwicklung und zugehörige Unterstützungsprozesse am Standort Zentrale",
DOC_DATE: "2026-07-07",
};
for (const [key, value] of Object.entries(demoVars)) {
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`);
console.log(`✔ Umsetzungshinweise (C6) importiert: ${hintCount} Hinweise`);
console.log(`✔ Risikokatalog (C4) importiert: ${riskCatCount} Katalog-Risiken`);
console.log(`✔ Prozess-Katalog (M2) importiert: ${processCatCount} Standard-Prozesse`);
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`);
// 11. Admin-Konsole: Mandanten-Einstellungen, Module, Plattform-Admin (getrennter Store)
// Demo-Plattform-Admin: gleiche Adresse wie der Mandanten-Admin, damit /login (Mandant)
// und /platform/login (Betrieb) mit denselben Demo-Zugangsdaten funktionieren. MFA wird
// beim ersten Plattform-Login eingerichtet.
await prisma.platformAdmin.upsert({
where: { email: "admin@demo.example" },
update: { name: "Anna Admin", status: "ACTIVE" },
create: { email: "admin@demo.example", name: "Anna Admin", passwordHash: await hashPassword("Demo1234!"), status: "ACTIVE" },
});
await prisma.tenantSettings.upsert({
where: { tenantId: tenant.id },
update: {},
create: {
tenantId: tenant.id,
orgName: "GEFIM Demo GmbH",
orgShort: "GEFIM",
sector: "IT-Dienstleistung",
ismsScope: "IT-Betrieb & Softwareentwicklung",
ismsScopeDescription: "IT-Betrieb, Softwareentwicklung und zugehörige Unterstützungsprozesse am Standort Zentrale",
roleManagement: "Geschäftsführung",
roleIsb: "Informationssicherheitsbeauftragte(r) (ISB)",
roleItLead: "IT-Leitung",
roleDpo: "Datenschutzbeauftragte(r) (DSB)",
tisaxLevel: "AL2",
},
});
for (const key of MODULE_KEYS) {
await prisma.tenantModule.upsert({
where: { tenantId_moduleKey: { tenantId: tenant.id, moduleKey: key } },
update: {},
create: { tenantId: tenant.id, moduleKey: key, enabled: true },
});
}
console.log(`✔ Admin-Konsole: Einstellungen, ${MODULE_KEYS.length} Module aktiv; Plattform-Admin admin@demo.example (getrennter Login /platform/login, MFA beim ersten Login)`);
}
main()
.then(() => prisma.$disconnect())
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});