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 = { 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); });