Files
certvia/prisma/schema.prisma
T
MartinandClaude Fable 5 668e587881 Iteration 4 (Maßnahmen) + Risikoanalyse nachgeschärft
Risikoanalyse:
- Risiko direkt aus dem Asset-Popup erstellbar ("+ Risiko erstellen"
  → Anlege-Popup mit vorverknüpftem Asset)
- Brutto-/Rest-Risiko detailliert: Eintrittswahrscheinlichkeit und
  Schaden als beschriftete Werte (X × Y = Score-Pille)
- Rest-Risiko wird NICHT mehr manuell erfasst, sondern automatisch aus
  den verknüpften Maßnahmen berechnet (Minderung je Dimension, Summe,
  Untergrenze 1; src/server/risk-calc.ts); Neuberechnung bei jeder
  Änderung an Bewertung oder Maßnahmen-Verknüpfungen
- Risikoregister unterhalb der Heatmap in voller Breite

Maßnahmen-Modul (SPEC §4.4):
- Measure/RiskMeasure-Schema mit RLS, laufende Nummer (M-001),
  Status/Priorität/Owner/Fälligkeit
- Kanban-Board mit Drag-and-Drop (dnd-kit): Karten zwischen Offen /
  In Umsetzung / Erledigt verschieben aktualisiert den Status per
  Server-Action inkl. Audit-Log; überfällige Karten rot markiert
- Maßnahmen-Popups (Detail read-only / Bearbeiten / Anlegen) nach
  App-Muster; Detail zeigt verknüpfte Risiken mit Minderung
- Im Risiko-Bearbeiten: bestehende Maßnahme verknüpfen ODER neue
  Maßnahme direkt anlegen & verknüpfen (jeweils mit Minderung
  Wahrscheinlichkeit/Schaden); "Notwendige Maßnahmen" im Risiko-Detail
  jetzt echt
- Seed: 4 Beispiel-Maßnahmen, Rest-Risiken daraus berechnet

Verifiziert im Browser: DnD-Statuswechsel (beide Richtungen, Audit-
Einträge), Rest-Risiko-Berechnung (R-001: 4×5=20 → 2×4=8), Risiko-
Anlage aus dem Asset inkl. automatischer Verknüpfung.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:07:59 +02:00

398 lines
12 KiB
Plaintext
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.
// 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 14 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 14 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 14, 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 15)
likelihood Int @default(1)
impact Int @default(1)
score Int @default(1) // redundant für Listen/Heatmap
// Rest-Risiko nach Maßnahmen
residualLikelihood Int? @map("residual_likelihood")
residualImpact Int? @map("residual_impact")
residualScore Int? @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 in Stufen (04) je Dimension
reductionLikelihood Int @default(0) @map("reduction_likelihood")
reductionImpact Int @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")
}
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")
}