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:
@@ -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;
|
||||
Reference in New Issue
Block a user