Fundament: Baseline-Migration mit RLS-Funktion

- 67 Certvia-Migrationen zu prisma/migrations/0001_baseline gesquasht
  (prisma migrate diff --from-empty --to-schema)
- RLS-Block: Rolle craftvia_app (NOLOGIN NOBYPASSRLS, idempotent), GRANTs +
  ALTER DEFAULT PRIVILEGES, wiederverwendbare Funktion enable_tenant_rls(tbl)
  (ENABLE + Policy tenant_isolation USING/WITH CHECK + FORCE + GRANT),
  angewendet auf alle Tenant-Tabellen (inkl. nullable tenant_id wie bisher)
- docs/craftvia/MIGRATIONS.md: Pflichten für neue Tenant-Tabellen
  (enable_tenant_rls + beide TENANT_MODELS-Listen + PII-Felder)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 11:40:34 +02:00
co-authored by Claude Opus 5
parent 9ec7fa5356
commit 6f3f5a5947
68 changed files with 573 additions and 3927 deletions
@@ -0,0 +1,502 @@
-- CreateSchema
CREATE SCHEMA IF NOT EXISTS "public";
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "vector";
-- CreateEnum
CREATE TYPE "TenantStatus" AS ENUM ('ACTIVE', 'SUSPENDED', 'ARCHIVED');
-- CreateEnum
CREATE TYPE "UserStatus" AS ENUM ('ACTIVE', 'INVITED', 'LOCKED', 'DEACTIVATED');
-- CreateTable
CREATE TABLE "tenants" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"short" TEXT,
"sector" TEXT,
"status" "TenantStatus" NOT NULL DEFAULT 'ACTIVE',
"config" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "tenants_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tenant_settings" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"org_name" TEXT NOT NULL,
"org_short" TEXT,
"address" TEXT,
"phone" TEXT,
"email" TEXT,
"sector" TEXT,
"logo_key" TEXT,
"accent" TEXT,
"locale" TEXT NOT NULL DEFAULT 'de',
"timezone" TEXT NOT NULL DEFAULT 'Europe/Berlin',
"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")
);
-- CreateTable
CREATE TABLE "identities" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"must_change_password" BOOLEAN NOT NULL DEFAULT false,
"mfa_secret" TEXT,
"mfa_enrolled_at" TIMESTAMP(3),
"recovery_codes" JSONB NOT NULL DEFAULT '[]',
"last_totp_step" BIGINT,
"failed_logins" INTEGER NOT NULL DEFAULT 0,
"locked_until" TIMESTAMP(3),
"sessions_valid_after" TIMESTAMP(3),
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
"ui_locale" TEXT NOT NULL DEFAULT 'de',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "identities_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"identity_id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"name" TEXT NOT NULL,
"status" "UserStatus" NOT NULL DEFAULT 'ACTIVE',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "webauthn_credentials" (
"id" TEXT NOT NULL,
"identity_id" TEXT NOT NULL,
"credential_id" TEXT NOT NULL,
"public_key" TEXT NOT NULL,
"counter" BIGINT NOT NULL DEFAULT 0,
"transports" TEXT[] DEFAULT ARRAY[]::TEXT[],
"device_name" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_used_at" TIMESTAMP(3),
CONSTRAINT "webauthn_credentials_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "roles" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"key" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "roles_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "permissions" (
"id" TEXT NOT NULL,
"key" TEXT NOT NULL,
CONSTRAINT "permissions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_roles" (
"user_id" TEXT NOT NULL,
"role_id" TEXT NOT NULL,
CONSTRAINT "user_roles_pkey" PRIMARY KEY ("user_id","role_id")
);
-- CreateTable
CREATE TABLE "role_permissions" (
"role_id" TEXT NOT NULL,
"permission_id" TEXT NOT NULL,
CONSTRAINT "role_permissions_pkey" PRIMARY KEY ("role_id","permission_id")
);
-- CreateTable
CREATE TABLE "platform_admins" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"name" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
"role" TEXT NOT NULL DEFAULT 'full',
"mfa_secret" TEXT,
"mfa_enrolled_at" TIMESTAMP(3),
"recovery_codes" JSONB NOT NULL DEFAULT '[]',
"failed_logins" INTEGER NOT NULL DEFAULT 0,
"locked_until" TIMESTAMP(3),
"last_totp_step" BIGINT,
"last_login_at" TIMESTAMP(3),
"sessions_valid_after" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "platform_admins_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "platform_settings" (
"id" TEXT NOT NULL DEFAULT 'singleton',
"mfa_required" BOOLEAN NOT NULL DEFAULT false,
"backup_target" TEXT NOT NULL DEFAULT 'local',
"backup_local_dir" TEXT,
"backup_s3_endpoint" TEXT,
"backup_s3_bucket" TEXT,
"backup_s3_region" TEXT,
"backup_s3_access_key" TEXT,
"backup_s3_secret_key_enc" TEXT,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "platform_settings_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "audit_logs" (
"id" TEXT NOT NULL,
"tenant_id" TEXT,
"scope" TEXT NOT NULL DEFAULT 'tenant',
"actor_id" TEXT,
"action" TEXT NOT NULL,
"entity" TEXT NOT NULL,
"entity_id" TEXT,
"before" JSONB,
"after" JSONB,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "mail_logs" (
"id" TEXT NOT NULL,
"tenant_id" TEXT,
"scope" TEXT NOT NULL DEFAULT 'tenant',
"to" TEXT NOT NULL,
"template" TEXT NOT NULL,
"locale" TEXT NOT NULL DEFAULT 'de',
"status" TEXT NOT NULL DEFAULT 'pending',
"provider_message_id" TEXT,
"error" TEXT,
"attempts" INTEGER NOT NULL DEFAULT 0,
"dedupe_key" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"sent_at" TIMESTAMP(3),
CONSTRAINT "mail_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "notification_preferences" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"event_type" TEXT NOT NULL,
"email" BOOLEAN NOT NULL DEFAULT true,
"locale" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "notification_preferences_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "auth_tokens" (
"id" TEXT NOT NULL,
"principal_type" TEXT NOT NULL,
"principal_id" TEXT NOT NULL,
"tenant_id" TEXT,
"type" TEXT NOT NULL,
"token_hash" TEXT NOT NULL,
"new_email" TEXT,
"expires_at" TIMESTAMP(3) NOT NULL,
"used_at" TIMESTAMP(3),
"request_ip" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "auth_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "deletion_certificates" (
"id" TEXT NOT NULL,
"scope" TEXT NOT NULL,
"tenant_id" TEXT,
"tenant_slug" TEXT,
"subject_identity_id" TEXT,
"subject_email" TEXT,
"actor_id" TEXT,
"deleted_counts" JSONB NOT NULL DEFAULT '{}',
"anonymized_counts" JSONB NOT NULL DEFAULT '{}',
"snapshot_id" TEXT,
"reason" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "deletion_certificates_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tombstone_entries" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"model" TEXT NOT NULL,
"target_field" TEXT NOT NULL,
"target_value" TEXT NOT NULL,
"action" TEXT NOT NULL,
"anonymized_fields" JSONB NOT NULL DEFAULT '{}',
"reason" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tombstone_entries_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "backup_jobs" (
"id" TEXT NOT NULL,
"kind" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'queued',
"tenant_id" TEXT NOT NULL,
"tenant_slug" TEXT,
"subject_identity_id" TEXT,
"actor_id" TEXT,
"snapshot_id" TEXT,
"params" JSONB NOT NULL DEFAULT '{}',
"result" JSONB,
"error" TEXT,
"download_token" TEXT,
"download_expires_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "backup_jobs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "tenants_slug_key" ON "tenants"("slug");
-- 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");
-- CreateIndex
CREATE UNIQUE INDEX "identities_email_key" ON "identities"("email");
-- CreateIndex
CREATE INDEX "users_tenant_id_idx" ON "users"("tenant_id");
-- CreateIndex
CREATE INDEX "users_identity_id_idx" ON "users"("identity_id");
-- CreateIndex
CREATE UNIQUE INDEX "users_tenant_id_email_key" ON "users"("tenant_id", "email");
-- CreateIndex
CREATE UNIQUE INDEX "users_tenant_id_identity_id_key" ON "users"("tenant_id", "identity_id");
-- CreateIndex
CREATE UNIQUE INDEX "webauthn_credentials_credential_id_key" ON "webauthn_credentials"("credential_id");
-- CreateIndex
CREATE INDEX "webauthn_credentials_identity_id_idx" ON "webauthn_credentials"("identity_id");
-- CreateIndex
CREATE INDEX "roles_tenant_id_idx" ON "roles"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "roles_tenant_id_key_key" ON "roles"("tenant_id", "key");
-- CreateIndex
CREATE UNIQUE INDEX "permissions_key_key" ON "permissions"("key");
-- CreateIndex
CREATE UNIQUE INDEX "platform_admins_email_key" ON "platform_admins"("email");
-- CreateIndex
CREATE INDEX "audit_logs_tenant_id_entity_entity_id_idx" ON "audit_logs"("tenant_id", "entity", "entity_id");
-- CreateIndex
CREATE INDEX "audit_logs_tenant_id_created_at_idx" ON "audit_logs"("tenant_id", "created_at");
-- CreateIndex
CREATE UNIQUE INDEX "mail_logs_dedupe_key_key" ON "mail_logs"("dedupe_key");
-- CreateIndex
CREATE INDEX "mail_logs_tenant_id_created_at_idx" ON "mail_logs"("tenant_id", "created_at");
-- CreateIndex
CREATE INDEX "mail_logs_status_idx" ON "mail_logs"("status");
-- CreateIndex
CREATE INDEX "notification_preferences_tenant_id_idx" ON "notification_preferences"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "notification_preferences_user_id_event_type_key" ON "notification_preferences"("user_id", "event_type");
-- CreateIndex
CREATE UNIQUE INDEX "auth_tokens_token_hash_key" ON "auth_tokens"("token_hash");
-- CreateIndex
CREATE INDEX "auth_tokens_principal_type_principal_id_type_idx" ON "auth_tokens"("principal_type", "principal_id", "type");
-- CreateIndex
CREATE INDEX "auth_tokens_expires_at_idx" ON "auth_tokens"("expires_at");
-- CreateIndex
CREATE INDEX "deletion_certificates_tenant_id_idx" ON "deletion_certificates"("tenant_id");
-- CreateIndex
CREATE INDEX "deletion_certificates_subject_identity_id_idx" ON "deletion_certificates"("subject_identity_id");
-- CreateIndex
CREATE INDEX "tombstone_entries_tenant_id_idx" ON "tombstone_entries"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "tombstone_entries_tenant_id_model_target_value_key" ON "tombstone_entries"("tenant_id", "model", "target_value");
-- CreateIndex
CREATE UNIQUE INDEX "backup_jobs_download_token_key" ON "backup_jobs"("download_token");
-- CreateIndex
CREATE INDEX "backup_jobs_tenant_id_idx" ON "backup_jobs"("tenant_id");
-- CreateIndex
CREATE INDEX "backup_jobs_status_idx" ON "backup_jobs"("status");
-- 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;
-- AddForeignKey
ALTER TABLE "users" ADD CONSTRAINT "users_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "users" ADD CONSTRAINT "users_identity_id_fkey" FOREIGN KEY ("identity_id") REFERENCES "identities"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "webauthn_credentials" ADD CONSTRAINT "webauthn_credentials_identity_id_fkey" FOREIGN KEY ("identity_id") REFERENCES "identities"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "roles" ADD CONSTRAINT "roles_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_permission_id_fkey" FOREIGN KEY ("permission_id") REFERENCES "permissions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- ============================================================================
-- Row Level Security (Craftvia-Baseline)
-- ============================================================================
-- Zweite Verteidigungslinie hinter dem Tenant-Guard (src/server/db.ts).
--
-- App-Rolle `craftvia_app` (NOLOGIN, NOBYPASSRLS): Mit RLS_ENFORCED=true verbindet sich
-- die App als diese Rolle (RLS_DATABASE_URL) und setzt `app.tenant_id` transaktionslokal.
-- LOGIN + Passwort sind ein SECRET und werden NICHT hier gesetzt, sondern operativ:
-- ALTER ROLE craftvia_app WITH LOGIN PASSWORD '<stark>';
--
-- Owner-/Migrationsrolle: Superuser bzw. BYPASSRLS — RLS greift für sie auch unter FORCE
-- nie. Migrationen, Seed und der mandantenübergreifende Login-Lookup bleiben funktionsfähig.
--
-- Nullable tenant_id (audit_logs, mail_logs, auth_tokens): identische Policy. Zeilen mit
-- tenant_id = NULL (Plattform-Ereignisse) erfüllen die Bedingung nie und sind für
-- craftvia_app weder les- noch schreibbar — sie werden ausschließlich über den Owner-Client
-- geschrieben (beabsichtigt, wie im Certvia-Fundament).
--
-- Neue Tenant-Tabellen in künftigen Migrationen: `SELECT enable_tenant_rls('<tabelle>');`
-- (siehe docs/craftvia/MIGRATIONS.md).
-- ============================================================================
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'craftvia_app') THEN
CREATE ROLE craftvia_app NOLOGIN NOBYPASSRLS;
END IF;
END
$$;
GRANT USAGE ON SCHEMA public TO craftvia_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO craftvia_app;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO craftvia_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO craftvia_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT USAGE, SELECT ON SEQUENCES TO craftvia_app;
-- Wiederverwendbar und idempotent: ENABLE + Policy (USING + WITH CHECK) + FORCE.
-- WITH CHECK ist zwingend: ohne sie lehnt Postgres unter FORCE jeden INSERT/UPDATE der
-- App-Rolle ab bzw. erlaubt das Umschreiben einer Zeile auf einen fremden Mandanten.
-- Ungesetzter Kontext → current_setting(..., true) = NULL → 0 Zeilen (fail-closed).
CREATE OR REPLACE FUNCTION enable_tenant_rls(tbl text) RETURNS void
LANGUAGE plpgsql AS $fn$
BEGIN
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', tbl);
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation ON %I', tbl);
EXECUTE format(
'CREATE POLICY tenant_isolation ON %I '
|| 'USING (tenant_id = current_setting(''app.tenant_id'', true)) '
|| 'WITH CHECK (tenant_id = current_setting(''app.tenant_id'', true))',
tbl
);
EXECUTE format('ALTER TABLE %I FORCE ROW LEVEL SECURITY', tbl);
EXECUTE format('GRANT SELECT, INSERT, UPDATE, DELETE ON %I TO craftvia_app', tbl);
END
$fn$;
-- Tenant-Tabellen (Spiegel von TENANT_MODELS in src/server/db.ts / backup/topology.ts)
SELECT enable_tenant_rls('users');
SELECT enable_tenant_rls('roles');
SELECT enable_tenant_rls('audit_logs');
SELECT enable_tenant_rls('mail_logs');
SELECT enable_tenant_rls('notification_preferences');
SELECT enable_tenant_rls('auth_tokens');
SELECT enable_tenant_rls('tenant_settings');
SELECT enable_tenant_rls('tenant_modules');
@@ -1,131 +0,0 @@
-- CreateExtension
CREATE EXTENSION IF NOT EXISTS "vector";
-- CreateEnum
CREATE TYPE "TenantStatus" AS ENUM ('ACTIVE', 'SUSPENDED');
-- CreateEnum
CREATE TYPE "UserStatus" AS ENUM ('ACTIVE', 'INVITED', 'LOCKED', 'DEACTIVATED');
-- CreateTable
CREATE TABLE "tenants" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"slug" TEXT NOT NULL,
"status" "TenantStatus" NOT NULL DEFAULT 'ACTIVE',
"config" JSONB NOT NULL DEFAULT '{}',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "tenants_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "users" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"name" TEXT NOT NULL,
"status" "UserStatus" NOT NULL DEFAULT 'ACTIVE',
"mfa_secret" TEXT,
"is_platform_admin" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "roles" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"key" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "roles_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "permissions" (
"id" TEXT NOT NULL,
"key" TEXT NOT NULL,
CONSTRAINT "permissions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "user_roles" (
"user_id" TEXT NOT NULL,
"role_id" TEXT NOT NULL,
CONSTRAINT "user_roles_pkey" PRIMARY KEY ("user_id","role_id")
);
-- CreateTable
CREATE TABLE "role_permissions" (
"role_id" TEXT NOT NULL,
"permission_id" TEXT NOT NULL,
CONSTRAINT "role_permissions_pkey" PRIMARY KEY ("role_id","permission_id")
);
-- CreateTable
CREATE TABLE "audit_logs" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"actor_id" TEXT,
"action" TEXT NOT NULL,
"entity" TEXT NOT NULL,
"entity_id" TEXT,
"before" JSONB,
"after" JSONB,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "audit_logs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "tenants_slug_key" ON "tenants"("slug");
-- CreateIndex
CREATE INDEX "users_tenant_id_idx" ON "users"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "users_tenant_id_email_key" ON "users"("tenant_id", "email");
-- CreateIndex
CREATE INDEX "roles_tenant_id_idx" ON "roles"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "roles_tenant_id_key_key" ON "roles"("tenant_id", "key");
-- CreateIndex
CREATE UNIQUE INDEX "permissions_key_key" ON "permissions"("key");
-- CreateIndex
CREATE INDEX "audit_logs_tenant_id_entity_entity_id_idx" ON "audit_logs"("tenant_id", "entity", "entity_id");
-- CreateIndex
CREATE INDEX "audit_logs_tenant_id_created_at_idx" ON "audit_logs"("tenant_id", "created_at");
-- AddForeignKey
ALTER TABLE "users" ADD CONSTRAINT "users_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "roles" ADD CONSTRAINT "roles_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_permission_id_fkey" FOREIGN KEY ("permission_id") REFERENCES "permissions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -1,36 +0,0 @@
-- Row Level Security als zweite Verteidigungslinie (docs/SPEC.md §2).
-- Primäre Isolation ist der Tenant-Guard in src/server/db.ts.
--
-- Aktivierungsstand: Policies sind definiert; die App verbindet sich aktuell
-- noch als Tabellen-Owner (Policies greifen für Owner ohne FORCE nicht).
-- Härtungs-Iteration (SPEC §11 Punkt 8): App-Verbindung auf die Rolle
-- "isms_app" umstellen und app.tenant_id pro Transaktion setzen — dann sind
-- die Policies scharf. FORCE wird bewusst nicht gesetzt, damit Migrationen,
-- Seeds und der mandantenübergreifende Login-Lookup als Owner funktionieren.
-- Eingeschränkte Anwendungsrolle (für die Härtungs-Iteration)
DO $$
BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'isms_app') THEN
CREATE ROLE isms_app NOLOGIN NOBYPASSRLS;
END IF;
END
$$;
GRANT USAGE ON SCHEMA public TO isms_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO isms_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO isms_app;
-- RLS auf allen mandanten-gebundenen Tabellen
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "roles" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "audit_logs" ENABLE ROW LEVEL SECURITY;
-- Isolation: Zugriff nur auf Zeilen des in app.tenant_id gesetzten Mandanten
CREATE POLICY tenant_isolation ON "users"
USING ("tenant_id" = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation ON "roles"
USING ("tenant_id" = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation ON "audit_logs"
USING ("tenant_id" = current_setting('app.tenant_id', true));
@@ -1,132 +0,0 @@
-- CreateEnum
CREATE TYPE "AssetType" AS ENUM ('INFORMATION', 'SYSTEM', 'APPLICATION', 'LOCATION', 'SUPPLIER', 'PERSON', 'DATA');
-- CreateEnum
CREATE TYPE "AssetStatus" AS ENUM ('ACTIVE', 'PLANNED', 'RETIRED');
-- CreateEnum
CREATE TYPE "ProcessAssetRole" AS ENUM ('PRIMARY', 'SECONDARY');
-- CreateTable
CREATE TABLE "assets" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"type" "AssetType" NOT NULL,
"status" "AssetStatus" NOT NULL DEFAULT 'ACTIVE',
"owner_id" TEXT,
"location" TEXT,
"tags" TEXT[] DEFAULT ARRAY[]::TEXT[],
"confidentiality" INTEGER NOT NULL DEFAULT 1,
"integrity" INTEGER NOT NULL DEFAULT 1,
"availability" INTEGER NOT NULL DEFAULT 1,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "assets_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "asset_relations" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
"related_asset_id" TEXT NOT NULL,
"type" TEXT NOT NULL DEFAULT 'depends_on',
CONSTRAINT "asset_relations_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "processes" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"owner_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "processes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "process_assets" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"process_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
"role" "ProcessAssetRole" NOT NULL DEFAULT 'SECONDARY',
CONSTRAINT "process_assets_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "bia_entries" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"process_id" TEXT NOT NULL,
"rto_hours" INTEGER,
"rpo_hours" INTEGER,
"mtd_hours" INTEGER,
"impact_c" INTEGER NOT NULL DEFAULT 1,
"impact_i" INTEGER NOT NULL DEFAULT 1,
"impact_a" INTEGER NOT NULL DEFAULT 1,
"criticality" INTEGER NOT NULL DEFAULT 1,
"notes" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "bia_entries_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "assets_tenant_id_idx" ON "assets"("tenant_id");
-- CreateIndex
CREATE INDEX "assets_tenant_id_type_idx" ON "assets"("tenant_id", "type");
-- CreateIndex
CREATE INDEX "asset_relations_tenant_id_idx" ON "asset_relations"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "asset_relations_asset_id_related_asset_id_type_key" ON "asset_relations"("asset_id", "related_asset_id", "type");
-- CreateIndex
CREATE INDEX "processes_tenant_id_idx" ON "processes"("tenant_id");
-- CreateIndex
CREATE INDEX "process_assets_tenant_id_idx" ON "process_assets"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "process_assets_process_id_asset_id_key" ON "process_assets"("process_id", "asset_id");
-- CreateIndex
CREATE UNIQUE INDEX "bia_entries_process_id_key" ON "bia_entries"("process_id");
-- CreateIndex
CREATE INDEX "bia_entries_tenant_id_idx" ON "bia_entries"("tenant_id");
-- AddForeignKey
ALTER TABLE "assets" ADD CONSTRAINT "assets_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "asset_relations" ADD CONSTRAINT "asset_relations_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "asset_relations" ADD CONSTRAINT "asset_relations_related_asset_id_fkey" FOREIGN KEY ("related_asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "processes" ADD CONSTRAINT "processes_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "process_assets" ADD CONSTRAINT "process_assets_process_id_fkey" FOREIGN KEY ("process_id") REFERENCES "processes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "process_assets" ADD CONSTRAINT "process_assets_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "bia_entries" ADD CONSTRAINT "bia_entries_process_id_fkey" FOREIGN KEY ("process_id") REFERENCES "processes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,18 +0,0 @@
-- RLS-Policies für die Assets-&-BIA-Tabellen (analog zur Migration row_level_security)
ALTER TABLE "assets" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "asset_relations" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "processes" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "process_assets" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "bia_entries" ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON "assets"
USING ("tenant_id" = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation ON "asset_relations"
USING ("tenant_id" = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation ON "processes"
USING ("tenant_id" = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation ON "process_assets"
USING ("tenant_id" = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation ON "bia_entries"
USING ("tenant_id" = current_setting('app.tenant_id', true));
@@ -1,74 +0,0 @@
-- CreateEnum
CREATE TYPE "ProcessCategory" AS ENUM ('CORE', 'MANAGEMENT', 'SUPPORT');
-- CreateEnum
CREATE TYPE "RiskTreatment" AS ENUM ('AVOID', 'MITIGATE', 'TRANSFER', 'ACCEPT');
-- CreateEnum
CREATE TYPE "RiskStatus" AS ENUM ('OPEN', 'IN_TREATMENT', 'ACCEPTED', 'CLOSED');
-- AlterTable
ALTER TABLE "processes" ADD COLUMN "category" "ProcessCategory" NOT NULL DEFAULT 'CORE';
-- CreateTable
CREATE TABLE "risks" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"ref_no" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"threat" TEXT,
"vulnerability" TEXT,
"likelihood" INTEGER NOT NULL DEFAULT 1,
"impact" INTEGER NOT NULL DEFAULT 1,
"score" INTEGER NOT NULL DEFAULT 1,
"residual_likelihood" INTEGER,
"residual_impact" INTEGER,
"residual_score" INTEGER,
"treatment" "RiskTreatment" NOT NULL DEFAULT 'MITIGATE',
"status" "RiskStatus" NOT NULL DEFAULT 'OPEN',
"owner_id" TEXT,
"process_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "risks_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "risk_assets" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"risk_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
CONSTRAINT "risk_assets_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "risks_tenant_id_idx" ON "risks"("tenant_id");
-- CreateIndex
CREATE INDEX "risks_tenant_id_status_idx" ON "risks"("tenant_id", "status");
-- CreateIndex
CREATE UNIQUE INDEX "risks_tenant_id_ref_no_key" ON "risks"("tenant_id", "ref_no");
-- CreateIndex
CREATE INDEX "risk_assets_tenant_id_idx" ON "risk_assets"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "risk_assets_risk_id_asset_id_key" ON "risk_assets"("risk_id", "asset_id");
-- AddForeignKey
ALTER TABLE "risks" ADD CONSTRAINT "risks_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "risks" ADD CONSTRAINT "risks_process_id_fkey" FOREIGN KEY ("process_id") REFERENCES "processes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "risk_assets" ADD CONSTRAINT "risk_assets_risk_id_fkey" FOREIGN KEY ("risk_id") REFERENCES "risks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "risk_assets" ADD CONSTRAINT "risk_assets_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,9 +0,0 @@
-- RLS-Policies für die Risiko-Tabellen (analog zu den vorherigen RLS-Migrationen)
ALTER TABLE "risks" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "risk_assets" ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON "risks"
USING ("tenant_id" = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation ON "risk_assets"
USING ("tenant_id" = current_setting('app.tenant_id', true));
@@ -1,59 +0,0 @@
-- CreateEnum
CREATE TYPE "MeasureStatus" AS ENUM ('OPEN', 'IN_PROGRESS', 'DONE');
-- CreateEnum
CREATE TYPE "MeasurePriority" AS ENUM ('LOW', 'MEDIUM', 'HIGH');
-- CreateTable
CREATE TABLE "measures" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"ref_no" INTEGER NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"status" "MeasureStatus" NOT NULL DEFAULT 'OPEN',
"priority" "MeasurePriority" NOT NULL DEFAULT 'MEDIUM',
"owner_id" TEXT,
"due_date" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "measures_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "risk_measures" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"risk_id" TEXT NOT NULL,
"measure_id" TEXT NOT NULL,
"reduction_likelihood" INTEGER NOT NULL DEFAULT 0,
"reduction_impact" INTEGER NOT NULL DEFAULT 0,
CONSTRAINT "risk_measures_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "measures_tenant_id_idx" ON "measures"("tenant_id");
-- CreateIndex
CREATE INDEX "measures_tenant_id_status_idx" ON "measures"("tenant_id", "status");
-- CreateIndex
CREATE UNIQUE INDEX "measures_tenant_id_ref_no_key" ON "measures"("tenant_id", "ref_no");
-- CreateIndex
CREATE INDEX "risk_measures_tenant_id_idx" ON "risk_measures"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "risk_measures_risk_id_measure_id_key" ON "risk_measures"("risk_id", "measure_id");
-- AddForeignKey
ALTER TABLE "measures" ADD CONSTRAINT "measures_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "risk_measures" ADD CONSTRAINT "risk_measures_risk_id_fkey" FOREIGN KEY ("risk_id") REFERENCES "risks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "risk_measures" ADD CONSTRAINT "risk_measures_measure_id_fkey" FOREIGN KEY ("measure_id") REFERENCES "measures"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,9 +0,0 @@
-- RLS-Policies für die Maßnahmen-Tabellen
ALTER TABLE "measures" ENABLE ROW LEVEL SECURITY;
ALTER TABLE "risk_measures" ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON "measures"
USING ("tenant_id" = current_setting('app.tenant_id', true));
CREATE POLICY tenant_isolation ON "risk_measures"
USING ("tenant_id" = current_setting('app.tenant_id', true));
@@ -1,32 +0,0 @@
-- AlterTable
ALTER TABLE "risk_measures" ALTER COLUMN "reduction_likelihood" SET DEFAULT 0,
ALTER COLUMN "reduction_likelihood" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "reduction_impact" SET DEFAULT 0,
ALTER COLUMN "reduction_impact" SET DATA TYPE DOUBLE PRECISION;
-- AlterTable
ALTER TABLE "risks" ALTER COLUMN "residual_likelihood" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "residual_impact" SET DATA TYPE DOUBLE PRECISION,
ALTER COLUMN "residual_score" SET DATA TYPE DOUBLE PRECISION;
-- CreateTable
CREATE TABLE "threats" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "threats_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "vulnerabilities" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
CONSTRAINT "vulnerabilities_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "threats_name_key" ON "threats"("name");
-- CreateIndex
CREATE UNIQUE INDEX "vulnerabilities_name_key" ON "vulnerabilities"("name");
@@ -1,260 +0,0 @@
-- CreateEnum
CREATE TYPE "SupplierStatus" AS ENUM ('ACTIVE', 'ONBOARDING', 'UNDER_REVIEW', 'OFFBOARDED');
-- CreateEnum
CREATE TYPE "AssessmentType" AS ENUM ('QUESTIONNAIRE', 'SELF_ASSESSMENT', 'AUDIT');
-- CreateEnum
CREATE TYPE "AssessmentStatus" AS ENUM ('SENT', 'RECEIVED', 'EVALUATED', 'OVERDUE');
-- CreateEnum
CREATE TYPE "EvidenceKind" AS ENUM ('CERTIFICATE', 'TISAX_LABEL', 'ATTESTATION', 'AUDIT_REPORT', 'SELF_ASSESSMENT');
-- CreateEnum
CREATE TYPE "ResponsibleParty" AS ENUM ('CLIENT', 'SUPPLIER', 'SHARED');
-- CreateTable
CREATE TABLE "suppliers" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"ref_no" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"sector" TEXT,
"services" TEXT,
"criticality" INTEGER NOT NULL DEFAULT 1,
"data_categories" TEXT[] DEFAULT ARRAY[]::TEXT[],
"confidentiality" INTEGER NOT NULL DEFAULT 1,
"integrity" INTEGER NOT NULL DEFAULT 1,
"availability" INTEGER NOT NULL DEFAULT 1,
"nis2_relevant" BOOLEAN NOT NULL DEFAULT false,
"status" "SupplierStatus" NOT NULL DEFAULT 'ACTIVE',
"contact" TEXT,
"next_review" TIMESTAMP(3),
"notes" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "suppliers_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "supplier_assets" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
CONSTRAINT "supplier_assets_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "supplier_assessments" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"type" "AssessmentType" NOT NULL,
"status" "AssessmentStatus" NOT NULL DEFAULT 'SENT',
"score" INTEGER,
"date" TIMESTAMP(3),
"next_review" TIMESTAMP(3),
"result" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "supplier_assessments_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "contracts" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"type" TEXT NOT NULL DEFAULT 'service',
"av_dpa" BOOLEAN NOT NULL DEFAULT false,
"security_clauses" BOOLEAN NOT NULL DEFAULT false,
"flowdown" BOOLEAN NOT NULL DEFAULT false,
"customer_transparency" BOOLEAN NOT NULL DEFAULT false,
"valid_from" TIMESTAMP(3),
"valid_to" TIMESTAMP(3),
"reference" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "contracts_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ndas" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"parties" TEXT,
"info_scope" TEXT,
"subject" TEXT,
"valid_from" TIMESTAMP(3),
"valid_to" TIMESTAMP(3),
"obligations" TEXT,
"extension_status" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ndas_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "supplier_evidence" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"kind" "EvidenceKind" NOT NULL,
"name" TEXT,
"protects_cia" TEXT,
"valid_to" TIMESTAMP(3),
"adequacy_checked" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "supplier_evidence_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "service_responsibilities" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"it_service" TEXT NOT NULL,
"requirement" TEXT NOT NULL,
"responsible_party" "ResponsibleParty" NOT NULL DEFAULT 'SHARED',
"isa_applicability" TEXT,
"evidence" TEXT,
"integrated_local_controls" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "service_responsibilities_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "subcontractors" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"flowdown_obligation" BOOLEAN NOT NULL DEFAULT false,
CONSTRAINT "subcontractors_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "management_decisions" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"reason_no_audit" TEXT NOT NULL,
"decision" TEXT NOT NULL,
"decided_by" TEXT,
"date" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"record_ref" TEXT,
CONSTRAINT "management_decisions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "supplier_controls" (
"id" TEXT NOT NULL,
"ref" TEXT NOT NULL,
"title" TEXT NOT NULL,
"objective" TEXT NOT NULL,
"must_req" TEXT,
"should_req" TEXT,
"high_req" TEXT,
"very_high_req" TEXT,
"simplified_group_assessment" BOOLEAN NOT NULL DEFAULT false,
"target_maturity" INTEGER NOT NULL DEFAULT 3,
"references" TEXT,
CONSTRAINT "supplier_controls_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "supplier_control_maturity" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"supplier_id" TEXT NOT NULL,
"control_id" TEXT NOT NULL,
"maturity" INTEGER NOT NULL DEFAULT 0,
"notes" TEXT,
CONSTRAINT "supplier_control_maturity_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "suppliers_tenant_id_idx" ON "suppliers"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "suppliers_tenant_id_ref_no_key" ON "suppliers"("tenant_id", "ref_no");
-- CreateIndex
CREATE INDEX "supplier_assets_tenant_id_idx" ON "supplier_assets"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "supplier_assets_supplier_id_asset_id_key" ON "supplier_assets"("supplier_id", "asset_id");
-- CreateIndex
CREATE INDEX "supplier_assessments_tenant_id_idx" ON "supplier_assessments"("tenant_id");
-- CreateIndex
CREATE INDEX "contracts_tenant_id_idx" ON "contracts"("tenant_id");
-- CreateIndex
CREATE INDEX "ndas_tenant_id_idx" ON "ndas"("tenant_id");
-- CreateIndex
CREATE INDEX "supplier_evidence_tenant_id_idx" ON "supplier_evidence"("tenant_id");
-- CreateIndex
CREATE INDEX "service_responsibilities_tenant_id_idx" ON "service_responsibilities"("tenant_id");
-- CreateIndex
CREATE INDEX "subcontractors_tenant_id_idx" ON "subcontractors"("tenant_id");
-- CreateIndex
CREATE INDEX "management_decisions_tenant_id_idx" ON "management_decisions"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "supplier_controls_ref_key" ON "supplier_controls"("ref");
-- CreateIndex
CREATE INDEX "supplier_control_maturity_tenant_id_idx" ON "supplier_control_maturity"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "supplier_control_maturity_supplier_id_control_id_key" ON "supplier_control_maturity"("supplier_id", "control_id");
-- AddForeignKey
ALTER TABLE "supplier_assets" ADD CONSTRAINT "supplier_assets_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "supplier_assets" ADD CONSTRAINT "supplier_assets_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "supplier_assessments" ADD CONSTRAINT "supplier_assessments_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "contracts" ADD CONSTRAINT "contracts_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ndas" ADD CONSTRAINT "ndas_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "supplier_evidence" ADD CONSTRAINT "supplier_evidence_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "service_responsibilities" ADD CONSTRAINT "service_responsibilities_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "subcontractors" ADD CONSTRAINT "subcontractors_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "management_decisions" ADD CONSTRAINT "management_decisions_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "supplier_control_maturity" ADD CONSTRAINT "supplier_control_maturity_supplier_id_fkey" FOREIGN KEY ("supplier_id") REFERENCES "suppliers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "supplier_control_maturity" ADD CONSTRAINT "supplier_control_maturity_control_id_fkey" FOREIGN KEY ("control_id") REFERENCES "supplier_controls"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,16 +0,0 @@
-- RLS-Policies für die Lieferanten-Tabellen (analog zu row_level_security)
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY[
'suppliers','supplier_assets','supplier_assessments','contracts','ndas',
'supplier_evidence','service_responsibilities','subcontractors',
'management_decisions','supplier_control_maturity'
] 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 $$;
@@ -1,239 +0,0 @@
-- CreateEnum
CREATE TYPE "SupplierLifecycle" AS ENUM ('ACTIVE', 'ONBOARDING', 'UNDER_REVIEW', 'OFFBOARDED');
-- CreateEnum
CREATE TYPE "RaciResponsibility" AS ENUM ('PROVIDER', 'US', 'SHARED');
-- AlterEnum
ALTER TYPE "AssetType" ADD VALUE 'IT_SERVICE';
-- DropForeignKey
ALTER TABLE "contracts" DROP CONSTRAINT "contracts_supplier_id_fkey";
-- DropForeignKey
ALTER TABLE "management_decisions" DROP CONSTRAINT "management_decisions_supplier_id_fkey";
-- DropForeignKey
ALTER TABLE "ndas" DROP CONSTRAINT "ndas_supplier_id_fkey";
-- DropForeignKey
ALTER TABLE "service_responsibilities" DROP CONSTRAINT "service_responsibilities_supplier_id_fkey";
-- DropForeignKey
ALTER TABLE "subcontractors" DROP CONSTRAINT "subcontractors_supplier_id_fkey";
-- DropForeignKey
ALTER TABLE "supplier_assessments" DROP CONSTRAINT "supplier_assessments_supplier_id_fkey";
-- DropForeignKey
ALTER TABLE "supplier_assets" DROP CONSTRAINT "supplier_assets_asset_id_fkey";
-- DropForeignKey
ALTER TABLE "supplier_assets" DROP CONSTRAINT "supplier_assets_supplier_id_fkey";
-- DropForeignKey
ALTER TABLE "supplier_control_maturity" DROP CONSTRAINT "supplier_control_maturity_control_id_fkey";
-- DropForeignKey
ALTER TABLE "supplier_control_maturity" DROP CONSTRAINT "supplier_control_maturity_supplier_id_fkey";
-- DropForeignKey
ALTER TABLE "supplier_evidence" DROP CONSTRAINT "supplier_evidence_supplier_id_fkey";
-- AlterTable
ALTER TABLE "contracts" DROP COLUMN "customer_transparency",
DROP COLUMN "supplier_id",
ADD COLUMN "asset_id" TEXT NOT NULL,
ADD COLUMN "customer_requirements_passed" BOOLEAN NOT NULL DEFAULT false;
-- AlterTable
ALTER TABLE "management_decisions" DROP COLUMN "supplier_id",
ADD COLUMN "asset_id" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "ndas" DROP COLUMN "supplier_id",
ADD COLUMN "asset_id" TEXT NOT NULL,
ADD COLUMN "beyond_term" BOOLEAN NOT NULL DEFAULT false;
-- AlterTable
ALTER TABLE "subcontractors" DROP COLUMN "supplier_id",
ADD COLUMN "asset_id" TEXT NOT NULL;
-- AlterTable
ALTER TABLE "supplier_assessments" DROP COLUMN "status",
DROP COLUMN "supplier_id",
ADD COLUMN "asset_id" TEXT NOT NULL,
ADD COLUMN "self_score" INTEGER,
ADD COLUMN "verified_score" INTEGER;
-- AlterTable
ALTER TABLE "supplier_evidence" DROP COLUMN "supplier_id",
ADD COLUMN "asset_id" TEXT NOT NULL;
-- DropTable
DROP TABLE "service_responsibilities";
-- DropTable
DROP TABLE "supplier_assets";
-- DropTable
DROP TABLE "supplier_control_maturity";
-- DropTable
DROP TABLE "supplier_controls";
-- DropTable
DROP TABLE "suppliers";
-- DropEnum
DROP TYPE "AssessmentStatus";
-- DropEnum
DROP TYPE "ResponsibleParty";
-- DropEnum
DROP TYPE "SupplierStatus";
-- CreateTable
CREATE TABLE "supplier_profiles" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
"ref_no" INTEGER NOT NULL,
"sector" TEXT,
"service_desc" TEXT,
"criticality" INTEGER NOT NULL DEFAULT 1,
"data_categories" TEXT[] DEFAULT ARRAY[]::TEXT[],
"nis2_relevant" BOOLEAN NOT NULL DEFAULT false,
"lifecycle" "SupplierLifecycle" NOT NULL DEFAULT 'ACTIVE',
"contact" TEXT,
"tisax_label" TEXT,
"tisax_valid_to" TIMESTAMP(3),
"next_review" TIMESTAMP(3),
"notes" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "supplier_profiles_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "it_service_profiles" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
"ref_no" INTEGER NOT NULL,
"provider_asset_id" TEXT,
"criticality" INTEGER NOT NULL DEFAULT 1,
"internal" BOOLEAN NOT NULL DEFAULT false,
"notes" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "it_service_profiles_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "service_control_responsibilities" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
"control_ref" TEXT NOT NULL,
"title" TEXT,
"applicable" BOOLEAN NOT NULL DEFAULT true,
"responsibility" "RaciResponsibility" NOT NULL DEFAULT 'SHARED',
"evidence_ref" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "service_control_responsibilities_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "maturity_assessments" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
"self_value" DOUBLE PRECISION,
"verified_value" DOUBLE PRECISION,
"computed_value" DOUBLE PRECISION,
"isb_value" DOUBLE PRECISION,
"isb_justification" TEXT,
"approved_by" TEXT,
"approved_at" TIMESTAMP(3),
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "maturity_assessments_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "supplier_profiles_asset_id_key" ON "supplier_profiles"("asset_id");
-- CreateIndex
CREATE INDEX "supplier_profiles_tenant_id_idx" ON "supplier_profiles"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "supplier_profiles_tenant_id_ref_no_key" ON "supplier_profiles"("tenant_id", "ref_no");
-- CreateIndex
CREATE UNIQUE INDEX "it_service_profiles_asset_id_key" ON "it_service_profiles"("asset_id");
-- CreateIndex
CREATE INDEX "it_service_profiles_tenant_id_idx" ON "it_service_profiles"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "it_service_profiles_tenant_id_ref_no_key" ON "it_service_profiles"("tenant_id", "ref_no");
-- CreateIndex
CREATE INDEX "service_control_responsibilities_tenant_id_idx" ON "service_control_responsibilities"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "maturity_assessments_asset_id_key" ON "maturity_assessments"("asset_id");
-- CreateIndex
CREATE INDEX "maturity_assessments_tenant_id_idx" ON "maturity_assessments"("tenant_id");
-- AddForeignKey
ALTER TABLE "supplier_profiles" ADD CONSTRAINT "supplier_profiles_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "it_service_profiles" ADD CONSTRAINT "it_service_profiles_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "it_service_profiles" ADD CONSTRAINT "it_service_profiles_provider_asset_id_fkey" FOREIGN KEY ("provider_asset_id") REFERENCES "assets"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "supplier_assessments" ADD CONSTRAINT "supplier_assessments_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "supplier_evidence" ADD CONSTRAINT "supplier_evidence_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "management_decisions" ADD CONSTRAINT "management_decisions_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "contracts" ADD CONSTRAINT "contracts_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ndas" ADD CONSTRAINT "ndas_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "subcontractors" ADD CONSTRAINT "subcontractors_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "service_control_responsibilities" ADD CONSTRAINT "service_control_responsibilities_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "maturity_assessments" ADD CONSTRAINT "maturity_assessments_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- RLS für neue/umgestellte Lieferanten-Tabellen
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['supplier_profiles','it_service_profiles','maturity_assessments','service_control_responsibilities'] 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 $$;
@@ -1,136 +0,0 @@
-- CreateEnum
CREATE TYPE "PolicyDocType" AS ENUM ('LEITLINIE', 'RICHTLINIE', 'VERFAHREN', 'REGISTER', 'HANDBUCH', 'EIGENES');
-- CreateEnum
CREATE TYPE "PolicyStatus" AS ENUM ('ENTWURF', 'IN_FREIGABE', 'FREIGEGEBEN', 'ARCHIVIERT');
-- CreateTable
CREATE TABLE "policy_documents" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"type" "PolicyDocType" NOT NULL,
"title" TEXT NOT NULL,
"version" TEXT NOT NULL DEFAULT '1.0',
"status" "PolicyStatus" NOT NULL DEFAULT 'FREIGEGEBEN',
"owner" TEXT,
"policy_code" TEXT,
"fulfills" TEXT[] DEFAULT ARRAY[]::TEXT[],
"raw_markdown" TEXT NOT NULL,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "policy_documents_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_requirements" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"req_id" TEXT NOT NULL,
"policy_code" TEXT NOT NULL,
"control" TEXT NOT NULL,
"obligation" TEXT NOT NULL,
"condition" TEXT,
"requirement" TEXT NOT NULL,
"implementation" TEXT NOT NULL,
"va_codes" TEXT[] DEFAULT ARRAY[]::TEXT[],
"nachweis_link" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_requirements_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_variables" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"key" TEXT NOT NULL,
"title" TEXT NOT NULL,
"kind" TEXT NOT NULL,
"group_name" TEXT,
"value" TEXT NOT NULL,
"required" BOOLEAN NOT NULL DEFAULT false,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "policy_variables_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_baseline_params" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"bl_id" TEXT NOT NULL,
"section" TEXT NOT NULL,
"name" TEXT NOT NULL,
"vorgabe" TEXT NOT NULL,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_baseline_params_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_evidence" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"nr" INTEGER NOT NULL,
"policy_code" TEXT NOT NULL,
"nachweis" TEXT NOT NULL,
"quelle" TEXT NOT NULL,
"verantwortlich" TEXT NOT NULL,
"turnus" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_evidence_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "policy_documents_tenant_id_idx" ON "policy_documents"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "policy_documents_tenant_id_code_key" ON "policy_documents"("tenant_id", "code");
-- CreateIndex
CREATE INDEX "policy_requirements_tenant_id_idx" ON "policy_requirements"("tenant_id");
-- CreateIndex
CREATE INDEX "policy_requirements_tenant_id_control_idx" ON "policy_requirements"("tenant_id", "control");
-- CreateIndex
CREATE UNIQUE INDEX "policy_requirements_tenant_id_req_id_key" ON "policy_requirements"("tenant_id", "req_id");
-- CreateIndex
CREATE INDEX "policy_variables_tenant_id_idx" ON "policy_variables"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "policy_variables_tenant_id_key_key" ON "policy_variables"("tenant_id", "key");
-- CreateIndex
CREATE INDEX "policy_baseline_params_tenant_id_idx" ON "policy_baseline_params"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "policy_baseline_params_tenant_id_bl_id_key" ON "policy_baseline_params"("tenant_id", "bl_id");
-- CreateIndex
CREATE INDEX "policy_evidence_tenant_id_idx" ON "policy_evidence"("tenant_id");
-- RLS-Policies für die Richtlinien-Tabellen
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY[
'policy_documents','policy_requirements','policy_variables',
'policy_baseline_params','policy_evidence'
] 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 $$;
@@ -1,156 +0,0 @@
-- CreateTable
CREATE TABLE "crypto_entries" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"dienst" TEXT NOT NULL,
"schluessel" TEXT NOT NULL,
"algorithmus" TEXT,
"ablauf_datum" TIMESTAMP(3),
"verantwortlich" TEXT NOT NULL,
"speicherort" TEXT,
"baseline_ref" TEXT,
"notes" TEXT,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "crypto_entries_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "classification_classes" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "classification_classes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "handling_aspects" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"category" TEXT,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "handling_aspects_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "handling_rules" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"class_id" TEXT NOT NULL,
"aspect_id" TEXT NOT NULL,
"text" TEXT NOT NULL,
CONSTRAINT "handling_rules_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "risk_matrix_classes" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"max_score" INTEGER NOT NULL,
"acceptance" TEXT NOT NULL,
"tone" TEXT NOT NULL,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "risk_matrix_classes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "risk_ew_levels" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"level" INTEGER NOT NULL,
"label" TEXT NOT NULL,
"definition" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "risk_ew_levels_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "risk_damage_dimensions" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"levels" JSONB NOT NULL,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "risk_damage_dimensions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "handbook_topics" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"category" TEXT NOT NULL,
"title" TEXT NOT NULL,
"body_md" TEXT NOT NULL,
"source_refs" TEXT[] DEFAULT ARRAY[]::TEXT[],
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "handbook_topics_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "crypto_entries_tenant_id_idx" ON "crypto_entries"("tenant_id");
-- CreateIndex
CREATE INDEX "classification_classes_tenant_id_idx" ON "classification_classes"("tenant_id");
-- CreateIndex
CREATE INDEX "handling_aspects_tenant_id_idx" ON "handling_aspects"("tenant_id");
-- CreateIndex
CREATE INDEX "handling_rules_tenant_id_idx" ON "handling_rules"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "handling_rules_class_id_aspect_id_key" ON "handling_rules"("class_id", "aspect_id");
-- CreateIndex
CREATE INDEX "risk_matrix_classes_tenant_id_idx" ON "risk_matrix_classes"("tenant_id");
-- CreateIndex
CREATE INDEX "risk_ew_levels_tenant_id_idx" ON "risk_ew_levels"("tenant_id");
-- CreateIndex
CREATE INDEX "risk_damage_dimensions_tenant_id_idx" ON "risk_damage_dimensions"("tenant_id");
-- CreateIndex
CREATE INDEX "handbook_topics_tenant_id_idx" ON "handbook_topics"("tenant_id");
-- AddForeignKey
ALTER TABLE "handling_rules" ADD CONSTRAINT "handling_rules_class_id_fkey" FOREIGN KEY ("class_id") REFERENCES "classification_classes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "handling_rules" ADD CONSTRAINT "handling_rules_aspect_id_fkey" FOREIGN KEY ("aspect_id") REFERENCES "handling_aspects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- RLS-Policies für die verwalteten Register-Tabellen
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY[
'crypto_entries','classification_classes','handling_aspects','handling_rules',
'risk_matrix_classes','risk_ew_levels','risk_damage_dimensions','handbook_topics'
] 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 $$;
@@ -1,5 +0,0 @@
-- AlterTable
ALTER TABLE "policy_documents" ADD COLUMN "approved_at" TIMESTAMP(3),
ADD COLUMN "approved_by" TEXT,
ADD COLUMN "submitted_by" TEXT;
@@ -1,3 +0,0 @@
-- AlterTable
ALTER TABLE "policy_documents" ADD COLUMN "protection_override" TEXT;
@@ -1,78 +0,0 @@
-- 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 $$;
@@ -1,46 +0,0 @@
-- DropForeignKey
ALTER TABLE "audit_logs" DROP CONSTRAINT "audit_logs_tenant_id_fkey";
-- AlterTable
ALTER TABLE "audit_logs" ALTER COLUMN "tenant_id" DROP NOT NULL;
-- CreateTable
CREATE TABLE "platform_admins" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"name" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
"mfa_secret" TEXT,
"mfa_enrolled_at" TIMESTAMP(3),
"recovery_codes" JSONB NOT NULL DEFAULT '[]',
"failed_logins" INTEGER NOT NULL DEFAULT 0,
"locked_until" TIMESTAMP(3),
"last_login_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "platform_admins_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "platform_admins_email_key" ON "platform_admins"("email");
-- AddForeignKey
ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_tenant_id_fkey" FOREIGN KEY ("tenant_id") REFERENCES "tenants"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- Datenmigration (Phase-1-Härtung Paket 2): bestehende isPlatformAdmin-Nutzer in den
-- getrennten PlatformAdmin-Store übernehmen (gleicher Argon2-Hash → Login sofort möglich)
-- und das Mandanten-Flag zurücksetzen. MFA wird beim ersten Plattform-Login eingerichtet.
INSERT INTO "platform_admins" ("id", "email", "password_hash", "name", "status", "recovery_codes", "failed_logins", "created_at", "updated_at")
SELECT gen_random_uuid()::text, lower("email"), "password_hash", "name", 'ACTIVE', '[]'::jsonb, 0, now(), now()
FROM "users"
WHERE "is_platform_admin" = true
ON CONFLICT ("email") DO NOTHING;
UPDATE "users" SET "is_platform_admin" = false WHERE "is_platform_admin" = true;
-- platform_admins ist plattformweit (kein tenant_id) → RLS bewusst NICHT aktiviert.
-- Zugriff für die eingeschränkte App-Rolle (Härtungs-Iteration) explizit gewähren.
GRANT SELECT, INSERT, UPDATE, DELETE ON "platform_admins" TO isms_app;
@@ -1,6 +0,0 @@
-- AlterTable
ALTER TABLE "policy_documents" ADD COLUMN "archived_at" TIMESTAMP(3);
-- AlterTable
ALTER TABLE "policy_requirements" ADD COLUMN "archived_at" TIMESTAMP(3);
@@ -1,22 +0,0 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "mfa_enrolled_at" TIMESTAMP(3),
ADD COLUMN "must_change_password" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "recovery_codes" JSONB NOT NULL DEFAULT '[]';
-- CreateTable
CREATE TABLE "platform_settings" (
"id" TEXT NOT NULL DEFAULT 'singleton',
"mfa_required" BOOLEAN NOT NULL DEFAULT false,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "platform_settings_pkey" PRIMARY KEY ("id")
);
-- platform_settings ist plattformweit (kein tenant_id) → RLS nicht aktiviert.
GRANT SELECT, INSERT, UPDATE, DELETE ON "platform_settings" TO isms_app;
-- Singleton-Zeile anlegen (MFA-Pflicht standardmäßig aus).
INSERT INTO "platform_settings" ("id", "mfa_required", "updated_at")
VALUES ('singleton', false, now())
ON CONFLICT ("id") DO NOTHING;
@@ -1,56 +0,0 @@
-- CreateTable
CREATE TABLE "tasks" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"type" TEXT NOT NULL,
"title" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'OPEN',
"entity_type" TEXT,
"entity_id" TEXT,
"entity_ref" TEXT,
"assignee_id" TEXT,
"created_by_id" TEXT,
"due_date" TIMESTAMP(3),
"resolved_by_id" TEXT,
"resolved_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "tasks_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "task_comments" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"task_id" TEXT NOT NULL,
"author_id" TEXT,
"kind" TEXT NOT NULL DEFAULT 'comment',
"body" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "task_comments_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "tasks_tenant_id_status_idx" ON "tasks"("tenant_id", "status");
-- CreateIndex
CREATE INDEX "tasks_tenant_id_assignee_id_status_idx" ON "tasks"("tenant_id", "assignee_id", "status");
-- CreateIndex
CREATE INDEX "task_comments_tenant_id_task_id_idx" ON "task_comments"("tenant_id", "task_id");
-- AddForeignKey
ALTER TABLE "task_comments" ADD CONSTRAINT "task_comments_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- RLS für die neuen mandantenbezogenen Tabellen (zweite Verteidigungslinie).
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['tasks','task_comments'] 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 $$;
@@ -1,9 +0,0 @@
-- WP2.2 (Rendering Variante B): führenden Artikel aus den betroffenen Variablen-Werten
-- entfernen, damit "im {{VAR}}" nicht mehr zu "im das …" rendert. Nur nicht angepasste
-- Werte (= alter Default) werden migriert; kundenindividuelle Werte bleiben unberührt.
UPDATE "policy_variables" SET "value" = 'Ticketsystem'
WHERE "key" = 'TOOL_TICKET' AND "value" = 'das Ticketsystem';
UPDATE "policy_variables" SET "value" = 'ISMS-Tool'
WHERE "key" = 'TOOL_NAME' AND "value" = 'das ISMS-Tool';
UPDATE "policy_variables" SET "value" = 'Entra ID / Active Directory'
WHERE "key" = 'TOOL_IAM' AND "value" = 'das zentrale Verzeichnis (Entra ID / Active Directory)';
@@ -1,55 +0,0 @@
-- CreateTable
CREATE TABLE "managed_registers" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"columns" JSONB NOT NULL DEFAULT '[]',
"review_cycle" TEXT,
"responsible" TEXT,
"supplier_link" BOOLEAN NOT NULL DEFAULT false,
"asset_link" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "managed_registers_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "register_rows" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"register_id" TEXT NOT NULL,
"values" JSONB NOT NULL DEFAULT '{}',
"supplier_ref" TEXT,
"asset_ref" TEXT,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "register_rows_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "managed_registers_tenant_id_idx" ON "managed_registers"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "managed_registers_tenant_id_code_key" ON "managed_registers"("tenant_id", "code");
-- CreateIndex
CREATE INDEX "register_rows_tenant_id_register_id_idx" ON "register_rows"("tenant_id", "register_id");
-- AddForeignKey
ALTER TABLE "register_rows" ADD CONSTRAINT "register_rows_register_id_fkey" FOREIGN KEY ("register_id") REFERENCES "managed_registers"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- RLS für die neuen mandantenbezogenen Tabellen.
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['managed_registers','register_rows'] 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 $$;
@@ -1,91 +0,0 @@
-- CreateEnum
CREATE TYPE "SoftwareApprovalStatus" AS ENUM ('BEANTRAGT', 'FREIGEGEBEN', 'GESPERRT');
-- CreateEnum
CREATE TYPE "ProjectStatus" AS ENUM ('GEPLANT', 'LAUFEND', 'ABGESCHLOSSEN', 'ABGEBROCHEN');
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "AssetType" ADD VALUE 'SOFTWARE';
ALTER TYPE "AssetType" ADD VALUE 'PROJECT';
-- CreateTable
CREATE TABLE "software_profiles" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
"ref_no" INTEGER NOT NULL,
"provider_asset_id" TEXT,
"version" TEXT,
"approval_status" "SoftwareApprovalStatus" NOT NULL DEFAULT 'BEANTRAGT',
"approved_by" TEXT,
"criticality" INTEGER NOT NULL DEFAULT 1,
"next_review" TIMESTAMP(3),
"notes" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "software_profiles_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "project_profiles" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
"ref_no" INTEGER NOT NULL,
"classification" TEXT,
"isb_involved" BOOLEAN NOT NULL DEFAULT false,
"status" "ProjectStatus" NOT NULL DEFAULT 'GEPLANT',
"notes" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_by" TEXT,
CONSTRAINT "project_profiles_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "software_profiles_asset_id_key" ON "software_profiles"("asset_id");
-- CreateIndex
CREATE INDEX "software_profiles_tenant_id_idx" ON "software_profiles"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "software_profiles_tenant_id_ref_no_key" ON "software_profiles"("tenant_id", "ref_no");
-- CreateIndex
CREATE UNIQUE INDEX "project_profiles_asset_id_key" ON "project_profiles"("asset_id");
-- CreateIndex
CREATE INDEX "project_profiles_tenant_id_idx" ON "project_profiles"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "project_profiles_tenant_id_ref_no_key" ON "project_profiles"("tenant_id", "ref_no");
-- AddForeignKey
ALTER TABLE "software_profiles" ADD CONSTRAINT "software_profiles_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "software_profiles" ADD CONSTRAINT "software_profiles_provider_asset_id_fkey" FOREIGN KEY ("provider_asset_id") REFERENCES "assets"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "project_profiles" ADD CONSTRAINT "project_profiles_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- RLS für die neuen mandantenbezogenen Tabellen.
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['software_profiles','project_profiles'] 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 $$;
@@ -1,6 +0,0 @@
-- AlterTable
ALTER TABLE "tasks" ADD COLUMN "links" JSONB,
ADD COLUMN "origin" TEXT,
ADD COLUMN "priority" TEXT NOT NULL DEFAULT 'mittel',
ADD COLUMN "resources" JSONB;
@@ -1,32 +0,0 @@
-- CreateEnum
CREATE TYPE "OnboardingStepStatus" AS ENUM ('offen', 'in_bearbeitung', 'zur_validierung', 'validiert');
-- CreateTable
CREATE TABLE "onboarding_progress" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"step_key" TEXT NOT NULL,
"status" "OnboardingStepStatus" NOT NULL DEFAULT 'offen',
"review_comment" TEXT,
"reviewer_id" TEXT,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "onboarding_progress_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "onboarding_progress_tenant_id_idx" ON "onboarding_progress"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "onboarding_progress_tenant_id_step_key_key" ON "onboarding_progress"("tenant_id", "step_key");
-- RLS für die neue mandantenbezogene Tabelle.
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['onboarding_progress'] 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 $$;
@@ -1,12 +0,0 @@
-- Generalisiertes Review-Status-Enum (Story F2, Contracts §2). Ersetzt das
-- schrittbezogene OnboardingStepStatus und ergänzt den Zustand `zurueckgewiesen`.
-- CreateEnum
CREATE TYPE "ObjectReviewStatus" AS ENUM ('offen', 'in_bearbeitung', 'zur_validierung', 'validiert', 'zurueckgewiesen');
-- AlterTable: onboarding_progress.status auf das neue Enum umziehen (werterhaltend)
ALTER TABLE "onboarding_progress" ALTER COLUMN "status" DROP DEFAULT;
ALTER TABLE "onboarding_progress" ALTER COLUMN "status" TYPE "ObjectReviewStatus" USING ("status"::text::"ObjectReviewStatus");
ALTER TABLE "onboarding_progress" ALTER COLUMN "status" SET DEFAULT 'offen';
-- DropEnum: altes, schrittbezogenes Enum entfernen
DROP TYPE "OnboardingStepStatus";
@@ -1,29 +0,0 @@
-- CreateTable
CREATE TABLE "wizard_facts" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"key" TEXT NOT NULL,
"section" TEXT NOT NULL,
"value" JSONB NOT NULL,
"updated_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "wizard_facts_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "wizard_facts_tenant_id_idx" ON "wizard_facts"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "wizard_facts_tenant_id_key_key" ON "wizard_facts"("tenant_id", "key");
-- RLS für die neue mandantenbezogene Tabelle.
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['wizard_facts'] 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 $$;
@@ -1,30 +0,0 @@
-- CreateTable
CREATE TABLE "wizard_scopes" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"pruefziele" TEXT[] DEFAULT ARRAY['informationssicherheit']::TEXT[],
"geltungsbereich" TEXT,
"standorte" TEXT[] DEFAULT ARRAY[]::TEXT[],
"ausschluesse" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "wizard_scopes_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "wizard_scopes_tenant_id_key" ON "wizard_scopes"("tenant_id");
-- CreateIndex
CREATE INDEX "wizard_scopes_tenant_id_idx" ON "wizard_scopes"("tenant_id");
-- RLS für die neue mandantenbezogene Tabelle.
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['wizard_scopes'] 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 $$;
@@ -1,25 +0,0 @@
-- CreateTable
CREATE TABLE "implementation_hints" (
"id" TEXT NOT NULL,
"req_id" TEXT NOT NULL,
"control" TEXT NOT NULL,
"stufe" TEXT NOT NULL,
"requirement" TEXT NOT NULL,
"organisational" TEXT NOT NULL,
"technical" TEXT NOT NULL,
"evidence" TEXT NOT NULL,
"template" TEXT,
"resources" TEXT NOT NULL,
"al_filter" TEXT NOT NULL,
"procurement" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "implementation_hints_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "implementation_hints_req_id_key" ON "implementation_hints"("req_id");
-- CreateIndex
CREATE INDEX "implementation_hints_control_idx" ON "implementation_hints"("control");
@@ -1,27 +0,0 @@
-- CreateTable
CREATE TABLE "policy_package_states" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"imported_version" TEXT,
"imported_at" TIMESTAMP(3),
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "policy_package_states_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "policy_package_states_tenant_id_key" ON "policy_package_states"("tenant_id");
-- CreateIndex
CREATE INDEX "policy_package_states_tenant_id_idx" ON "policy_package_states"("tenant_id");
-- RLS für die neue mandantenbezogene Tabelle.
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['policy_package_states'] 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 $$;
@@ -1,28 +0,0 @@
-- AlterTable
ALTER TABLE "risks" ADD COLUMN "catalog_code" TEXT;
-- CreateTable
CREATE TABLE "risk_catalog_entries" (
"id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"category" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT NOT NULL,
"controls" TEXT[] DEFAULT ARRAY[]::TEXT[],
"asset_types" TEXT[] DEFAULT ARRAY[]::TEXT[],
"standard_measure" TEXT NOT NULL,
"default_likelihood" INTEGER NOT NULL,
"default_impact" INTEGER NOT NULL,
"rationale" TEXT NOT NULL,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "risk_catalog_entries_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "risk_catalog_entries_code_key" ON "risk_catalog_entries"("code");
-- CreateIndex
CREATE INDEX "risk_catalog_entries_category_idx" ON "risk_catalog_entries"("category");
@@ -1,5 +0,0 @@
-- AlterTable
ALTER TABLE "risks" ADD COLUMN "acceptance_rationale" TEXT,
ADD COLUMN "accepted_at" TIMESTAMP(3),
ADD COLUMN "accepted_by_id" TEXT;
@@ -1,35 +0,0 @@
-- CreateTable
CREATE TABLE "control_assessments" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"control" TEXT NOT NULL,
"suggested_value" INTEGER,
"confirmed_value" INTEGER NOT NULL,
"target" INTEGER NOT NULL,
"justification" TEXT,
"confirmed_by_id" TEXT,
"confirmed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "control_assessments_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "control_assessments_tenant_id_idx" ON "control_assessments"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "control_assessments_tenant_id_control_key" ON "control_assessments"("tenant_id", "control");
-- Row Level Security (zweite Verteidigungslinie, analog übrige Mandanten-Tabellen)
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['control_assessments'] 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 $$;
@@ -1,3 +0,0 @@
-- AlterTable
ALTER TABLE "tasks" ADD COLUMN "description" TEXT;
@@ -1,36 +0,0 @@
-- CreateTable
CREATE TABLE "control_implementations" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"req_id" TEXT NOT NULL,
"control" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'offen',
"note" TEXT,
"updated_by_id" TEXT,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "control_implementations_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "control_implementations_tenant_id_idx" ON "control_implementations"("tenant_id");
-- CreateIndex
CREATE INDEX "control_implementations_tenant_id_control_idx" ON "control_implementations"("tenant_id", "control");
-- CreateIndex
CREATE UNIQUE INDEX "control_implementations_tenant_id_req_id_key" ON "control_implementations"("tenant_id", "req_id");
-- Row Level Security (zweite Verteidigungslinie, analog übrige Mandanten-Tabellen)
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['control_implementations'] 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 $$;
@@ -1,9 +0,0 @@
-- F-05: Brute-Force-Schutz Mandanten-Login — Fehlversuchszähler + Kontosperre.
-- Nur eigene DDL (die geteilte lokale DB kann fremde Branch-Änderungen enthalten;
-- diese wurden bewusst NICHT übernommen). Die Tabelle "users" hat bereits eine
-- aktive RLS-Policy (tenant_isolation, Migration 20260702095816); das Hinzufügen
-- von Spalten erfordert keinen neuen RLS-DO-Block.
-- AlterTable
ALTER TABLE "users" ADD COLUMN "failed_logins" INTEGER NOT NULL DEFAULT 0,
ADD COLUMN "locked_until" TIMESTAMP(3);
@@ -1,391 +0,0 @@
-- ============================================================================
-- F-04: Row Level Security scharfschalten (FORCE + WITH CHECK + Kontext)
-- ============================================================================
-- Diese Migration enthält NUR SQL (keine Schemaänderung) und ist idempotent.
--
-- Ziel: Die in den bisherigen Migrationen definierten `tenant_isolation`-Policies
-- werden für die eingeschränkte App-Rolle `isms_app` (NOBYPASSRLS) wirksam.
--
-- Warum FORCE für den Owner UNSCHÄDLICH ist:
-- PostgreSQL wendet RLS auf einen Superuser bzw. eine Rolle mit BYPASSRLS NIE
-- an — auch nicht unter FORCE ROW LEVEL SECURITY. Die Owner-/Migrations-Rolle
-- `isms` ist Superuser + BYPASSRLS, sieht also weiterhin ALLE Zeilen. Deshalb
-- bleiben Migrationen, Seed und der mandantenübergreifende Login-Lookup (die
-- über `DATABASE_URL` = Owner laufen) voll funktionsfähig. FORCE greift nur für
-- `isms_app`. WICHTIG für Prod: Die Owner-/Migrate-Rolle MUSS BYPASSRLS bzw.
-- Superuser sein, sonst sähe der Login keine Nutzer (null Zeilen).
--
-- Warum WITH CHECK zwingend nötig ist (INSERT-Falle):
-- Unter FORCE ROW LEVEL SECURITY lehnt Postgres jeden INSERT/UPDATE ab, dessen
-- Zielzeile keine WITH-CHECK-Policy erfüllt. Die bisherigen Policies hatten nur
-- USING (Lesefilter), KEIN WITH CHECK → damit würde `isms_app` KEINE Zeile mehr
-- anlegen können. Jede Policy erhält deshalb hier zusätzlich
-- `WITH CHECK (tenant_id = current_setting('app.tenant_id', true))`, sodass nur
-- Zeilen des aktiven Mandanten geschrieben werden dürfen (verhindert auch das
-- „Umschreiben" einer Zeile auf einen fremden Mandanten).
--
-- Wie der Kontext gesetzt wird:
-- `current_setting('app.tenant_id', true)` liest den GUC-Parameter (2. Arg true
-- = kein Fehler, wenn ungesetzt → NULL). Die App setzt ihn TRANSAKTIONSLOKAL via
-- `SELECT set_config('app.tenant_id', <tenantId>, true)` in derselben Transaktion
-- wie die eigentliche Operation (siehe src/server/db.ts, dbForTenant, RLS-Pfad).
-- Ist der Kontext NICHT gesetzt, ergibt `tenant_id = NULL` → NULL (nicht TRUE):
-- `isms_app` sieht dann 0 Zeilen (fail-closed). Genau deshalb MUSS das
-- Scharfschalten zusammen mit der Kontextsetzung (RLS_ENFORCED=true) erfolgen.
--
-- App-Rolle / Secret (NICHT in dieser Migration):
-- `isms_app` existiert bereits (NOLOGIN, NOBYPASSRLS, mit GRANTs). Für den
-- scharfen Betrieb braucht sie LOGIN + starkes Passwort — das ist ein SECRET und
-- gehört NICHT in versioniertes SQL. Setze es operativ (einmalig), z. B.:
-- ALTER ROLE isms_app WITH LOGIN PASSWORD '<stark>';
-- und verbinde die App über RLS_DATABASE_URL als isms_app (RLS_ENFORCED=true).
-- Siehe docs/DEPLOY-PROD-CONTABO.md und .env.prod.example.
--
-- Idempotenz: ENABLE/FORCE sind idempotent; die Policy wird vor CREATE via
-- DROP POLICY IF EXISTS entfernt (deckt Gruppe A = 12 bestehende USING-only-
-- Policies und Gruppe B = 37 ohne Policy einheitlich ab).
-- ============================================================================
ALTER TABLE "asset_relations" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "asset_relations";
CREATE POLICY tenant_isolation ON "asset_relations"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "asset_relations" FORCE ROW LEVEL SECURITY;
ALTER TABLE "assets" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "assets";
CREATE POLICY tenant_isolation ON "assets"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "assets" FORCE ROW LEVEL SECURITY;
ALTER TABLE "audit_logs" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "audit_logs";
CREATE POLICY tenant_isolation ON "audit_logs"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "audit_logs" FORCE ROW LEVEL SECURITY;
ALTER TABLE "bia_entries" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "bia_entries";
CREATE POLICY tenant_isolation ON "bia_entries"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "bia_entries" FORCE ROW LEVEL SECURITY;
ALTER TABLE "measures" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "measures";
CREATE POLICY tenant_isolation ON "measures"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "measures" FORCE ROW LEVEL SECURITY;
ALTER TABLE "process_assets" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "process_assets";
CREATE POLICY tenant_isolation ON "process_assets"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "process_assets" FORCE ROW LEVEL SECURITY;
ALTER TABLE "processes" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "processes";
CREATE POLICY tenant_isolation ON "processes"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "processes" FORCE ROW LEVEL SECURITY;
ALTER TABLE "risk_assets" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "risk_assets";
CREATE POLICY tenant_isolation ON "risk_assets"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "risk_assets" FORCE ROW LEVEL SECURITY;
ALTER TABLE "risk_measures" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "risk_measures";
CREATE POLICY tenant_isolation ON "risk_measures"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "risk_measures" FORCE ROW LEVEL SECURITY;
ALTER TABLE "risks" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "risks";
CREATE POLICY tenant_isolation ON "risks"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "risks" FORCE ROW LEVEL SECURITY;
ALTER TABLE "roles" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "roles";
CREATE POLICY tenant_isolation ON "roles"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "roles" FORCE ROW LEVEL SECURITY;
ALTER TABLE "users" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "users";
CREATE POLICY tenant_isolation ON "users"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "users" FORCE ROW LEVEL SECURITY;
ALTER TABLE "tasks" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "tasks";
CREATE POLICY tenant_isolation ON "tasks"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "tasks" FORCE ROW LEVEL SECURITY;
ALTER TABLE "task_comments" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "task_comments";
CREATE POLICY tenant_isolation ON "task_comments"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "task_comments" FORCE ROW LEVEL SECURITY;
ALTER TABLE "managed_registers" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "managed_registers";
CREATE POLICY tenant_isolation ON "managed_registers"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "managed_registers" FORCE ROW LEVEL SECURITY;
ALTER TABLE "register_rows" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "register_rows";
CREATE POLICY tenant_isolation ON "register_rows"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "register_rows" FORCE ROW LEVEL SECURITY;
ALTER TABLE "onboarding_progress" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "onboarding_progress";
CREATE POLICY tenant_isolation ON "onboarding_progress"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "onboarding_progress" FORCE ROW LEVEL SECURITY;
ALTER TABLE "wizard_facts" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "wizard_facts";
CREATE POLICY tenant_isolation ON "wizard_facts"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "wizard_facts" FORCE ROW LEVEL SECURITY;
ALTER TABLE "wizard_scopes" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "wizard_scopes";
CREATE POLICY tenant_isolation ON "wizard_scopes"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "wizard_scopes" FORCE ROW LEVEL SECURITY;
ALTER TABLE "policy_package_states" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "policy_package_states";
CREATE POLICY tenant_isolation ON "policy_package_states"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "policy_package_states" FORCE ROW LEVEL SECURITY;
ALTER TABLE "tenant_settings" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "tenant_settings";
CREATE POLICY tenant_isolation ON "tenant_settings"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "tenant_settings" FORCE ROW LEVEL SECURITY;
ALTER TABLE "tenant_modules" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "tenant_modules";
CREATE POLICY tenant_isolation ON "tenant_modules"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "tenant_modules" FORCE ROW LEVEL SECURITY;
ALTER TABLE "supplier_profiles" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "supplier_profiles";
CREATE POLICY tenant_isolation ON "supplier_profiles"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "supplier_profiles" FORCE ROW LEVEL SECURITY;
ALTER TABLE "it_service_profiles" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "it_service_profiles";
CREATE POLICY tenant_isolation ON "it_service_profiles"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "it_service_profiles" FORCE ROW LEVEL SECURITY;
ALTER TABLE "software_profiles" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "software_profiles";
CREATE POLICY tenant_isolation ON "software_profiles"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "software_profiles" FORCE ROW LEVEL SECURITY;
ALTER TABLE "project_profiles" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "project_profiles";
CREATE POLICY tenant_isolation ON "project_profiles"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "project_profiles" FORCE ROW LEVEL SECURITY;
ALTER TABLE "supplier_assessments" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "supplier_assessments";
CREATE POLICY tenant_isolation ON "supplier_assessments"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "supplier_assessments" FORCE ROW LEVEL SECURITY;
ALTER TABLE "contracts" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "contracts";
CREATE POLICY tenant_isolation ON "contracts"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "contracts" FORCE ROW LEVEL SECURITY;
ALTER TABLE "ndas" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "ndas";
CREATE POLICY tenant_isolation ON "ndas"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "ndas" FORCE ROW LEVEL SECURITY;
ALTER TABLE "supplier_evidence" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "supplier_evidence";
CREATE POLICY tenant_isolation ON "supplier_evidence"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "supplier_evidence" FORCE ROW LEVEL SECURITY;
ALTER TABLE "service_control_responsibilities" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "service_control_responsibilities";
CREATE POLICY tenant_isolation ON "service_control_responsibilities"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "service_control_responsibilities" FORCE ROW LEVEL SECURITY;
ALTER TABLE "subcontractors" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "subcontractors";
CREATE POLICY tenant_isolation ON "subcontractors"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "subcontractors" FORCE ROW LEVEL SECURITY;
ALTER TABLE "management_decisions" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "management_decisions";
CREATE POLICY tenant_isolation ON "management_decisions"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "management_decisions" FORCE ROW LEVEL SECURITY;
ALTER TABLE "maturity_assessments" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "maturity_assessments";
CREATE POLICY tenant_isolation ON "maturity_assessments"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "maturity_assessments" FORCE ROW LEVEL SECURITY;
ALTER TABLE "control_assessments" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "control_assessments";
CREATE POLICY tenant_isolation ON "control_assessments"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "control_assessments" FORCE ROW LEVEL SECURITY;
ALTER TABLE "control_implementations" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "control_implementations";
CREATE POLICY tenant_isolation ON "control_implementations"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "control_implementations" FORCE ROW LEVEL SECURITY;
ALTER TABLE "policy_documents" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "policy_documents";
CREATE POLICY tenant_isolation ON "policy_documents"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "policy_documents" FORCE ROW LEVEL SECURITY;
ALTER TABLE "policy_requirements" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "policy_requirements";
CREATE POLICY tenant_isolation ON "policy_requirements"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "policy_requirements" FORCE ROW LEVEL SECURITY;
ALTER TABLE "policy_variables" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "policy_variables";
CREATE POLICY tenant_isolation ON "policy_variables"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "policy_variables" FORCE ROW LEVEL SECURITY;
ALTER TABLE "policy_baseline_params" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "policy_baseline_params";
CREATE POLICY tenant_isolation ON "policy_baseline_params"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "policy_baseline_params" FORCE ROW LEVEL SECURITY;
ALTER TABLE "policy_evidence" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "policy_evidence";
CREATE POLICY tenant_isolation ON "policy_evidence"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "policy_evidence" FORCE ROW LEVEL SECURITY;
ALTER TABLE "crypto_entries" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "crypto_entries";
CREATE POLICY tenant_isolation ON "crypto_entries"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "crypto_entries" FORCE ROW LEVEL SECURITY;
ALTER TABLE "classification_classes" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "classification_classes";
CREATE POLICY tenant_isolation ON "classification_classes"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "classification_classes" FORCE ROW LEVEL SECURITY;
ALTER TABLE "handling_aspects" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "handling_aspects";
CREATE POLICY tenant_isolation ON "handling_aspects"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "handling_aspects" FORCE ROW LEVEL SECURITY;
ALTER TABLE "handling_rules" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "handling_rules";
CREATE POLICY tenant_isolation ON "handling_rules"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "handling_rules" FORCE ROW LEVEL SECURITY;
ALTER TABLE "risk_matrix_classes" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "risk_matrix_classes";
CREATE POLICY tenant_isolation ON "risk_matrix_classes"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "risk_matrix_classes" FORCE ROW LEVEL SECURITY;
ALTER TABLE "risk_ew_levels" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "risk_ew_levels";
CREATE POLICY tenant_isolation ON "risk_ew_levels"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "risk_ew_levels" FORCE ROW LEVEL SECURITY;
ALTER TABLE "risk_damage_dimensions" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "risk_damage_dimensions";
CREATE POLICY tenant_isolation ON "risk_damage_dimensions"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "risk_damage_dimensions" FORCE ROW LEVEL SECURITY;
ALTER TABLE "handbook_topics" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "handbook_topics";
CREATE POLICY tenant_isolation ON "handbook_topics"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "handbook_topics" FORCE ROW LEVEL SECURITY;
@@ -1,14 +0,0 @@
-- F-17: TOTP-Replay-Schutz — zuletzt akzeptierter RFC-6238-Zeitschritt je Identität.
-- Ein bereits verwendeter (oder älterer) Zeitschritt wird beim Login/2FA abgelehnt.
--
-- Nur EIGENE DDL (die geteilte lokale DB kann Änderungen paralleler Branches
-- enthalten; fremde Blöcke wurden bewusst NICHT übernommen). "users" hat bereits
-- eine aktive RLS-Policy (tenant_isolation, Migration 20260702095816); das reine
-- Hinzufügen einer Spalte erfordert keinen neuen RLS-DO-Block. "platform_admins"
-- ist plattformweit (kein tenant_id/RLS).
-- AlterTable
ALTER TABLE "platform_admins" ADD COLUMN "last_totp_step" BIGINT;
-- AlterTable
ALTER TABLE "users" ADD COLUMN "last_totp_step" BIGINT;
@@ -1,84 +0,0 @@
-- CreateTable
CREATE TABLE "mail_logs" (
"id" TEXT NOT NULL,
"tenant_id" TEXT,
"scope" TEXT NOT NULL DEFAULT 'tenant',
"to" TEXT NOT NULL,
"template" TEXT NOT NULL,
"locale" TEXT NOT NULL DEFAULT 'de',
"status" TEXT NOT NULL DEFAULT 'pending',
"provider_message_id" TEXT,
"error" TEXT,
"attempts" INTEGER NOT NULL DEFAULT 0,
"dedupe_key" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"sent_at" TIMESTAMP(3),
CONSTRAINT "mail_logs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "notification_preferences" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"event_type" TEXT NOT NULL,
"email" BOOLEAN NOT NULL DEFAULT true,
"locale" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "notification_preferences_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "mail_logs_dedupe_key_key" ON "mail_logs"("dedupe_key");
-- CreateIndex
CREATE INDEX "mail_logs_tenant_id_created_at_idx" ON "mail_logs"("tenant_id", "created_at");
-- CreateIndex
CREATE INDEX "mail_logs_status_idx" ON "mail_logs"("status");
-- CreateIndex
CREATE INDEX "notification_preferences_tenant_id_idx" ON "notification_preferences"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "notification_preferences_user_id_event_type_key" ON "notification_preferences"("user_id", "event_type");
-- ============================================================================
-- Row Level Security (SEC1)
--
-- Muster wie seit F-04 (20260730160000_rls_enforce): ENABLE + FORCE + Policy mit
-- USING *und* WITH CHECK. Ohne WITH CHECK koennte die eingeschraenkte App-Rolle
-- `isms_app` unter FORCE keine Zeilen mehr anlegen.
--
-- mail_logs.tenant_id ist NULLABLE (Plattform-Mails ohne Mandantenbezug). Die
-- Policy ist trotzdem identisch zu audit_logs, das dieselbe Situation hat:
-- Zeilen mit tenant_id = NULL erfuellen die Bedingung nie und sind fuer `isms_app`
-- weder les- noch schreibbar. Das ist beabsichtigt — Plattform-Zeilen werden
-- ausschliesslich ueber den rohen `prisma`-Client (Owner-Rolle, BYPASSRLS)
-- geschrieben, genau wie writePlatformAudit().
--
-- GRANT ist noetig, weil die pauschalen Rechte aus 20260702095816 nur fuer die
-- damals existierenden Tabellen galten; ALTER DEFAULT PRIVILEGES greift nur fuer
-- Objekte, die von der dort gesetzten Rolle erzeugt werden.
-- ============================================================================
GRANT SELECT, INSERT, UPDATE, DELETE ON "mail_logs" TO isms_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON "notification_preferences" TO isms_app;
ALTER TABLE "mail_logs" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "mail_logs";
CREATE POLICY tenant_isolation ON "mail_logs"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "mail_logs" FORCE ROW LEVEL SECURITY;
ALTER TABLE "notification_preferences" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "notification_preferences";
CREATE POLICY tenant_isolation ON "notification_preferences"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "notification_preferences" FORCE ROW LEVEL SECURITY;
@@ -1,57 +0,0 @@
-- AlterTable
ALTER TABLE "platform_admins" ADD COLUMN "sessions_valid_after" TIMESTAMP(3);
-- AlterTable
ALTER TABLE "users" ADD COLUMN "sessions_valid_after" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "auth_tokens" (
"id" TEXT NOT NULL,
"principal_type" TEXT NOT NULL,
"principal_id" TEXT NOT NULL,
"tenant_id" TEXT,
"type" TEXT NOT NULL,
"token_hash" TEXT NOT NULL,
"new_email" TEXT,
"expires_at" TIMESTAMP(3) NOT NULL,
"used_at" TIMESTAMP(3),
"request_ip" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "auth_tokens_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "auth_tokens_token_hash_key" ON "auth_tokens"("token_hash");
-- CreateIndex
CREATE INDEX "auth_tokens_principal_type_principal_id_type_idx" ON "auth_tokens"("principal_type", "principal_id", "type");
-- CreateIndex
CREATE INDEX "auth_tokens_expires_at_idx" ON "auth_tokens"("expires_at");
-- ============================================================================
-- Row Level Security (SEC2)
--
-- Muster wie seit F-04: ENABLE + FORCE + Policy mit USING *und* WITH CHECK.
--
-- auth_tokens.tenant_id ist NULLABLE (Plattform-Admin-Tokens ohne
-- Mandantenbezug) — dieselbe Situation wie bei audit_logs und mail_logs. Zeilen
-- mit tenant_id = NULL sind fuer `isms_app` weder les- noch schreibbar.
--
-- WICHTIG: Der Token-Lookup beim Einloesen laeuft ohne Session (der Nutzer ist
-- gerade nicht angemeldet) und damit ohne Mandantenkontext. Er erfolgt deshalb
-- bewusst ueber den rohen `prisma`-Client (Owner-Rolle, BYPASSRLS) — siehe
-- src/server/auth-token.ts. Die Policy bleibt als zweite Verteidigungslinie
-- gegen versehentliche Zugriffe ueber den Mandanten-Guard bestehen.
-- ============================================================================
GRANT SELECT, INSERT, UPDATE, DELETE ON "auth_tokens" TO isms_app;
ALTER TABLE "auth_tokens" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "auth_tokens";
CREATE POLICY tenant_isolation ON "auth_tokens"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "auth_tokens" FORCE ROW LEVEL SECURITY;
@@ -1,38 +0,0 @@
-- CreateTable
CREATE TABLE "webauthn_credentials" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"credential_id" TEXT NOT NULL,
"public_key" TEXT NOT NULL,
"counter" BIGINT NOT NULL DEFAULT 0,
"transports" TEXT[] DEFAULT ARRAY[]::TEXT[],
"device_name" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"last_used_at" TIMESTAMP(3),
CONSTRAINT "webauthn_credentials_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "webauthn_credentials_credential_id_key" ON "webauthn_credentials"("credential_id");
-- CreateIndex
CREATE INDEX "webauthn_credentials_tenant_id_idx" ON "webauthn_credentials"("tenant_id");
-- CreateIndex
CREATE INDEX "webauthn_credentials_user_id_idx" ON "webauthn_credentials"("user_id");
-- AddForeignKey
ALTER TABLE "webauthn_credentials" ADD CONSTRAINT "webauthn_credentials_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Row Level Security (analog übrige Mandanten-Tabellen)
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY['webauthn_credentials'] 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 $$;
@@ -1,15 +0,0 @@
-- CreateEnum
CREATE TYPE "Domain" AS ENUM ('GOVERNANCE', 'HR', 'PHYSICAL', 'BCM', 'IT', 'PROCUREMENT', 'COMPLIANCE', 'DATA_PROTECTION', 'PROTOTYPE');
-- CreateEnum
CREATE TYPE "RaciKind" AS ENUM ('RESPONSIBLE', 'ACCOUNTABLE', 'CONSULTED', 'INFORMED');
-- CreateEnum
CREATE TYPE "InfoLabel" AS ENUM ('NONE', 'INFO_HIGH', 'INFO_VERY_HIGH', 'PROTOTYPE', 'PERSONAL_DATA');
-- AlterTable
ALTER TABLE "tasks" ADD COLUMN "domain" "Domain",
ADD COLUMN "order_idx" INTEGER NOT NULL DEFAULT 0;
-- CreateIndex
CREATE INDEX "tasks_tenant_id_domain_status_idx" ON "tasks"("tenant_id", "domain", "status");
@@ -1,3 +0,0 @@
-- AlterTable
ALTER TABLE "platform_admins" ADD COLUMN "role" TEXT NOT NULL DEFAULT 'full';
@@ -1,136 +0,0 @@
-- AlterTable
ALTER TABLE "assets" ADD COLUMN "label" "InfoLabel" NOT NULL DEFAULT 'NONE',
ADD COLUMN "normalized_name" TEXT;
-- AlterTable
ALTER TABLE "tasks" ADD COLUMN "effective_until" TIMESTAMP(3),
ADD COLUMN "recurrence" TEXT,
ADD COLUMN "remind_at" TIMESTAMP(3);
-- CreateTable
CREATE TABLE "process_catalog" (
"id" TEXT NOT NULL,
"code" TEXT NOT NULL,
"name" TEXT NOT NULL,
"category" "ProcessCategory" NOT NULL,
"suggested_asset_types" "AssetType"[],
"suggested_risk_codes" TEXT[] DEFAULT ARRAY[]::TEXT[],
"suggested_info_labels" "InfoLabel"[],
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "process_catalog_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "task_participants" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"task_id" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"raci" "RaciKind" NOT NULL,
CONSTRAINT "task_participants_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "control_domain_map" (
"id" TEXT NOT NULL,
"tenant_id" TEXT,
"control" TEXT NOT NULL,
"domain" "Domain" NOT NULL,
"raci" "RaciKind" NOT NULL DEFAULT 'RESPONSIBLE',
"function_key" TEXT,
CONSTRAINT "control_domain_map_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "evidence" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"title" TEXT NOT NULL,
"kind" TEXT NOT NULL,
"file_ref" TEXT,
"task_id" TEXT,
"control" TEXT,
"valid_from" TIMESTAMP(3),
"valid_until" TIMESTAMP(3),
"created_by_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "evidence_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "project_function_assignments" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"function_key" TEXT NOT NULL,
"user_id" TEXT,
"domain" "Domain",
"invited_email" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "project_function_assignments_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "process_catalog_code_key" ON "process_catalog"("code");
-- CreateIndex
CREATE INDEX "task_participants_tenant_id_user_id_idx" ON "task_participants"("tenant_id", "user_id");
-- CreateIndex
CREATE UNIQUE INDEX "task_participants_tenant_id_task_id_user_id_raci_key" ON "task_participants"("tenant_id", "task_id", "user_id", "raci");
-- CreateIndex
CREATE INDEX "control_domain_map_tenant_id_control_idx" ON "control_domain_map"("tenant_id", "control");
-- CreateIndex
CREATE INDEX "control_domain_map_control_idx" ON "control_domain_map"("control");
-- CreateIndex
CREATE INDEX "evidence_tenant_id_control_idx" ON "evidence"("tenant_id", "control");
-- CreateIndex
CREATE INDEX "evidence_tenant_id_task_id_idx" ON "evidence"("tenant_id", "task_id");
-- CreateIndex
CREATE INDEX "project_function_assignments_tenant_id_function_key_idx" ON "project_function_assignments"("tenant_id", "function_key");
-- CreateIndex
CREATE UNIQUE INDEX "assets_tenant_id_normalized_name_key" ON "assets"("tenant_id", "normalized_name");
-- AddForeignKey
ALTER TABLE "task_participants" ADD CONSTRAINT "task_participants_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "tasks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "evidence" ADD CONSTRAINT "evidence_task_id_fkey" FOREIGN KEY ("task_id") REFERENCES "tasks"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- ── Tenant-Isolation (RLS) für neue mandantenbezogene Tabellen ───────────────
-- Muster wie seit F-04: ENABLE + FORCE + Policy mit USING *und* WITH CHECK.
-- Globale Tabellen (process_catalog) und die tenant-optionale control_domain_map
-- (globale Defaults mit tenant_id = NULL) sind bewusst ausgenommen.
ALTER TABLE "project_function_assignments" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "project_function_assignments";
CREATE POLICY tenant_isolation ON "project_function_assignments"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "project_function_assignments" FORCE ROW LEVEL SECURITY;
ALTER TABLE "task_participants" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "task_participants";
CREATE POLICY tenant_isolation ON "task_participants"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "task_participants" FORCE ROW LEVEL SECURITY;
ALTER TABLE "evidence" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "evidence";
CREATE POLICY tenant_isolation ON "evidence"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "evidence" FORCE ROW LEVEL SECURITY;
@@ -1,18 +0,0 @@
-- TISAX M1: Onboarding-Step-Keys umbenannt (scoping→scope, policies→policy).
-- Bestehende onboarding_progress-Zeilen umschlüsseln, damit der Fortschritt erhalten
-- bleibt (sonst verwaisen die Alt-Keys und die Schritte starten wieder als „offen").
-- NOT EXISTS-Guard gegen die @@unique(tenant_id, step_key): existiert der Ziel-Key
-- bereits, wird nicht umbenannt (die neue Zeile gewinnt).
UPDATE "onboarding_progress" o SET "step_key" = 'scope'
WHERE o."step_key" = 'scoping'
AND NOT EXISTS (
SELECT 1 FROM "onboarding_progress" i
WHERE i."tenant_id" = o."tenant_id" AND i."step_key" = 'scope'
);
UPDATE "onboarding_progress" o SET "step_key" = 'policy'
WHERE o."step_key" = 'policies'
AND NOT EXISTS (
SELECT 1 FROM "onboarding_progress" i
WHERE i."tenant_id" = o."tenant_id" AND i."step_key" = 'policy'
);
@@ -1,18 +0,0 @@
-- AlterTable
ALTER TABLE "processes" ADD COLUMN "bia_status" TEXT NOT NULL DEFAULT 'offen',
ADD COLUMN "catalog_code" TEXT,
ADD COLUMN "data_protection_relevant" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "deputy_owner_id" TEXT,
ADD COLUMN "in_scope" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "interfaces" TEXT,
ADD COLUMN "legal_basis" TEXT,
ADD COLUMN "parent_id" TEXT,
ADD COLUMN "prototype_relevant" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "purpose" TEXT;
-- CreateIndex
CREATE INDEX "processes_tenant_id_parent_id_idx" ON "processes"("tenant_id", "parent_id");
-- AddForeignKey
ALTER TABLE "processes" ADD CONSTRAINT "processes_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "processes"("id") ON DELETE SET NULL ON UPDATE CASCADE;
@@ -1,3 +0,0 @@
-- AlterTable
ALTER TABLE "process_catalog" ADD COLUMN "parent_code" TEXT;
@@ -1,94 +0,0 @@
-- CreateEnum
CREATE TYPE "AuditType" AS ENUM ('INTERNAL', 'EXTERNAL');
-- CreateEnum
CREATE TYPE "AuditStatus" AS ENUM ('PLANNED', 'IN_PREPARATION', 'DONE');
-- CreateTable
CREATE TABLE "audits" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"type" "AuditType" NOT NULL,
"title" TEXT NOT NULL,
"planned_date" TEXT,
"scope" TEXT,
"assessment_level" TEXT,
"provider" TEXT,
"auditor_user_id" TEXT,
"status" "AuditStatus" NOT NULL DEFAULT 'PLANNED',
"preparation_deadline" TIMESTAMP(3),
"result" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "audits_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "audit_evidence_items" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"audit_id" TEXT NOT NULL,
"control" TEXT NOT NULL,
"title" TEXT NOT NULL,
"req_id" TEXT,
"assigned_user_id" TEXT,
"assigned_function_key" TEXT,
"status" TEXT NOT NULL DEFAULT 'offen',
"evidence_id" TEXT,
"task_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "audit_evidence_items_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "control_descriptions" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"control" TEXT NOT NULL,
"req_id" TEXT NOT NULL,
"draft_text" TEXT,
"source_ref" TEXT,
"confidence" TEXT,
"status" TEXT NOT NULL DEFAULT 'open',
"open_answer" TEXT,
"updated_by_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "control_descriptions_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "audits_tenant_id_status_idx" ON "audits"("tenant_id", "status");
-- CreateIndex
CREATE INDEX "audit_evidence_items_tenant_id_audit_id_idx" ON "audit_evidence_items"("tenant_id", "audit_id");
-- CreateIndex
CREATE INDEX "audit_evidence_items_tenant_id_assigned_user_id_status_idx" ON "audit_evidence_items"("tenant_id", "assigned_user_id", "status");
-- CreateIndex
CREATE INDEX "control_descriptions_tenant_id_control_idx" ON "control_descriptions"("tenant_id", "control");
-- CreateIndex
CREATE UNIQUE INDEX "control_descriptions_tenant_id_req_id_key" ON "control_descriptions"("tenant_id", "req_id");
-- AddForeignKey
ALTER TABLE "audit_evidence_items" ADD CONSTRAINT "audit_evidence_items_audit_id_fkey" FOREIGN KEY ("audit_id") REFERENCES "audits"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- ── Tenant-Isolation (RLS) für neue Audit-Tabellen (Muster wie F-04) ──────────
ALTER TABLE "audits" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "audits";
CREATE POLICY tenant_isolation ON "audits" USING ("tenant_id" = current_setting('app.tenant_id', true)) WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "audits" FORCE ROW LEVEL SECURITY;
ALTER TABLE "audit_evidence_items" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "audit_evidence_items";
CREATE POLICY tenant_isolation ON "audit_evidence_items" USING ("tenant_id" = current_setting('app.tenant_id', true)) WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "audit_evidence_items" FORCE ROW LEVEL SECURITY;
ALTER TABLE "control_descriptions" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "control_descriptions";
CREATE POLICY tenant_isolation ON "control_descriptions" USING ("tenant_id" = current_setting('app.tenant_id', true)) WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "control_descriptions" FORCE ROW LEVEL SECURITY;
@@ -1,3 +0,0 @@
-- AlterTable
ALTER TABLE "policy_documents" ADD COLUMN "domain" "Domain";
@@ -1,158 +0,0 @@
-- Globale Richtlinien-Vorlagen (Plattform-Ebene, KEIN tenantId/RLS — analog implementation_hints).
-- Master-Quelle für den Mandanten-Import; Versionierung (DRAFT/PUBLISHED) + Sprache (locale).
-- CreateEnum
CREATE TYPE "PolicyTemplateStatus" AS ENUM ('DRAFT', 'PUBLISHED', 'ARCHIVED');
-- CreateTable
CREATE TABLE "policy_template_versions" (
"id" TEXT NOT NULL,
"version" TEXT NOT NULL,
"status" "PolicyTemplateStatus" NOT NULL DEFAULT 'DRAFT',
"notes" TEXT,
"published_by" TEXT,
"published_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "policy_template_versions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_template_docs" (
"id" TEXT NOT NULL,
"version_id" TEXT NOT NULL,
"locale" TEXT NOT NULL,
"code" TEXT NOT NULL,
"type" "PolicyDocType" NOT NULL,
"title" TEXT NOT NULL,
"doc_version" TEXT NOT NULL DEFAULT '1.0',
"policy_code" TEXT,
"fulfills" TEXT[] DEFAULT ARRAY[]::TEXT[],
"domain" "Domain",
"raw_markdown" TEXT NOT NULL,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "policy_template_docs_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_template_requirements" (
"id" TEXT NOT NULL,
"version_id" TEXT NOT NULL,
"locale" TEXT NOT NULL,
"req_id" TEXT NOT NULL,
"policy_code" TEXT NOT NULL,
"control" TEXT NOT NULL,
"obligation" TEXT NOT NULL,
"condition" TEXT,
"requirement" TEXT NOT NULL,
"implementation" TEXT NOT NULL,
"va_codes" TEXT[] DEFAULT ARRAY[]::TEXT[],
"nachweis_link" TEXT,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_template_requirements_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_template_variables" (
"id" TEXT NOT NULL,
"version_id" TEXT NOT NULL,
"locale" TEXT NOT NULL,
"key" TEXT NOT NULL,
"title" TEXT NOT NULL,
"kind" TEXT NOT NULL,
"group_name" TEXT,
"value" TEXT NOT NULL,
"required" BOOLEAN NOT NULL DEFAULT false,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_template_variables_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_template_baseline_params" (
"id" TEXT NOT NULL,
"version_id" TEXT NOT NULL,
"locale" TEXT NOT NULL,
"bl_id" TEXT NOT NULL,
"section" TEXT NOT NULL,
"name" TEXT NOT NULL,
"vorgabe" TEXT NOT NULL,
"order_idx" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_template_baseline_params_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_template_evidence" (
"id" TEXT NOT NULL,
"version_id" TEXT NOT NULL,
"locale" TEXT NOT NULL,
"nr" INTEGER NOT NULL,
"policy_code" TEXT NOT NULL,
"nachweis" TEXT NOT NULL,
"quelle" TEXT NOT NULL,
"verantwortlich" TEXT NOT NULL,
"turnus" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_template_evidence_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "policy_template_versions_version_key" ON "policy_template_versions"("version");
-- CreateIndex
CREATE INDEX "policy_template_versions_status_idx" ON "policy_template_versions"("status");
-- CreateIndex
CREATE INDEX "policy_template_docs_version_id_locale_idx" ON "policy_template_docs"("version_id", "locale");
-- CreateIndex
CREATE UNIQUE INDEX "policy_template_docs_version_id_locale_code_key" ON "policy_template_docs"("version_id", "locale", "code");
-- CreateIndex
CREATE INDEX "policy_template_requirements_version_id_locale_idx" ON "policy_template_requirements"("version_id", "locale");
-- CreateIndex
CREATE UNIQUE INDEX "policy_template_requirements_version_id_locale_req_id_key" ON "policy_template_requirements"("version_id", "locale", "req_id");
-- CreateIndex
CREATE INDEX "policy_template_variables_version_id_locale_idx" ON "policy_template_variables"("version_id", "locale");
-- CreateIndex
CREATE UNIQUE INDEX "policy_template_variables_version_id_locale_key_key" ON "policy_template_variables"("version_id", "locale", "key");
-- CreateIndex
CREATE INDEX "policy_template_baseline_params_version_id_locale_idx" ON "policy_template_baseline_params"("version_id", "locale");
-- CreateIndex
CREATE UNIQUE INDEX "policy_template_baseline_params_version_id_locale_bl_id_key" ON "policy_template_baseline_params"("version_id", "locale", "bl_id");
-- CreateIndex
CREATE INDEX "policy_template_evidence_version_id_locale_idx" ON "policy_template_evidence"("version_id", "locale");
-- CreateIndex
CREATE UNIQUE INDEX "policy_template_evidence_version_id_locale_nr_key" ON "policy_template_evidence"("version_id", "locale", "nr");
-- AddForeignKey
ALTER TABLE "policy_template_docs" ADD CONSTRAINT "policy_template_docs_version_id_fkey" FOREIGN KEY ("version_id") REFERENCES "policy_template_versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "policy_template_requirements" ADD CONSTRAINT "policy_template_requirements_version_id_fkey" FOREIGN KEY ("version_id") REFERENCES "policy_template_versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "policy_template_variables" ADD CONSTRAINT "policy_template_variables_version_id_fkey" FOREIGN KEY ("version_id") REFERENCES "policy_template_versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "policy_template_baseline_params" ADD CONSTRAINT "policy_template_baseline_params_version_id_fkey" FOREIGN KEY ("version_id") REFERENCES "policy_template_versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "policy_template_evidence" ADD CONSTRAINT "policy_template_evidence_version_id_fkey" FOREIGN KEY ("version_id") REFERENCES "policy_template_versions"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,54 +0,0 @@
-- ============================================================================
-- WS0 (Option C) — Fundament: globale `Identity` + `User.identity_id`
-- ============================================================================
-- Umbau "Zentrale Identität mit Mandanten-Mitgliedschaften" (docs/FEINDESIGN-
-- identity-mandanten.md). Dieser Schritt ist ADDITIV (Expand/Contract):
-- * `identities` wird neu angelegt und ist GLOBAL — bewusst OHNE
-- `tenant_id`, OHNE RLS/Policy und NICHT in TENANT_MODELS. Der Zugriff läuft
-- ausschließlich über den Owner-`prisma`-Client (wie heute der Login-Lookup).
-- isms_app (RLS-Rolle) greift nie direkt auf `identities` zu; die FK-Prüfung
-- beim INSERT in `users` läuft über den internen RI-Trigger (Owner-Recht) und
-- braucht kein SELECT-GRANT für isms_app.
-- * `users` behält in diesem Schritt seine Auth-Spalten (Legacy), damit Login/
-- Guards grün und lauffähig bleiben. Die Konsumenten wandern in WS1–WS4 auf
-- Identity; die dann ungenutzten Spalten entfernt eine spätere Contract-Migration.
--
-- Hinweis Entscheidung D: kein Backfill/Merge. `identity_id` ist NOT NULL — die
-- Migration wird deshalb auf einer LEEREN `users`-Tabelle angewandt (Test/Coolify:
-- DB leeren -> migrate deploy -> seed; lokal via `prisma migrate reset --force`).
-- ============================================================================
-- CreateTable (GLOBAL — keine RLS!)
CREATE TABLE "identities" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"must_change_password" BOOLEAN NOT NULL DEFAULT false,
"mfa_secret" TEXT,
"mfa_enrolled_at" TIMESTAMP(3),
"recovery_codes" JSONB NOT NULL DEFAULT '[]',
"last_totp_step" BIGINT,
"failed_logins" INTEGER NOT NULL DEFAULT 0,
"locked_until" TIMESTAMP(3),
"sessions_valid_after" TIMESTAMP(3),
"status" TEXT NOT NULL DEFAULT 'ACTIVE',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "identities_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "identities_email_key" ON "identities"("email");
-- AlterTable: Mitgliedschaft -> Identity (Option C)
ALTER TABLE "users" ADD COLUMN "identity_id" TEXT NOT NULL;
-- CreateIndex
CREATE INDEX "users_identity_id_idx" ON "users"("identity_id");
-- CreateIndex: eine Person max. 1 Mitgliedschaft je Mandant
CREATE UNIQUE INDEX "users_tenant_id_identity_id_key" ON "users"("tenant_id", "identity_id");
-- AddForeignKey
ALTER TABLE "users" ADD CONSTRAINT "users_identity_id_fkey" FOREIGN KEY ("identity_id") REFERENCES "identities"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
@@ -1,35 +0,0 @@
-- ============================================================================
-- WS4b (Option C) — WebAuthn/Passkeys von der Mitgliedschaft auf die GLOBALE Identity
-- ============================================================================
-- Passkeys sind identitäts-, nicht mandantengebunden. Die Tabelle verliert daher
-- tenant_id + RLS und verweist künftig auf `identities`. `webauthn_credentials` ist
-- damit ein GLOBALES Modell (nicht mehr in TENANT_MODELS).
--
-- Reihenfolge: erst RLS/Policy abbauen (die Policy referenziert tenant_id), dann die
-- Spalten umbauen. identity_id ist NOT NULL — die Migration läuft auf einer leeren
-- Tabelle (im Seed werden keine Passkeys angelegt; Bestand = Testdaten).
-- ============================================================================
-- RLS/Policy entfernen (war über webauthn_credentials-Migration + rls_enforce gesetzt).
ALTER TABLE "webauthn_credentials" DISABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "webauthn_credentials";
-- DropForeignKey
ALTER TABLE "webauthn_credentials" DROP CONSTRAINT "webauthn_credentials_user_id_fkey";
-- DropIndex
DROP INDEX "webauthn_credentials_tenant_id_idx";
-- DropIndex
DROP INDEX "webauthn_credentials_user_id_idx";
-- AlterTable: tenant_id/user_id → identity_id
ALTER TABLE "webauthn_credentials" DROP COLUMN "tenant_id",
DROP COLUMN "user_id",
ADD COLUMN "identity_id" TEXT NOT NULL;
-- CreateIndex
CREATE INDEX "webauthn_credentials_identity_id_idx" ON "webauthn_credentials"("identity_id");
-- AddForeignKey
ALTER TABLE "webauthn_credentials" ADD CONSTRAINT "webauthn_credentials_identity_id_fkey" FOREIGN KEY ("identity_id") REFERENCES "identities"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,20 +0,0 @@
-- ============================================================================
-- Contract-Migration (Option C) — ungenutzte Auth-Spalten von `users` entfernen
-- ============================================================================
-- Abschluss des Expand/Contract-Umbaus: Passwort/MFA/Kill-Switch/Lockout leben seit
-- WS1–WS4b ausschließlich an der GLOBALEN `Identity`. Die per-Mandant-Zeile `users`
-- ist reine Mitgliedschaft — ihre früheren (Legacy-)Auth-Spalten werden hier entfernt.
-- Nach WS1–WS4b referenziert kein Code diese Spalten mehr (durch tsc verifiziert).
-- DROP COLUMN läuft auch auf gefüllten Tabellen (kein Reset nötig).
-- ============================================================================
ALTER TABLE "users" DROP COLUMN "failed_logins",
DROP COLUMN "is_platform_admin",
DROP COLUMN "last_totp_step",
DROP COLUMN "locked_until",
DROP COLUMN "mfa_enrolled_at",
DROP COLUMN "mfa_secret",
DROP COLUMN "must_change_password",
DROP COLUMN "password_hash",
DROP COLUMN "recovery_codes",
DROP COLUMN "sessions_valid_after";
@@ -1,12 +0,0 @@
-- ============================================================================
-- Identity.uiLocale — persönliche UI-Sprache (Option C)
-- ============================================================================
-- Neue, personengebundene Präferenz an der GLOBALEN `Identity` (folgt der Person
-- über alle Mandanten). Steuert die UI-Sprache (next-intl, src/i18n/request.ts).
-- Bewusst getrennt von `tenant_settings.locale` (= Vorlagen-Import-Sprache, Inhalt).
-- Kein RLS (Identity ist global, nicht in TENANT_MODELS). Werte: 'de' | 'en'.
-- ADD COLUMN mit Default läuft auch auf gefüllten Tabellen (kein Reset nötig).
-- ============================================================================
-- AlterTable
ALTER TABLE "identities" ADD COLUMN "ui_locale" TEXT NOT NULL DEFAULT 'de';
@@ -1,40 +0,0 @@
-- Backup/DSGVO-Lane: globale Modelle (kein tenant_id, kein RLS, analog Kataloge).
-- CreateTable
CREATE TABLE "deletion_certificates" (
"id" TEXT NOT NULL,
"scope" TEXT NOT NULL,
"tenant_id" TEXT,
"tenant_slug" TEXT,
"subject_identity_id" TEXT,
"subject_email" TEXT,
"actor_id" TEXT,
"deleted_counts" JSONB NOT NULL DEFAULT '{}',
"anonymized_counts" JSONB NOT NULL DEFAULT '{}',
"snapshot_id" TEXT,
"reason" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "deletion_certificates_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "tombstone_entries" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"model" TEXT NOT NULL,
"target_field" TEXT NOT NULL,
"target_value" TEXT NOT NULL,
"action" TEXT NOT NULL,
"anonymized_fields" JSONB NOT NULL DEFAULT '{}',
"reason" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tombstone_entries_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "deletion_certificates_tenant_id_idx" ON "deletion_certificates"("tenant_id");
CREATE INDEX "deletion_certificates_subject_identity_id_idx" ON "deletion_certificates"("subject_identity_id");
CREATE INDEX "tombstone_entries_tenant_id_idx" ON "tombstone_entries"("tenant_id");
CREATE UNIQUE INDEX "tombstone_entries_tenant_id_model_target_value_key" ON "tombstone_entries"("tenant_id", "model", "target_value");
@@ -1,28 +0,0 @@
-- Backup-Portal-Lane: Job-Status für Portal-Restore, Export-jetzt und DSGVO-Zustellung.
-- GLOBAL (kein tenant_id-RLS, analog deletion_certificates) — Betreiber-/Plattform-Fähigkeit.
-- CreateTable
CREATE TABLE "backup_jobs" (
"id" TEXT NOT NULL,
"kind" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'queued',
"tenant_id" TEXT NOT NULL,
"tenant_slug" TEXT,
"subject_identity_id" TEXT,
"actor_id" TEXT,
"snapshot_id" TEXT,
"params" JSONB NOT NULL DEFAULT '{}',
"result" JSONB,
"error" TEXT,
"download_token" TEXT,
"download_expires_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "backup_jobs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "backup_jobs_download_token_key" ON "backup_jobs"("download_token");
CREATE INDEX "backup_jobs_tenant_id_idx" ON "backup_jobs"("tenant_id");
CREATE INDEX "backup_jobs_status_idx" ON "backup_jobs"("status");
@@ -1,13 +0,0 @@
-- Lane „Konfigurierbarer Backup-Zielspeicher": PlatformSetting (Singleton) um die
-- Zielspeicher-Konfiguration erweitern. Additiv, alle Spalten nullable bzw. mit
-- Default → kein Backfill nötig, bestehende Zeile bleibt gültig (Default 'local').
-- Das S3-Secret wird NUR verschlüsselt abgelegt (backup_s3_secret_key_enc, secret-crypto).
ALTER TABLE "platform_settings"
ADD COLUMN "backup_target" TEXT NOT NULL DEFAULT 'local',
ADD COLUMN "backup_local_dir" TEXT,
ADD COLUMN "backup_s3_endpoint" TEXT,
ADD COLUMN "backup_s3_bucket" TEXT,
ADD COLUMN "backup_s3_region" TEXT,
ADD COLUMN "backup_s3_access_key" TEXT,
ADD COLUMN "backup_s3_secret_key_enc" TEXT;
@@ -1,246 +0,0 @@
-- CreateEnum
CREATE TYPE "IncidentCategory" AS ENUM ('malware', 'phishing', 'unauthorized_access', 'data_loss', 'outage', 'physical', 'misconfiguration', 'supplier', 'prototype_customer_data', 'other');
-- CreateEnum
CREATE TYPE "IncidentStatus" AS ENUM ('neu', 'triage', 'in_bearbeitung', 'eingedaemmt', 'behoben', 'abgeschlossen', 'wiedereroeffnet');
-- AlterTable
ALTER TABLE "tenant_settings" ADD COLUMN "nis2_category" TEXT NOT NULL DEFAULT 'keine';
-- CreateTable
CREATE TABLE "incidents" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"ref_no" TEXT NOT NULL,
"title" TEXT NOT NULL,
"description" TEXT,
"source" TEXT NOT NULL DEFAULT 'manual',
"reporter_name" TEXT,
"reporter_contact" TEXT,
"occurred_at" TIMESTAMP(3),
"detected_at" TIMESTAMP(3),
"reported_at" TIMESTAMP(3),
"category" "IncidentCategory" NOT NULL DEFAULT 'other',
"impact_c" INTEGER NOT NULL DEFAULT 0,
"impact_i" INTEGER NOT NULL DEFAULT 0,
"impact_a" INTEGER NOT NULL DEFAULT 0,
"urgency" INTEGER NOT NULL DEFAULT 2,
"affected_data_categories" TEXT[] DEFAULT ARRAY[]::TEXT[],
"personal_data" BOOLEAN NOT NULL DEFAULT false,
"prototype_data" BOOLEAN NOT NULL DEFAULT false,
"severity" TEXT NOT NULL DEFAULT 'mittel',
"priority" TEXT NOT NULL DEFAULT 'mittel',
"owner_id" TEXT,
"assignee_id" TEXT,
"status" "IncidentStatus" NOT NULL DEFAULT 'neu',
"restricted" BOOLEAN NOT NULL DEFAULT false,
"nis2_relevant" BOOLEAN NOT NULL DEFAULT false,
"dsgvo_relevant" BOOLEAN NOT NULL DEFAULT false,
"report_status" TEXT NOT NULL DEFAULT 'none',
"erstmeldung_due_at" TIMESTAMP(3),
"folgemeldung_due_at" TIMESTAMP(3),
"abschluss_due_at" TIMESTAMP(3),
"dsgvo_due_at" TIMESTAMP(3),
"root_cause" TEXT,
"resolution" TEXT,
"closing_note" TEXT,
"lessons_learned" TEXT,
"created_by" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "incidents_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "incident_comments" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"incident_id" TEXT NOT NULL,
"author_id" TEXT,
"body" TEXT NOT NULL,
"internal" BOOLEAN NOT NULL DEFAULT false,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "incident_comments_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "incident_assets" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"incident_id" TEXT NOT NULL,
"asset_id" TEXT NOT NULL,
CONSTRAINT "incident_assets_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "incident_processes" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"incident_id" TEXT NOT NULL,
"process_id" TEXT NOT NULL,
CONSTRAINT "incident_processes_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "incident_risks" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"incident_id" TEXT NOT NULL,
"risk_id" TEXT NOT NULL,
CONSTRAINT "incident_risks_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "incident_controls" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"incident_id" TEXT NOT NULL,
"control_ref" TEXT NOT NULL,
CONSTRAINT "incident_controls_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "incident_measures" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"incident_id" TEXT NOT NULL,
"measure_id" TEXT NOT NULL,
CONSTRAINT "incident_measures_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "incident_attachments" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"incident_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"storage_key" TEXT,
"size" INTEGER,
"mime" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "incident_attachments_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "incidents_tenant_id_idx" ON "incidents"("tenant_id");
-- CreateIndex
CREATE INDEX "incidents_tenant_id_status_idx" ON "incidents"("tenant_id", "status");
-- CreateIndex
CREATE INDEX "incidents_tenant_id_severity_idx" ON "incidents"("tenant_id", "severity");
-- CreateIndex
CREATE UNIQUE INDEX "incidents_tenant_id_ref_no_key" ON "incidents"("tenant_id", "ref_no");
-- CreateIndex
CREATE INDEX "incident_comments_tenant_id_incident_id_idx" ON "incident_comments"("tenant_id", "incident_id");
-- CreateIndex
CREATE INDEX "incident_assets_tenant_id_idx" ON "incident_assets"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "incident_assets_incident_id_asset_id_key" ON "incident_assets"("incident_id", "asset_id");
-- CreateIndex
CREATE INDEX "incident_processes_tenant_id_idx" ON "incident_processes"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "incident_processes_incident_id_process_id_key" ON "incident_processes"("incident_id", "process_id");
-- CreateIndex
CREATE INDEX "incident_risks_tenant_id_idx" ON "incident_risks"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "incident_risks_incident_id_risk_id_key" ON "incident_risks"("incident_id", "risk_id");
-- CreateIndex
CREATE INDEX "incident_controls_tenant_id_idx" ON "incident_controls"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "incident_controls_incident_id_control_ref_key" ON "incident_controls"("incident_id", "control_ref");
-- CreateIndex
CREATE INDEX "incident_measures_tenant_id_idx" ON "incident_measures"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "incident_measures_incident_id_measure_id_key" ON "incident_measures"("incident_id", "measure_id");
-- CreateIndex
CREATE INDEX "incident_attachments_tenant_id_incident_id_idx" ON "incident_attachments"("tenant_id", "incident_id");
-- AddForeignKey
ALTER TABLE "incidents" ADD CONSTRAINT "incidents_owner_id_fkey" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incidents" ADD CONSTRAINT "incidents_assignee_id_fkey" FOREIGN KEY ("assignee_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_comments" ADD CONSTRAINT "incident_comments_incident_id_fkey" FOREIGN KEY ("incident_id") REFERENCES "incidents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_assets" ADD CONSTRAINT "incident_assets_incident_id_fkey" FOREIGN KEY ("incident_id") REFERENCES "incidents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_assets" ADD CONSTRAINT "incident_assets_asset_id_fkey" FOREIGN KEY ("asset_id") REFERENCES "assets"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_processes" ADD CONSTRAINT "incident_processes_incident_id_fkey" FOREIGN KEY ("incident_id") REFERENCES "incidents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_processes" ADD CONSTRAINT "incident_processes_process_id_fkey" FOREIGN KEY ("process_id") REFERENCES "processes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_risks" ADD CONSTRAINT "incident_risks_incident_id_fkey" FOREIGN KEY ("incident_id") REFERENCES "incidents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_risks" ADD CONSTRAINT "incident_risks_risk_id_fkey" FOREIGN KEY ("risk_id") REFERENCES "risks"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_controls" ADD CONSTRAINT "incident_controls_incident_id_fkey" FOREIGN KEY ("incident_id") REFERENCES "incidents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_measures" ADD CONSTRAINT "incident_measures_incident_id_fkey" FOREIGN KEY ("incident_id") REFERENCES "incidents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_measures" ADD CONSTRAINT "incident_measures_measure_id_fkey" FOREIGN KEY ("measure_id") REFERENCES "measures"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_attachments" ADD CONSTRAINT "incident_attachments_incident_id_fkey" FOREIGN KEY ("incident_id") REFERENCES "incidents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Row Level Security (zweite Verteidigungslinie, analog übrige Mandanten-Tabellen;
-- FORCE + WITH CHECK für den scharfen isms_app-Pfad, F-04). Lokal (Owner-Rolle ohne
-- FORCE) bleibt der Betrieb unverändert.
DO $$
DECLARE t text;
BEGIN
FOREACH t IN ARRAY ARRAY[
'incidents',
'incident_comments',
'incident_assets',
'incident_processes',
'incident_risks',
'incident_controls',
'incident_measures',
'incident_attachments'
] LOOP
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', t);
EXECUTE format('DROP POLICY IF EXISTS tenant_isolation ON %I', t);
EXECUTE format(
'CREATE POLICY tenant_isolation ON %I USING (tenant_id = current_setting(''app.tenant_id'', true)) WITH CHECK (tenant_id = current_setting(''app.tenant_id'', true))',
t
);
EXECUTE format('ALTER TABLE %I FORCE ROW LEVEL SECURITY', t);
END LOOP;
END $$;
@@ -1,43 +0,0 @@
-- Modul „Vorfälle" IM-C: Verknüpfung Nachweise + Abschluss-Felder (§8/§9).
--
-- 1) incidents: Wirksamkeit der Maßnahmen + optionaler Post-Incident-Review
-- (Kurzbericht, speist das Management-Review als Feld/Notiz).
-- 2) incident_evidence: n:m-Verknüpfung Vorfall ↔ zentrales Evidence-Modell
-- (Beweissicherung, ISO A.5.28).
-- AlterTable
ALTER TABLE "incidents" ADD COLUMN "measures_effectiveness" TEXT,
ADD COLUMN "post_incident_review" TEXT;
-- CreateTable
CREATE TABLE "incident_evidence" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"incident_id" TEXT NOT NULL,
"evidence_id" TEXT NOT NULL,
"note" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "incident_evidence_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "incident_evidence_tenant_id_idx" ON "incident_evidence"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "incident_evidence_incident_id_evidence_id_key" ON "incident_evidence"("incident_id", "evidence_id");
-- AddForeignKey
ALTER TABLE "incident_evidence" ADD CONSTRAINT "incident_evidence_incident_id_fkey" FOREIGN KEY ("incident_id") REFERENCES "incidents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "incident_evidence" ADD CONSTRAINT "incident_evidence_evidence_id_fkey" FOREIGN KEY ("evidence_id") REFERENCES "evidence"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Row Level Security (analog übrige Mandanten-Tabellen; FORCE + WITH CHECK für den
-- scharfen isms_app-Pfad, F-04). Lokal (Owner-Rolle ohne FORCE) unverändert.
ALTER TABLE "incident_evidence" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "incident_evidence";
CREATE POLICY tenant_isolation ON "incident_evidence"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "incident_evidence" FORCE ROW LEVEL SECURITY;
@@ -1,67 +0,0 @@
-- IM-D — E-Mail-to-Ticket (Inbound) + Provisionierung.
-- 1) incidents.inbound_message_id: Idempotenz gegen Doppel-Ticket beim erneuten
-- IMAP-Abholen derselben Mail (Dedupe per Message-ID, KONZEPT §2).
-- 2) incident_intake_config: mandantengebundene Intake-Konfiguration (RLS).
-- 3) incident_inbound_review: plattformweite Review-Queue (KEINE RLS — tenant-los).
-- AlterTable
ALTER TABLE "incidents" ADD COLUMN "inbound_message_id" TEXT;
-- CreateIndex
CREATE INDEX "incidents_inbound_message_id_idx" ON "incidents"("inbound_message_id");
-- CreateTable (mandantengebunden, RLS)
CREATE TABLE "incident_intake_config" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"token" TEXT NOT NULL,
"allowlist_domains" TEXT[] DEFAULT ARRAY[]::TEXT[],
"source_address" TEXT,
"status" TEXT NOT NULL DEFAULT 'weiterleitung_ausstehend',
"verified_at" TIMESTAMP(3),
"notify_email" TEXT,
"notify_locale" TEXT NOT NULL DEFAULT 'de',
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "incident_intake_config_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "incident_intake_config_tenant_id_key" ON "incident_intake_config"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "incident_intake_config_token_key" ON "incident_intake_config"("token");
-- CreateTable (plattformweit, KEINE RLS — tenant-los)
CREATE TABLE "incident_inbound_review" (
"id" TEXT NOT NULL,
"message_id" TEXT,
"sender" TEXT NOT NULL,
"subject" TEXT,
"recipient" TEXT,
"token" TEXT,
"reason" TEXT NOT NULL,
"status" TEXT NOT NULL DEFAULT 'offen',
"tenant_id" TEXT,
"received_at" TIMESTAMP(3) NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "incident_inbound_review_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "incident_inbound_review_status_idx" ON "incident_inbound_review"("status");
-- CreateIndex
CREATE INDEX "incident_inbound_review_message_id_idx" ON "incident_inbound_review"("message_id");
-- Row Level Security auf der mandantengebundenen Intake-Konfiguration (analog übrige
-- Mandanten-Tabellen; FORCE + WITH CHECK für den scharfen isms_app-Pfad, F-04). Die
-- Review-Queue bleibt bewusst OHNE RLS (plattformweite Betreiber-Sicht).
ALTER TABLE "incident_intake_config" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "incident_intake_config";
CREATE POLICY tenant_isolation ON "incident_intake_config"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "incident_intake_config" FORCE ROW LEVEL SECURITY;
@@ -1,67 +0,0 @@
-- CreateEnum
CREATE TYPE "Framework" AS ENUM ('ISO_27001', 'TISAX');
-- DropIndex
DROP INDEX "policy_package_states_tenant_id_key";
-- DropIndex
DROP INDEX "policy_template_versions_version_key";
-- AlterTable
ALTER TABLE "policy_package_states" ADD COLUMN "framework" "Framework" NOT NULL DEFAULT 'TISAX';
-- AlterTable
ALTER TABLE "policy_requirements" ADD COLUMN "framework" "Framework" NOT NULL DEFAULT 'TISAX';
-- AlterTable
ALTER TABLE "policy_template_versions" ADD COLUMN "framework" "Framework" NOT NULL DEFAULT 'TISAX';
-- CreateTable
CREATE TABLE "tenant_frameworks" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"framework" "Framework" NOT NULL,
"is_primary" BOOLEAN NOT NULL DEFAULT false,
"config" JSONB,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tenant_frameworks_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "tenant_frameworks_tenant_id_idx" ON "tenant_frameworks"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "tenant_frameworks_tenant_id_framework_key" ON "tenant_frameworks"("tenant_id", "framework");
-- CreateIndex
CREATE UNIQUE INDEX "policy_package_states_tenant_id_framework_key" ON "policy_package_states"("tenant_id", "framework");
-- CreateIndex
CREATE INDEX "policy_requirements_tenant_id_framework_idx" ON "policy_requirements"("tenant_id", "framework");
-- CreateIndex
CREATE UNIQUE INDEX "policy_template_versions_framework_version_key" ON "policy_template_versions"("framework", "version");
-- ─────────────────────────────────────────────────────────────────────────────
-- AP1 — Framework-Dimension: Backfill + RLS (manuell an das Diff-DDL angehängt).
-- ─────────────────────────────────────────────────────────────────────────────
-- Backfill: Bestandsmandanten führen bisher TISAX → je Mandant eine TenantFramework-
-- Zeile (isPrimary=true) anlegen. Idempotent über den (tenant_id, framework)-Unique.
-- Die framework-Spalten in policy_requirements/policy_package_states/
-- policy_template_versions sind bereits über DEFAULT 'TISAX' backfilled (s. o.).
INSERT INTO "tenant_frameworks" ("id", "tenant_id", "framework", "is_primary", "created_at")
SELECT gen_random_uuid()::text, t."id", 'TISAX', true, CURRENT_TIMESTAMP
FROM "tenants" t
ON CONFLICT ("tenant_id", "framework") DO NOTHING;
-- Row Level Security auf der mandantengebundenen Framework-Tabelle (F-04-Muster:
-- FORCE + WITH CHECK für den scharfen isms_app-Pfad, analog übrige Mandanten-Tabellen).
ALTER TABLE "tenant_frameworks" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "tenant_frameworks";
CREATE POLICY tenant_isolation ON "tenant_frameworks"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "tenant_frameworks" FORCE ROW LEVEL SECURITY;
@@ -1,37 +0,0 @@
-- CreateTable
CREATE TABLE "soa_entries" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"framework" "Framework" NOT NULL,
"control" TEXT NOT NULL,
"title" TEXT,
"applicable" BOOLEAN NOT NULL DEFAULT true,
"justification" TEXT NOT NULL DEFAULT '',
"source" TEXT,
"implementation_status" TEXT NOT NULL DEFAULT 'geplant',
"owner_id" TEXT,
"policy_code" TEXT,
"evidence_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "soa_entries_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "soa_entries_tenant_id_idx" ON "soa_entries"("tenant_id");
-- CreateIndex
CREATE INDEX "soa_entries_tenant_id_framework_idx" ON "soa_entries"("tenant_id", "framework");
-- CreateIndex
CREATE UNIQUE INDEX "soa_entries_tenant_id_framework_control_key" ON "soa_entries"("tenant_id", "framework", "control");
-- AP3 — Row Level Security auf der SoA-Tabelle (F-04: FORCE + WITH CHECK).
ALTER TABLE "soa_entries" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "soa_entries";
CREATE POLICY tenant_isolation ON "soa_entries"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "soa_entries" FORCE ROW LEVEL SECURITY;
@@ -1,175 +0,0 @@
-- CreateTable
CREATE TABLE "kpis" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"data_source" TEXT,
"unit" TEXT,
"target" TEXT,
"cadence" TEXT NOT NULL DEFAULT 'monatlich',
"owner_id" TEXT,
"active" BOOLEAN NOT NULL DEFAULT true,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "kpis_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "kpi_values" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"kpi_id" TEXT NOT NULL,
"period" TEXT NOT NULL,
"value" TEXT NOT NULL,
"note" TEXT,
"recorded_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "kpi_values_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "management_reviews" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"review_date" TIMESTAMP(3) NOT NULL,
"status" TEXT NOT NULL DEFAULT 'entwurf',
"inputs" TEXT,
"results" TEXT,
"owner_id" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "management_reviews_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "management_review_decisions" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"review_id" TEXT NOT NULL,
"decision" TEXT NOT NULL,
"owner_id" TEXT,
"due_date" TIMESTAMP(3),
"status" TEXT NOT NULL DEFAULT 'offen',
CONSTRAINT "management_review_decisions_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "nonconformities" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"ref_no" TEXT NOT NULL,
"source" TEXT NOT NULL,
"description" TEXT NOT NULL,
"immediate_correction" TEXT,
"status" TEXT NOT NULL DEFAULT 'offen',
"owner_id" TEXT,
"detected_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"closed_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "nonconformities_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "corrective_actions" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"nonconformity_id" TEXT NOT NULL,
"root_cause" TEXT,
"action" TEXT NOT NULL,
"owner_id" TEXT,
"due_date" TIMESTAMP(3),
"status" TEXT NOT NULL DEFAULT 'geplant',
"effectiveness_check" TEXT,
"effectiveness_confirmed_at" TIMESTAMP(3),
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "corrective_actions_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "kpis_tenant_id_idx" ON "kpis"("tenant_id");
-- CreateIndex
CREATE INDEX "kpi_values_tenant_id_idx" ON "kpi_values"("tenant_id");
-- CreateIndex
CREATE INDEX "kpi_values_kpi_id_idx" ON "kpi_values"("kpi_id");
-- CreateIndex
CREATE UNIQUE INDEX "kpi_values_tenant_id_kpi_id_period_key" ON "kpi_values"("tenant_id", "kpi_id", "period");
-- CreateIndex
CREATE INDEX "management_reviews_tenant_id_idx" ON "management_reviews"("tenant_id");
-- CreateIndex
CREATE INDEX "management_review_decisions_tenant_id_idx" ON "management_review_decisions"("tenant_id");
-- CreateIndex
CREATE INDEX "management_review_decisions_review_id_idx" ON "management_review_decisions"("review_id");
-- CreateIndex
CREATE INDEX "nonconformities_tenant_id_idx" ON "nonconformities"("tenant_id");
-- CreateIndex
CREATE UNIQUE INDEX "nonconformities_tenant_id_ref_no_key" ON "nonconformities"("tenant_id", "ref_no");
-- CreateIndex
CREATE INDEX "corrective_actions_tenant_id_idx" ON "corrective_actions"("tenant_id");
-- CreateIndex
CREATE INDEX "corrective_actions_nonconformity_id_idx" ON "corrective_actions"("nonconformity_id");
-- AddForeignKey
ALTER TABLE "kpi_values" ADD CONSTRAINT "kpi_values_kpi_id_fkey" FOREIGN KEY ("kpi_id") REFERENCES "kpis"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "management_review_decisions" ADD CONSTRAINT "management_review_decisions_review_id_fkey" FOREIGN KEY ("review_id") REFERENCES "management_reviews"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "corrective_actions" ADD CONSTRAINT "corrective_actions_nonconformity_id_fkey" FOREIGN KEY ("nonconformity_id") REFERENCES "nonconformities"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AP4 — Row Level Security (F-04: FORCE + WITH CHECK) für die neuen Mandanten-Tabellen.
ALTER TABLE "kpis" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "kpis";
CREATE POLICY tenant_isolation ON "kpis"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "kpis" FORCE ROW LEVEL SECURITY;
ALTER TABLE "kpi_values" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "kpi_values";
CREATE POLICY tenant_isolation ON "kpi_values"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "kpi_values" FORCE ROW LEVEL SECURITY;
ALTER TABLE "management_reviews" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "management_reviews";
CREATE POLICY tenant_isolation ON "management_reviews"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "management_reviews" FORCE ROW LEVEL SECURITY;
ALTER TABLE "management_review_decisions" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "management_review_decisions";
CREATE POLICY tenant_isolation ON "management_review_decisions"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "management_review_decisions" FORCE ROW LEVEL SECURITY;
ALTER TABLE "nonconformities" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "nonconformities";
CREATE POLICY tenant_isolation ON "nonconformities"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "nonconformities" FORCE ROW LEVEL SECURITY;
ALTER TABLE "corrective_actions" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "corrective_actions";
CREATE POLICY tenant_isolation ON "corrective_actions"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "corrective_actions" FORCE ROW LEVEL SECURITY;
@@ -1,68 +0,0 @@
-- AlterTable
ALTER TABLE "policy_documents" ADD COLUMN "next_review_at" TIMESTAMP(3),
ADD COLUMN "review_cycle" TEXT;
-- CreateTable
CREATE TABLE "policy_acknowledgements" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"policy_document_id" TEXT NOT NULL,
"version" TEXT NOT NULL,
"user_id" TEXT NOT NULL,
"acknowledged_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_acknowledgements_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "policy_document_versions" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"policy_document_id" TEXT NOT NULL,
"version" TEXT NOT NULL,
"title" TEXT NOT NULL,
"change_note" TEXT,
"created_by" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "policy_document_versions_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "policy_acknowledgements_tenant_id_idx" ON "policy_acknowledgements"("tenant_id");
-- CreateIndex
CREATE INDEX "policy_acknowledgements_policy_document_id_version_idx" ON "policy_acknowledgements"("policy_document_id", "version");
-- CreateIndex
CREATE UNIQUE INDEX "policy_acknowledgements_tenant_id_policy_document_id_user_i_key" ON "policy_acknowledgements"("tenant_id", "policy_document_id", "user_id", "version");
-- CreateIndex
CREATE INDEX "policy_document_versions_tenant_id_idx" ON "policy_document_versions"("tenant_id");
-- CreateIndex
CREATE INDEX "policy_document_versions_policy_document_id_idx" ON "policy_document_versions"("policy_document_id");
-- CreateIndex
CREATE INDEX "policy_documents_tenant_id_next_review_at_idx" ON "policy_documents"("tenant_id", "next_review_at");
-- AddForeignKey
ALTER TABLE "policy_acknowledgements" ADD CONSTRAINT "policy_acknowledgements_policy_document_id_fkey" FOREIGN KEY ("policy_document_id") REFERENCES "policy_documents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "policy_document_versions" ADD CONSTRAINT "policy_document_versions_policy_document_id_fkey" FOREIGN KEY ("policy_document_id") REFERENCES "policy_documents"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AP5 — Row Level Security (F-04) für die neuen Mandanten-Tabellen.
ALTER TABLE "policy_acknowledgements" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "policy_acknowledgements";
CREATE POLICY tenant_isolation ON "policy_acknowledgements"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "policy_acknowledgements" FORCE ROW LEVEL SECURITY;
ALTER TABLE "policy_document_versions" ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS tenant_isolation ON "policy_document_versions";
CREATE POLICY tenant_isolation ON "policy_document_versions"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "policy_document_versions" FORCE ROW LEVEL SECURITY;
@@ -1,35 +0,0 @@
-- CreateTable
CREATE TABLE "process_dependencies" (
"id" TEXT NOT NULL,
"tenant_id" TEXT NOT NULL,
"source_process_id" TEXT NOT NULL,
"target_process_id" TEXT NOT NULL,
"note" TEXT,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL,
CONSTRAINT "process_dependencies_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "process_dependencies_tenant_id_idx" ON "process_dependencies"("tenant_id");
-- CreateIndex
CREATE INDEX "process_dependencies_target_process_id_idx" ON "process_dependencies"("target_process_id");
-- CreateIndex
CREATE UNIQUE INDEX "process_dependencies_source_process_id_target_process_id_key" ON "process_dependencies"("source_process_id", "target_process_id");
-- AddForeignKey
ALTER TABLE "process_dependencies" ADD CONSTRAINT "process_dependencies_source_process_id_fkey" FOREIGN KEY ("source_process_id") REFERENCES "processes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "process_dependencies" ADD CONSTRAINT "process_dependencies_target_process_id_fkey" FOREIGN KEY ("target_process_id") REFERENCES "processes"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Row Level Security: Mandantentrennung (analog übrige tenant-scoped Tabellen)
ALTER TABLE "process_dependencies" ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON "process_dependencies"
USING ("tenant_id" = current_setting('app.tenant_id', true))
WITH CHECK ("tenant_id" = current_setting('app.tenant_id', true));
ALTER TABLE "process_dependencies" FORCE ROW LEVEL SECURITY;