feat(quick-260911-nke): current_user_id(), Benutzerdimension in den Regeln, forTenant() mit userId — ein Pfad
- Neue Migration 20260911120000_rls_user_dimension_personal_tables: current_user_id() (NULLIF-gefaltet), zehn persoenliche Tabellen umgestellt (acht als eine Regel, SearchProvider/TenderRssFeedSource als je vier befehlsgetrennte Regeln), vier Verwaltungstabellen bewusst unveraendert. Lokal angewendet (migrate deploy, Prisma-Binary aus apps/api/node_modules/.bin), schema.prisma unveraendert. - forTenant(prisma, tenantId, userId?): beide set_config in EINER getaggten Anweisung, $transaction-Array bleibt bei zwei Eintraegen (WINDOWS #20), Leerstring ohne Benutzer statt Weglassen. - tender-saved-search.service.ts: alle vier forTenant()-Aufrufe reichen userId durch; Detektor-Regex bestaetigt 4 Treffer. - rls-scratch-check.mjs: current_user_id() aus der neuen Migration geschnitten (nicht getippt), drei Funktionsfaelle gemessen, neue runUserDimensionChecks() mit generiertem Client fuer TenderSavedSearch (vier Wahrheiten + Spaltenabgleich), die alte Loch-Pruefung tendersavedsearch-fremder-nutzer-desselben-mandanten-sichtbar umgedreht (alte Messung unter neuem Namen erhalten, neue Umkehrung MIT Benutzer). sqlStateOf() um Message-Fallback ergaenzt (RLS-Ablehnung ueber generierten Client traegt den SQLSTATE nur im Fehlertext, nicht in .meta.code). - Baseline: 1020/62 Tests, Typpruefung sauber, Werkzeug 146/146 bestanden. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -107,16 +107,55 @@ import { PrismaClient } from '@prisma/client';
|
||||
* Transaktion gilt weiterhin: vor jedem neuen Fall erneut pruefen, nicht
|
||||
* von hier abschreiben — eine andere Lastform oder ein anderer Pool koennte
|
||||
* ein anderes Ergebnis liefern.
|
||||
*
|
||||
* BENUTZERDIMENSION (Etappe 3b, 260911-nke):
|
||||
*
|
||||
* `forTenant()` bekommt einen OPTIONALEN dritten Parameter `userId` statt
|
||||
* eines Schwesterhelfers (`forTenantAndUser()`). Grund: der Detektor der
|
||||
* Bestandsaufnahme (`rls-access-inventory.spec.ts`) erkennt gebundene
|
||||
* Aufrufstellen ueber den Regex `const X = forTenant(` — ein anders
|
||||
* benannter Schwesterhelfer waere fuer ihn UNSICHTBAR, jeder damit
|
||||
* gebundene Zugriff wuerde faelschlich als ungebunden gezaehlt. Ein
|
||||
* dritter Parameter aendert am Match des Regex nichts, weil er nur den
|
||||
* Funktionsnamen und das oeffnende `(` prueft. Praezedenz fuer "Helfer
|
||||
* erweitern statt zweiten bauen": `withTenantTransaction()` oben, das
|
||||
* ebenfalls keinen Zwilling bekam.
|
||||
*
|
||||
* Ohne `userId` wird `app.current_user` auf den LEERSTRING gesetzt, nicht
|
||||
* weggelassen. Grund: `set_config(..., true)` gilt nur transaktionslokal
|
||||
* (siehe WINDOWS-#20-Herleitung oben) — ein Aufruf ohne Benutzer koennte
|
||||
* sonst theoretisch einen Benutzer aus einer fruaheren Transaktion
|
||||
* DERSELBEN Verbindung erben, sollte spaeter jemand `local=false`
|
||||
* einfuehren. Der Leerstring schliesst das aus. `current_user_id()`
|
||||
* (neue Migration 20260911120000) faltet den Leerstring per `NULLIF` auf
|
||||
* NULL — die Regeln der zehn persoenlichen Tabellen behandeln "ungesetzt"
|
||||
* und "leer" dadurch gleich.
|
||||
*
|
||||
* Beide `set_config`-Aufrufe stehen in EINER getaggten Anweisung
|
||||
* (kommasepariert) — das `$transaction`-Array behaelt weiterhin GENAU ZWEI
|
||||
* Eintraege (Kontext-Anweisung, eigentliche Abfrage), das WINDOWS-#20-Muster
|
||||
* bleibt unangetastet.
|
||||
*
|
||||
* Wer den Benutzer setzt: NUR Nutzer-CRUD-Aufrufer (die zehn persoenlichen
|
||||
* Tabellen betreffende Methoden in calendar/dashboard/favorites/tenders).
|
||||
* Hintergrunddienste (`tender-digest.scheduler.ts`) und Verwaltungswege
|
||||
* (ldap, groups, user, tenant, auth, dkv, module-registry) rufen weiterhin
|
||||
* OHNE Benutzer — die `IS NULL OR`-Form der Regeln macht das zu einer
|
||||
* bewussten Eigenschaft (Admin/Hintergrunddienst sieht den ganzen
|
||||
* Mandanten), nicht zu einer Luecke. `withTenantTransaction()` bekommt
|
||||
* KEINEN dritten Parameter: kein Nutzer-CRUD-Aufrufer nutzt diese Funktion
|
||||
* (nur `groups`, ein Verwaltungsweg) — ein unbenutzter Parameter waere
|
||||
* Spekulation ohne heutigen Aufrufer.
|
||||
*/
|
||||
export function forTenant(prisma: PrismaClient, tenantId: string) {
|
||||
export function forTenant(prisma: PrismaClient, tenantId: string, userId?: string) {
|
||||
return prisma.$extends({
|
||||
query: {
|
||||
$allOperations({ args, query }: { args: any; query: (args: any) => any }) {
|
||||
const setTenantContext = (prisma as any)
|
||||
.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true)`;
|
||||
const setContext = (prisma as any)
|
||||
.$executeRaw`SELECT set_config('app.current_tenant', ${tenantId}, true), set_config('app.current_user', ${userId ?? ''}, true)`;
|
||||
|
||||
return (prisma as any)
|
||||
.$transaction([setTenantContext, query(args)])
|
||||
.$transaction([setContext, query(args)])
|
||||
.then((results: any[]) => results[1]);
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user