feat(260929-dzu): persoenliche eigene Module je Benutzer (API, Migration, Zeilenschutz)
- ownerUserId (NULL = gemeinsam, sonst persoenlich) mit Zeilenschutz nach Muster SearchProvider - GET nur gemeinsame + eigene, fremde persoenliche Eintraege 404 - POST fuer jeden Benutzer, shared nur fuer Administratoren (403) - PATCH/DELETE: persoenlich nur Besitzer, gemeinsam nur Administrator - Zugriffsklassifikation nachgemessen: 61/223/6 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
-- 260929-dzu — Eigene Module fuer jeden Benutzer: persoenliche Eintraege.
|
||||
--
|
||||
-- Zweck: jeder Benutzer darf eigene Seitenleisten-Eintraege anlegen, die nur
|
||||
-- er selbst sieht. Die Spalte "ownerUserId" unterscheidet: NULL = gemeinsamer
|
||||
-- Eintrag (vom Administrator, fuer alle sichtbar, bisheriges Verhalten),
|
||||
-- gesetzt = persoenlicher Eintrag dieses Benutzers. Faellt der Benutzer weg,
|
||||
-- fallen seine Eintraege mit (ON DELETE CASCADE). Bestehende Zeilen bleiben
|
||||
-- gemeinsam (NULL).
|
||||
--
|
||||
-- Zeilenschutz: Muster "SearchProvider" (20260911120000_rls_user_dimension_
|
||||
-- personal_tables) — Spalte mit NULL = gemeinsame Zeile. Die eine Regel
|
||||
-- "tenant_isolation_policy" (aus 20260929120000, ohne Benutzerdimension) wird
|
||||
-- durch vier nach Befehl getrennte Regeln ersetzt (Praezedenz 260910-jab (3)):
|
||||
-- ein einzelner USING-Ausdruck, der die gemeinsame Zeile zum Lesen einschliesst,
|
||||
-- wuerde sie sonst auch zum Aendern/Entfernen freigeben.
|
||||
-- SELECT: Mandant UND (kein Benutzer gesetzt ODER gemeinsame Zeile ODER
|
||||
-- eigene Zeile).
|
||||
-- INSERT/UPDATE/DELETE: Mandant UND (kein Benutzer gesetzt ODER eigene
|
||||
-- Zeile). Ein Benutzerkontext kann gemeinsame Zeilen also NICHT
|
||||
-- schreiben; der Administrator-Weg fuer gemeinsame Eintraege bindet
|
||||
-- deshalb ohne Benutzer (`forTenant(prisma, tenantId)`), die
|
||||
-- Rollenpruefung liegt im Controller/Dienst.
|
||||
-- Die Regelnamen sind neu (vier statt eine), rls-coverage.spec.ts fordert nur
|
||||
-- mindestens eine Regel je Tabelle mit eingeschaltetem RLS.
|
||||
--
|
||||
-- Rechte fuer tessera_app kommen ueber ALTER DEFAULT PRIVILEGES aus
|
||||
-- 20260909130000_rls_app_role — hier nichts zu tun.
|
||||
--
|
||||
-- WICHTIG: wie alle RLS-Regeln dieses Schemas wirken diese erst, wenn die
|
||||
-- Anwendung als Rolle ohne Umgehungsrecht verbindet (Schalter heute AUS, siehe
|
||||
-- docs/mandantentrennung-datenbankrolle.md). Bis dahin tragen die
|
||||
-- Anwendungspruefungen im Dienst den Schutz allein.
|
||||
|
||||
ALTER TABLE "CustomModule" ADD COLUMN "ownerUserId" TEXT;
|
||||
|
||||
CREATE INDEX "CustomModule_tenantId_ownerUserId_idx" ON "CustomModule"("tenantId", "ownerUserId");
|
||||
|
||||
ALTER TABLE "CustomModule" ADD CONSTRAINT "CustomModule_ownerUserId_fkey"
|
||||
FOREIGN KEY ("ownerUserId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
DROP POLICY tenant_isolation_policy ON "CustomModule";
|
||||
|
||||
CREATE POLICY tenant_user_read_policy ON "CustomModule"
|
||||
FOR SELECT
|
||||
USING (
|
||||
"tenantId" = current_tenant_id()
|
||||
AND (
|
||||
current_user_id() IS NULL
|
||||
OR "ownerUserId" IS NULL
|
||||
OR "ownerUserId" = current_user_id()
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY tenant_user_insert_policy ON "CustomModule"
|
||||
FOR INSERT
|
||||
WITH CHECK (
|
||||
"tenantId" = current_tenant_id()
|
||||
AND (current_user_id() IS NULL OR "ownerUserId" = current_user_id())
|
||||
);
|
||||
|
||||
CREATE POLICY tenant_user_update_policy ON "CustomModule"
|
||||
FOR UPDATE
|
||||
USING (
|
||||
"tenantId" = current_tenant_id()
|
||||
AND (current_user_id() IS NULL OR "ownerUserId" = current_user_id())
|
||||
)
|
||||
WITH CHECK (
|
||||
"tenantId" = current_tenant_id()
|
||||
AND (current_user_id() IS NULL OR "ownerUserId" = current_user_id())
|
||||
);
|
||||
|
||||
CREATE POLICY tenant_user_delete_policy ON "CustomModule"
|
||||
FOR DELETE
|
||||
USING (
|
||||
"tenantId" = current_tenant_id()
|
||||
AND (current_user_id() IS NULL OR "ownerUserId" = current_user_id())
|
||||
);
|
||||
Reference in New Issue
Block a user