92e8eaffa5
- Second, deliberately separate migration (pure hand-SQL, no Prisma- generated DDL): ENABLE/FORCE ROW LEVEL SECURITY plus a tenant_isolation_policy for each of the three new tables, following the pattern of 20260618112133_rls_policies (Auth-Kerntabellen) rather than the RLS-exempt Tender* app-layer tables - Group/ModuleGrant compare tenantId directly against current_tenant_id(); GroupMembership has no own tenantId and follows the PasswordResetToken join pattern (groupId IN (SELECT id FROM Group WHERE tenantId = ...)) - migration-sql.spec.ts extended with a second describe block covering both migration files (6x ROW LEVEL SECURITY, 3x CREATE POLICY, the join vs. direct-comparison shape) - Re-ran the Task-2 end-to-end proof after applying this migration: identical result (USER without grant 403 + empty list, USER with direct grant 200 + slug present, ADMIN 200) — the app's DB role (tessera) is a Postgres superuser with rolbypassrls=true, so it bypasses RLS as documented as an acceptable outcome by the plan; RLS remains the defense-in-depth net for any future non-superuser connection
38 lines
1.6 KiB
SQL
38 lines
1.6 KiB
SQL
-- Phase 15 (T-15-11, 15-RESEARCH.md Pitfall 4 / Annahme A1) — RLS für
|
|
-- Group, GroupMembership und ModuleGrant.
|
|
--
|
|
-- Begründung: diese drei Tabellen steuern unmittelbar, wer auf was
|
|
-- zugreifen darf, und folgen damit dem Muster der Auth-Kerntabellen
|
|
-- (User, LdapConfig, LdapFieldMapping, PasswordResetToken — siehe
|
|
-- 20260618112133_rls_policies), NICHT dem App-Layer-Muster der
|
|
-- Tender*-Tabellen (die bewusst ohne RLS bleiben, D-03). RLS ist hier
|
|
-- ein zweites Sicherheitsnetz für den Fall, dass irgendwo ein
|
|
-- `where: { tenantId }` vergessen wird — ModuleAccessService liest über
|
|
-- unskaliertes `this.prisma`, ohne dass `app.current_tenant` gesetzt
|
|
-- ist; dieser zweite Netz-Layer greift nur, wenn die Datenbankrolle RLS
|
|
-- nicht ohnehin umgeht.
|
|
|
|
-- Enable RLS on Group table (direkte tenantId-Spalte)
|
|
ALTER TABLE "Group" ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE "Group" FORCE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_policy ON "Group"
|
|
USING ("tenantId" = current_tenant_id());
|
|
|
|
-- Enable RLS on GroupMembership table (keine eigene tenantId-Spalte,
|
|
-- Join-Muster wie PasswordResetToken -> User)
|
|
ALTER TABLE "GroupMembership" ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE "GroupMembership" FORCE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_policy ON "GroupMembership"
|
|
USING ("groupId" IN (
|
|
SELECT "id" FROM "Group" WHERE "tenantId" = current_tenant_id()
|
|
));
|
|
|
|
-- Enable RLS on ModuleGrant table (direkte tenantId-Spalte)
|
|
ALTER TABLE "ModuleGrant" ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE "ModuleGrant" FORCE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation_policy ON "ModuleGrant"
|
|
USING ("tenantId" = current_tenant_id());
|