- AGENTS.md und README.md auf Craftvia umgeschrieben (Regeln, Andockpunkte, Stack mit Garage, tsx-Tests, Gate, Demo-Logins) - ISMS-Dokumente entfernt (SPEC, Prototypen, Lane-Prompts, Übergaben, Konzepte Incidents/Framework); Fundament-Doku (Deploy, Sicherheit, Backup, Identity) bleibt - Fundament-Tests an Craftvia-Rollen/Branding angepasst, Resttreffer certvia/isms in Skripten und Kommentaren bereinigt Gate: prisma generate, migrate status, tsc, lint, build, 22/22 Testskripte grün. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
503 lines
17 KiB
PL/PgSQL
503 lines
17 KiB
PL/PgSQL
-- 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, unverändert aus dem 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');
|