Admin-Konsole & Mandantenverwaltung (Phase 1)
Plattform-Admin-Konsole (Superadmin) + Kunden-Einstellungsbereich: - Datenmodell: TenantSettings (Quelle der ISMS-Template-Variablen), TenantModule (Feature-Toggles je Mandant), Tenant += short/sector, TenantStatus += ARCHIVED, AuditLog += scope (tenant|platform); RLS für die neuen Tenant-Tabellen. - Wiederverwendbare Provisionierung (server/provision.ts): Rollen+Permissions, erster Admin-User, Einstellungen, alle Module, optional Richtlinienpaket-Seed + TISAX-Default; idempotent + Audit (§3.7). syncPolicyVariablesFromSettings mappt Stammdaten → ISMS-Variablen (ORG_NAME, ISMS_SCOPE, ROLE_* …). - Admin-Konsole /admin (Guard isPlatformAdmin): Mandantenliste, „Neuer Kunde" (anlegen + provisionieren), Detailseite mit Modul-Toggles, Lebenszyklus (aktiv/gesperrt/archiviert), Nutzerliste. Aktionen im Plattform-Audit-Log. - Kunden-Einstellungen /settings (Guard tenant:manage): Unternehmensdaten, verantwortliche Rollen, Branding/Regionales, TISAX-Level — Stammdaten speisen die ISMS-Variablen (eine Pflegestelle). Modul-Übersicht. - Modul-Gating der Navigation (deaktivierte Module ausgeblendet); Login bereits für gesperrte/archivierte Mandanten blockiert (auth.authorize). - Seed: Demo-Mandant mit Einstellungen, 12 aktiven Modulen; admin@demo.example = Superadmin. Verifiziert: tsc/lint/build grün; Seed rollt Einstellungen/Module/Superadmin aus. (Browser-Verifikation diese Sitzung nicht möglich — Preview-Tools getrennt.) Offen (Phase 2): separater Superadmin-Store + eigener Login + MFA-Pflicht; Impersonation (zeitlich begrenzt, protokolliert); Plan/Limits; Logo-Upload/ Objektspeicher; SMTP/Benachrichtigungen; per-Route-Modul-Enforcement (serverseitig); Datenexport/Löschung/Retention (DSGVO); SSO. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "TenantStatus" ADD VALUE 'ARCHIVED';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "audit_logs" ADD COLUMN "scope" TEXT NOT NULL DEFAULT 'tenant';
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "tenants" ADD COLUMN "sector" TEXT,
|
||||
ADD COLUMN "short" TEXT;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "tenant_settings" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"org_name" TEXT NOT NULL,
|
||||
"org_short" TEXT,
|
||||
"address" TEXT,
|
||||
"sector" TEXT,
|
||||
"duns" TEXT,
|
||||
"isms_scope" TEXT,
|
||||
"isms_scope_description" TEXT,
|
||||
"role_management" TEXT,
|
||||
"role_isb" TEXT,
|
||||
"role_it_lead" TEXT,
|
||||
"role_dpo" TEXT,
|
||||
"logo_key" TEXT,
|
||||
"accent" TEXT,
|
||||
"locale" TEXT NOT NULL DEFAULT 'de',
|
||||
"timezone" TEXT NOT NULL DEFAULT 'Europe/Berlin',
|
||||
"tisax_level" TEXT NOT NULL DEFAULT 'AL2',
|
||||
"security_policy" JSONB NOT NULL DEFAULT '{}',
|
||||
"smtp" JSONB NOT NULL DEFAULT '{}',
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "tenant_settings_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "tenant_modules" (
|
||||
"id" TEXT NOT NULL,
|
||||
"tenant_id" TEXT NOT NULL,
|
||||
"module_key" TEXT NOT NULL,
|
||||
"enabled" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "tenant_modules_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "tenant_settings_tenant_id_key" ON "tenant_settings"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "tenant_settings_tenant_id_idx" ON "tenant_settings"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "tenant_modules_tenant_id_idx" ON "tenant_modules"("tenant_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "tenant_modules_tenant_id_module_key_key" ON "tenant_modules"("tenant_id", "module_key");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tenant_settings" ADD CONSTRAINT "tenant_settings_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "tenant_modules" ADD CONSTRAINT "tenant_modules_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
|
||||
-- RLS für die neuen mandantenbezogenen Tabellen
|
||||
DO $$
|
||||
DECLARE t text;
|
||||
BEGIN
|
||||
FOREACH t IN ARRAY ARRAY['tenant_settings','tenant_modules'] 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 $$;
|
||||
@@ -18,6 +18,7 @@ datasource db {
|
||||
enum TenantStatus {
|
||||
ACTIVE
|
||||
SUSPENDED
|
||||
ARCHIVED
|
||||
}
|
||||
|
||||
enum UserStatus {
|
||||
@@ -31,6 +32,8 @@ model Tenant {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
slug String @unique
|
||||
short String?
|
||||
sector String?
|
||||
status TenantStatus @default(ACTIVE)
|
||||
config Json @default("{}")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
@@ -39,10 +42,59 @@ model Tenant {
|
||||
users User[]
|
||||
roles Role[]
|
||||
auditLogs AuditLog[]
|
||||
settings TenantSettings?
|
||||
modules TenantModule[]
|
||||
|
||||
@@map("tenants")
|
||||
}
|
||||
|
||||
// Mandanten-Einstellungen: Quelle der ISMS-Template-Variablen (§4.1) + Branding/Policy
|
||||
model TenantSettings {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @unique @map("tenant_id")
|
||||
orgName String @map("org_name")
|
||||
orgShort String? @map("org_short")
|
||||
address String?
|
||||
sector String?
|
||||
duns String?
|
||||
ismsScope String? @map("isms_scope")
|
||||
ismsScopeDescription String? @map("isms_scope_description")
|
||||
roleManagement String? @map("role_management")
|
||||
roleIsb String? @map("role_isb")
|
||||
roleItLead String? @map("role_it_lead")
|
||||
roleDpo String? @map("role_dpo")
|
||||
logoKey String? @map("logo_key")
|
||||
accent String?
|
||||
locale String @default("de")
|
||||
timezone String @default("Europe/Berlin")
|
||||
tisaxLevel String @default("AL2") @map("tisax_level") // AL2 | AL3
|
||||
securityPolicy Json @default("{}") @map("security_policy") // pw/mfa/session
|
||||
smtp Json @default("{}")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([tenantId])
|
||||
@@map("tenant_settings")
|
||||
}
|
||||
|
||||
// Modul-Freischaltung je Mandant (Feature-Toggle) — gated Navigation, API und Daten
|
||||
model TenantModule {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
moduleKey String @map("module_key")
|
||||
enabled Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([tenantId, moduleKey])
|
||||
@@index([tenantId])
|
||||
@@map("tenant_modules")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
@@ -641,6 +693,7 @@ model MaturityAssessment {
|
||||
model AuditLog {
|
||||
id String @id @default(cuid())
|
||||
tenantId String @map("tenant_id")
|
||||
scope String @default("tenant") // tenant | platform
|
||||
actorId String? @map("actor_id")
|
||||
action String // create | update | delete | login | export | …
|
||||
entity String // z. B. asset, risk, user
|
||||
|
||||
@@ -6,6 +6,7 @@ import { join } from "node:path";
|
||||
import { PERMISSIONS, ROLE_DEFS } from "../src/server/rbac";
|
||||
import { importPolicies } from "./import-policies";
|
||||
import { importManaged } from "./import-managed";
|
||||
import { MODULE_KEYS } from "../src/lib/modules";
|
||||
|
||||
/**
|
||||
* Seed: global permission catalog + demo tenant with roles and users.
|
||||
@@ -526,6 +527,34 @@ async function main() {
|
||||
|
||||
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, Superadmin
|
||||
await prisma.user.updateMany({ where: { tenantId: tenant.id, email: "admin@demo.example" }, data: { isPlatformAdmin: true } });
|
||||
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, admin@demo.example = Superadmin`);
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user