- Minderung je Maßnahmen-Verknüpfung jetzt dezimal (0,00–4,00 statt ganzer Punkte): mehrere Maßnahmen summieren sich, bis eine Dimension einen vollen Punkt sinkt; Rest-Risiko entsprechend dezimal (z. B. 2,5 × 4 = 10), gerundet auf 2 Nachkommastellen, lokalisierte Anzeige - Minderung bereits verknüpfter Maßnahmen direkt im Risiko-Bearbeiten editierbar (Eingabefelder je Verknüpfung + Speichern-Button); Komma-Eingaben werden akzeptiert - Globale Kataloge Bedrohungen/Schwachstellen (je 15 Einträge, Seed): Eingabefelder schlagen per datalist Katalogwerte vor, Freitext bleibt weiterhin möglich (SPEC §5 Threat/Vulnerability) - Schema: reduction*/residual* auf Float, Threat-/Vulnerability-Modelle Verifiziert: −1 + −0,5 Minderung → Rest 2,5 × 4 = 10 · Erhöht (DB + Anzeige), Neuberechnung auch beim Entfernen einer Verknüpfung. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
416 lines
12 KiB
Plaintext
416 lines
12 KiB
Plaintext
// ISMS-Tool — Prisma-Schema
|
||
// Fundament (Iteration 1): Mandanten, Nutzer, RBAC, Audit-Log.
|
||
// Fachliche Entitäten (Assets, BIA, Risiken, …) folgen je Iteration — siehe docs/SPEC.md §5.
|
||
//
|
||
// Multi-Tenant-Regel: Jede fachliche Tabelle trägt tenantId. Zugriff nur über den
|
||
// zentralen Tenant-Guard (src/server/db.ts); zusätzlich Postgres RLS (prisma/rls.sql).
|
||
|
||
generator client {
|
||
provider = "prisma-client-js"
|
||
previewFeatures = ["postgresqlExtensions"]
|
||
}
|
||
|
||
datasource db {
|
||
provider = "postgresql"
|
||
extensions = [pgvector(map: "vector")]
|
||
}
|
||
|
||
enum TenantStatus {
|
||
ACTIVE
|
||
SUSPENDED
|
||
}
|
||
|
||
enum UserStatus {
|
||
ACTIVE
|
||
INVITED
|
||
LOCKED
|
||
DEACTIVATED
|
||
}
|
||
|
||
model Tenant {
|
||
id String @id @default(cuid())
|
||
name String
|
||
slug String @unique
|
||
status TenantStatus @default(ACTIVE)
|
||
config Json @default("{}")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
users User[]
|
||
roles Role[]
|
||
auditLogs AuditLog[]
|
||
|
||
@@map("tenants")
|
||
}
|
||
|
||
model User {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
email String
|
||
passwordHash String @map("password_hash")
|
||
name String
|
||
status UserStatus @default(ACTIVE)
|
||
mfaSecret String? @map("mfa_secret")
|
||
// Plattform-Admin ist mandantenübergreifend (Betreiberrolle)
|
||
isPlatformAdmin Boolean @default(false) @map("is_platform_admin")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||
userRoles UserRole[]
|
||
ownedAssets Asset[] @relation("assetOwner")
|
||
ownedProcesses Process[] @relation("processOwner")
|
||
ownedRisks Risk[] @relation("riskOwner")
|
||
ownedMeasures Measure[] @relation("measureOwner")
|
||
|
||
@@unique([tenantId, email])
|
||
@@index([tenantId])
|
||
@@map("users")
|
||
}
|
||
|
||
model Role {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
key String // z. B. tenant-admin, isb, auditor, owner, user
|
||
name String
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||
userRoles UserRole[]
|
||
rolePermissions RolePermission[]
|
||
|
||
@@unique([tenantId, key])
|
||
@@index([tenantId])
|
||
@@map("roles")
|
||
}
|
||
|
||
model Permission {
|
||
id String @id @default(cuid())
|
||
key String @unique // z. B. asset:read, risk:write, incident:manage
|
||
|
||
rolePermissions RolePermission[]
|
||
|
||
@@map("permissions")
|
||
}
|
||
|
||
model UserRole {
|
||
userId String @map("user_id")
|
||
roleId String @map("role_id")
|
||
|
||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
||
|
||
@@id([userId, roleId])
|
||
@@map("user_roles")
|
||
}
|
||
|
||
model RolePermission {
|
||
roleId String @map("role_id")
|
||
permissionId String @map("permission_id")
|
||
|
||
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
||
permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade)
|
||
|
||
@@id([roleId, permissionId])
|
||
@@map("role_permissions")
|
||
}
|
||
|
||
// ── Assets & BIA (ein Modul, SPEC §4.1) ─────────────────────────────────────
|
||
|
||
enum AssetType {
|
||
INFORMATION
|
||
SYSTEM
|
||
APPLICATION
|
||
LOCATION
|
||
SUPPLIER
|
||
PERSON
|
||
DATA
|
||
}
|
||
|
||
enum AssetStatus {
|
||
ACTIVE
|
||
PLANNED
|
||
RETIRED
|
||
}
|
||
|
||
enum ProcessAssetRole {
|
||
PRIMARY
|
||
SECONDARY
|
||
}
|
||
|
||
enum ProcessCategory {
|
||
CORE // Kernprozess
|
||
MANAGEMENT // Managementprozess
|
||
SUPPORT // Unterstützender Prozess
|
||
}
|
||
|
||
model Asset {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
name String
|
||
description String?
|
||
type AssetType
|
||
status AssetStatus @default(ACTIVE)
|
||
ownerId String? @map("owner_id")
|
||
location String?
|
||
tags String[] @default([])
|
||
// Schutzbedarf 1–4 je Schutzziel (C/I/A)
|
||
confidentiality Int @default(1)
|
||
integrity Int @default(1)
|
||
availability Int @default(1)
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
createdBy String? @map("created_by")
|
||
|
||
owner User? @relation("assetOwner", fields: [ownerId], references: [id])
|
||
relationsFrom AssetRelation[] @relation("relationFrom")
|
||
relationsTo AssetRelation[] @relation("relationTo")
|
||
processAssets ProcessAsset[]
|
||
riskAssets RiskAsset[]
|
||
|
||
@@index([tenantId])
|
||
@@index([tenantId, type])
|
||
@@map("assets")
|
||
}
|
||
|
||
model AssetRelation {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
assetId String @map("asset_id")
|
||
relatedAssetId String @map("related_asset_id")
|
||
// z. B. depends_on, hosts, processes_data_of
|
||
type String @default("depends_on")
|
||
|
||
asset Asset @relation("relationFrom", fields: [assetId], references: [id], onDelete: Cascade)
|
||
relatedAsset Asset @relation("relationTo", fields: [relatedAssetId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([assetId, relatedAssetId, type])
|
||
@@index([tenantId])
|
||
@@map("asset_relations")
|
||
}
|
||
|
||
model Process {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
name String
|
||
description String?
|
||
category ProcessCategory @default(CORE)
|
||
ownerId String? @map("owner_id")
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
createdBy String? @map("created_by")
|
||
|
||
owner User? @relation("processOwner", fields: [ownerId], references: [id])
|
||
processAssets ProcessAsset[]
|
||
bia BiaEntry?
|
||
risks Risk[]
|
||
|
||
@@index([tenantId])
|
||
@@map("processes")
|
||
}
|
||
|
||
model ProcessAsset {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
processId String @map("process_id")
|
||
assetId String @map("asset_id")
|
||
role ProcessAssetRole @default(SECONDARY)
|
||
|
||
process Process @relation(fields: [processId], references: [id], onDelete: Cascade)
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([processId, assetId])
|
||
@@index([tenantId])
|
||
@@map("process_assets")
|
||
}
|
||
|
||
model BiaEntry {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
processId String @unique @map("process_id")
|
||
// Wiederanlaufparameter in Stunden (SPEC §4.1.2)
|
||
rtoHours Int? @map("rto_hours")
|
||
rpoHours Int? @map("rpo_hours")
|
||
mtdHours Int? @map("mtd_hours")
|
||
// Schadenshöhe 1–4 je Schutzziel
|
||
impactC Int @default(1) @map("impact_c")
|
||
impactI Int @default(1) @map("impact_i")
|
||
impactA Int @default(1) @map("impact_a")
|
||
// Kritikalität 1–4, abgeleitet (Max-Prinzip), redundant gespeichert für Listen/Filter
|
||
criticality Int @default(1)
|
||
notes String?
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
|
||
process Process @relation(fields: [processId], references: [id], onDelete: Cascade)
|
||
|
||
@@index([tenantId])
|
||
@@map("bia_entries")
|
||
}
|
||
|
||
// ── Risikoanalyse (SPEC §4.2) ───────────────────────────────────────────────
|
||
|
||
enum RiskTreatment {
|
||
AVOID // Vermeiden
|
||
MITIGATE // Vermindern
|
||
TRANSFER // Übertragen
|
||
ACCEPT // Akzeptieren
|
||
}
|
||
|
||
enum RiskStatus {
|
||
OPEN
|
||
IN_TREATMENT
|
||
ACCEPTED
|
||
CLOSED
|
||
}
|
||
|
||
model Risk {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
// Laufende Nummer je Mandant → Anzeige als "R-001"
|
||
refNo Int @map("ref_no")
|
||
|
||
title String
|
||
description String?
|
||
threat String? // Bedrohung (Katalog folgt, SPEC §5)
|
||
vulnerability String? // Schwachstelle
|
||
|
||
// Brutto-Bewertung: Eintrittswahrscheinlichkeit × Auswirkung (je 1–5)
|
||
likelihood Int @default(1)
|
||
impact Int @default(1)
|
||
score Int @default(1) // redundant für Listen/Heatmap
|
||
|
||
// Rest-Risiko nach Maßnahmen (berechnet, dezimal wegen Teil-Minderungen)
|
||
residualLikelihood Float? @map("residual_likelihood")
|
||
residualImpact Float? @map("residual_impact")
|
||
residualScore Float? @map("residual_score")
|
||
|
||
treatment RiskTreatment @default(MITIGATE)
|
||
status RiskStatus @default(OPEN)
|
||
ownerId String? @map("owner_id")
|
||
processId String? @map("process_id")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
createdBy String? @map("created_by")
|
||
|
||
owner User? @relation("riskOwner", fields: [ownerId], references: [id])
|
||
process Process? @relation(fields: [processId], references: [id])
|
||
riskAssets RiskAsset[]
|
||
riskMeasures RiskMeasure[]
|
||
|
||
@@unique([tenantId, refNo])
|
||
@@index([tenantId])
|
||
@@index([tenantId, status])
|
||
@@map("risks")
|
||
}
|
||
|
||
model RiskAsset {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
riskId String @map("risk_id")
|
||
assetId String @map("asset_id")
|
||
|
||
risk Risk @relation(fields: [riskId], references: [id], onDelete: Cascade)
|
||
asset Asset @relation(fields: [assetId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([riskId, assetId])
|
||
@@index([tenantId])
|
||
@@map("risk_assets")
|
||
}
|
||
|
||
// ── Maßnahmen (SPEC §4.4) ───────────────────────────────────────────────────
|
||
|
||
enum MeasureStatus {
|
||
OPEN
|
||
IN_PROGRESS
|
||
DONE
|
||
}
|
||
|
||
enum MeasurePriority {
|
||
LOW
|
||
MEDIUM
|
||
HIGH
|
||
}
|
||
|
||
model Measure {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
refNo Int @map("ref_no") // Anzeige als "M-001"
|
||
|
||
title String
|
||
description String?
|
||
status MeasureStatus @default(OPEN)
|
||
priority MeasurePriority @default(MEDIUM)
|
||
ownerId String? @map("owner_id")
|
||
dueDate DateTime? @map("due_date")
|
||
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
updatedAt DateTime @updatedAt @map("updated_at")
|
||
createdBy String? @map("created_by")
|
||
|
||
owner User? @relation("measureOwner", fields: [ownerId], references: [id])
|
||
riskMeasures RiskMeasure[]
|
||
|
||
@@unique([tenantId, refNo])
|
||
@@index([tenantId])
|
||
@@index([tenantId, status])
|
||
@@map("measures")
|
||
}
|
||
|
||
// Verknüpfung Risiko ↔ Maßnahme inkl. erwarteter Risikominderung:
|
||
// Das Rest-Risiko wird aus likelihood/impact minus der Summe der
|
||
// Minderungen aller verknüpften Maßnahmen berechnet (siehe src/server/risk-calc.ts).
|
||
model RiskMeasure {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
riskId String @map("risk_id")
|
||
measureId String @map("measure_id")
|
||
|
||
// Minderung je Dimension (0,00–4,00) — mehrere Maßnahmen summieren sich,
|
||
// bis ein voller Punkt (oder mehr) erreicht ist
|
||
reductionLikelihood Float @default(0) @map("reduction_likelihood")
|
||
reductionImpact Float @default(0) @map("reduction_impact")
|
||
|
||
risk Risk @relation(fields: [riskId], references: [id], onDelete: Cascade)
|
||
measure Measure @relation(fields: [measureId], references: [id], onDelete: Cascade)
|
||
|
||
@@unique([riskId, measureId])
|
||
@@index([tenantId])
|
||
@@map("risk_measures")
|
||
}
|
||
|
||
// Globale Kataloge (SPEC §5: Threat/Vulnerability, mandantenübergreifend).
|
||
// Risiken speichern weiterhin Freitext — der Katalog liefert Vorschläge (datalist),
|
||
// eigene Formulierungen bleiben möglich.
|
||
model Threat {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
|
||
@@map("threats")
|
||
}
|
||
|
||
model Vulnerability {
|
||
id String @id @default(cuid())
|
||
name String @unique
|
||
|
||
@@map("vulnerabilities")
|
||
}
|
||
|
||
model AuditLog {
|
||
id String @id @default(cuid())
|
||
tenantId String @map("tenant_id")
|
||
actorId String? @map("actor_id")
|
||
action String // create | update | delete | login | export | …
|
||
entity String // z. B. asset, risk, user
|
||
entityId String? @map("entity_id")
|
||
before Json?
|
||
after Json?
|
||
createdAt DateTime @default(now()) @map("created_at")
|
||
|
||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||
|
||
@@index([tenantId, entity, entityId])
|
||
@@index([tenantId, createdAt])
|
||
@@map("audit_logs")
|
||
}
|