import { hash } from "@node-rs/argon2"; import type { PrismaClient } from "@prisma/client"; import { PERMISSIONS, ROLE_DEFS } from "@/server/rbac"; import { MODULES } from "@/lib/modules"; import { importPolicies } from "../../prisma/import-policies"; import { importManaged } from "../../prisma/import-managed"; /** * Mappt die Mandanten-Stammdaten (TenantSettings) auf die ISMS-Template-Variablen * des Richtlinienmoduls (§4.1) — eine Pflegestelle, keine Doppeleingabe. */ export async function syncPolicyVariablesFromSettings( prisma: PrismaClient, tenantId: string, s: { orgName: string; orgShort?: string | null; ismsScope?: string | null; ismsScopeDescription?: string | null; roleManagement?: string | null; roleIsb?: string | null; roleItLead?: string | null; roleDpo?: string | null; } ) { const map: Record = { ORG_NAME: s.orgName, ORG_SHORT: s.orgShort, ISMS_SCOPE: s.ismsScope, ISMS_SCOPE_DESCRIPTION: s.ismsScopeDescription, ROLE_MANAGEMENT: s.roleManagement, ROLE_ISB: s.roleIsb, ROLE_IT_LEAD: s.roleItLead, ROLE_DPO: s.roleDpo, }; for (const [key, value] of Object.entries(map)) { if (value == null || value === "") continue; await prisma.policyVariable.updateMany({ where: { tenantId, key }, data: { value } }); } } export interface ProvisionOpts { name: string; slug: string; short?: string; sector?: string; admin: { email: string; name: string; password: string }; tisaxLevel?: "AL2" | "AL3"; seedPoliciesDir?: string; actorId?: string | null; } /** * Legt einen Mandanten idempotent an bzw. aktualisiert ihn: Standard-Rollen + * Permissions, erster Admin-User, Mandanten-Einstellungen, alle Module aktiv, * optional Richtlinienpaket-Seed inkl. TISAX-Default (§3.7 Auto-Provisioning). */ export async function provisionTenant(prisma: PrismaClient, opts: ProvisionOpts) { const tisaxLevel = opts.tisaxLevel ?? "AL2"; // 1. Globaler Permission-Katalog for (const key of PERMISSIONS) { await prisma.permission.upsert({ where: { key }, update: {}, create: { key } }); } // 2. Mandant const tenant = await prisma.tenant.upsert({ where: { slug: opts.slug }, update: { name: opts.name, short: opts.short ?? null, sector: opts.sector ?? null }, create: { name: opts.name, slug: opts.slug, short: opts.short ?? null, sector: opts.sector ?? null }, }); // 3. Rollen + Permissions 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 }, }); } } // 4. Erster Admin-User (Mandanten-Admin + ISB) const passwordHash = await hash(opts.admin.password); const admin = await prisma.user.upsert({ where: { tenantId_email: { tenantId: tenant.id, email: opts.admin.email } }, update: { name: opts.admin.name }, create: { tenantId: tenant.id, email: opts.admin.email, name: opts.admin.name, passwordHash }, }); const adminRoles = await prisma.role.findMany({ where: { tenantId: tenant.id, key: { in: ["tenant-admin", "isb"] } } }); for (const r of adminRoles) { await prisma.userRole.upsert({ where: { userId_roleId: { userId: admin.id, roleId: r.id } }, update: {}, create: { userId: admin.id, roleId: r.id }, }); } // 5. Mandanten-Einstellungen (Quelle der ISMS-Variablen) await prisma.tenantSettings.upsert({ where: { tenantId: tenant.id }, update: {}, create: { tenantId: tenant.id, orgName: opts.name, orgShort: opts.short ?? null, sector: opts.sector ?? null, roleManagement: "Geschäftsführung", roleIsb: "Informationssicherheitsbeauftragte(r) (ISB)", roleItLead: "IT-Leitung", roleDpo: "Datenschutzbeauftragte(r) (DSB)", tisaxLevel, }, }); // 6. Alle Module aktivieren for (const m of MODULES) { await prisma.tenantModule.upsert({ where: { tenantId_moduleKey: { tenantId: tenant.id, moduleKey: m.key } }, update: {}, create: { tenantId: tenant.id, moduleKey: m.key, enabled: true }, }); } // 7. Richtlinienpaket + TISAX-Default (falls Seed-Verzeichnis übergeben) if (opts.seedPoliciesDir) { await importPolicies(prisma, tenant.id, opts.seedPoliciesDir); await importManaged(prisma, tenant.id); await prisma.policyVariable.updateMany({ where: { tenantId: tenant.id, key: "FLAG_HIGH_PROTECTION" }, data: { value: "true" } }); await prisma.policyVariable.updateMany({ where: { tenantId: tenant.id, key: "FLAG_VERY_HIGH_PROTECTION" }, data: { value: tisaxLevel === "AL3" ? "true" : "false" } }); const settings = await prisma.tenantSettings.findUnique({ where: { tenantId: tenant.id } }); if (settings) await syncPolicyVariablesFromSettings(prisma, tenant.id, settings); } await prisma.auditLog.create({ data: { tenantId: tenant.id, scope: "platform", actorId: opts.actorId ?? null, action: "provision", entity: "tenant", entityId: tenant.id, after: { name: opts.name, tisaxLevel } }, }); return tenant; }