Files
msolarczekandClaude Opus 5 c8e6f30a27
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s
Basis: Certvia dev@a48c5fb als Fundament für Craftvia
Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-14 11:05:39 +02:00

55 lines
2.7 KiB
SQL
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ============================================================================
-- 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;