feat(260911-e2s): Benutzerzaehler im TenantController binden (Fan-out je Mandant)
findAll/findOne/remove zaehlen Benutzer je Mandant jetzt ueber drei
gebundene Aufrufstellen (tenantPrisma.user.count mit where: { tenantId
}, in remove zusaetzlich isActive: true) statt ueber den
Relationszaehler, der nach dem Scharfschalten unbemerkt unter der
Regel von User gelaufen waere (260911-e2s, Aufgabe 1, Pruefungen 5-7).
Fan-out-Muster aus UserService.findAllForPlatformAdmin uebernommen; die
vier tenant-Zugriffe bleiben ungebunden (Tenant ohne Regel). Antwortform,
Meldungen und Statuscodes unveraendert.
tenant.controller.spec.ts legt die Testlage aus dem Nichts an (20
Faelle): Zwei-Klienten-Nachweis ueber __makeBoundClient, Rollen-
Metadaten-Test (Klasse SUPER_ADMIN, kein Handler ueberschreibt), Wachhund
gegen mehrfache Klientenerzeugung. Falsifizierungsnachweis durchgefuehrt:
der probeweise ungebundene Zaehler in findOne macht 2 Faelle rot mit
"Cannot read properties of undefined (reading 'count')" — die dkv-Form
der Falsifizierung, nicht nur eine falsche Zahl —, danach zurueckgenommen.
Klassifikation und Entwicklungsanleitung nachgezogen: 64 Paare (ein
neues, tenant.controller.ts/user), Uebersichtszeile 8/3, Klassen-
Verteilung 32 muss-mandantengebunden, Erkennungsluecke fuer
Relationseinbindungen im Kopf der Bestandsaufnahme benannt, "Zwei
belegte Befunde" und "Was diese Etappe NICHT entscheidet" (erster
Punkt aufgeloest). Beide Dokument-Falsifizierungsnachweise durchgefuehrt
(falsche Klasse macht rls-access-inventory.spec.ts rot, falsche
Uebersichtszahl macht das herleitende Gate rot), zurueckgenommen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
This commit is contained in:
@@ -145,13 +145,13 @@ Details dazu im Abschnitt [Das Modulsystem](#das-modulsystem).
|
||||
zeigt intern auf `http://api:3001`) auf.
|
||||
2. Die Anfrage trifft in `apps/api/src/main.ts` auf die globale `ValidationPipe` und läuft dann
|
||||
durch die drei global registrierten `APP_GUARD`s aus `app.module.ts`, in genau dieser
|
||||
Reihenfolge: `JwtAuthGuard` (Auth) → `TenantGuard` (setzt `req.tenantId`/`req.tenantPrisma`
|
||||
Reihenfolge: `JwtAuthGuard` (Auth) → `TenantGuard` (setzt `req.tenantId`
|
||||
aus dem JWT) → `RolesGuard` (prüft `@Roles()`).
|
||||
3. Trägt der Controller zusätzlich `@UseModule('slug')`, prüft anschließend `ModuleGuard`
|
||||
(`apps/api/src/module-registry/module.guard.ts`) Modulzugriff über `ModuleAccessService`.
|
||||
4. Der Controller ruft den zugehörigen Service auf, der über `PrismaService`
|
||||
(`apps/api/src/prisma/prisma.service.ts`) oder — für mandantensensible Tabellen — über den
|
||||
tenant-gescopten Client aus `req.tenantPrisma` auf Postgres zugreift.
|
||||
(`apps/api/src/prisma/prisma.service.ts`) oder — für mandantensensible Tabellen — über einen
|
||||
dienst-intern per `forTenant()` gebundenen Client auf Postgres zugreift.
|
||||
5. Die Antwort geht als JSON zurück; das Frontend rendert sie in der jeweiligen Server- oder
|
||||
Client-Komponente.
|
||||
|
||||
@@ -296,16 +296,17 @@ Unterverzeichnisse, die vom selben `layout.tsx` mitgedeckt werden.
|
||||
Der tatsächliche Mechanismus ist `TenantGuard` (`apps/api/src/tenant/tenant.guard.ts`), global als
|
||||
`APP_GUARD` in `app.module.ts` registriert — er läuft nach `JwtAuthGuard`, weil `req.user` erst
|
||||
dann gesetzt ist. `TenantGuard` liest `tenantId` aus dem JWT-Claim des Anfragenden, erlaubt
|
||||
SUPER_ADMIN einen Wechsel per `x-tenant-id`-Header, und setzt anschließend `req.tenantId` sowie
|
||||
`req.tenantPrisma` — einen über `forTenant()`
|
||||
(`apps/api/src/prisma/prisma-tenant.extension.ts`) erzeugten Prisma-Client, der vor **jeder** Query
|
||||
in einer Transaktion `SELECT set_config('app.current_tenant', $1, true)` ausführt.
|
||||
SUPER_ADMIN einen Wechsel per `x-tenant-id`-Header, und setzt anschließend AUSSCHLIESSLICH
|
||||
`req.tenantId` (260911-e2s). Die Bindung an den Mandanten geschieht dienst-intern, je
|
||||
Service-Methode neu, über das Bindungshilfsmittel `forTenant()`
|
||||
(`apps/api/src/prisma/prisma-tenant.extension.ts`), das vor **jeder** Query in einer Transaktion
|
||||
`SELECT set_config('app.current_tenant', $1, true)` ausführt — der Guard selbst erzeugt keinen
|
||||
Prisma-Client mehr und veröffentlicht keinen auf dem Anfrageobjekt.
|
||||
|
||||
> Im Code existiert daneben eine gleichnamige `TenantMiddleware`
|
||||
> (`apps/api/src/tenant/tenant.middleware.ts`) mit identischer Logik. Sie ist in `app.module.ts`
|
||||
> nirgends über `.apply(...).forRoutes(...)` eingebunden — der tatsächlich aktive Mechanismus ist
|
||||
> ausschließlich `TenantGuard`. Vereinzelte Code-Kommentare verweisen noch auf „TenantMiddleware“;
|
||||
> gemeint ist in jedem Fall der Guard.
|
||||
> Ein früherer Entwurf veröffentlichte zusätzlich einen gebundenen Prisma-Client auf dem
|
||||
> Anfrageobjekt, dupliziert in einer gleichnamigen, nie in `app.module.ts` registrierten
|
||||
> Express-Middleware mit identischer Logik — beides wurde mit 260911-e2s entfernt, nachdem eine
|
||||
> Volltextsuche keinen Leser dieser Eigenschaft außerhalb der beiden Dateien fand.
|
||||
|
||||
`app.current_tenant` wird von **Postgres Row-Level-Security** ausgewertet. RLS-Policies sind aber
|
||||
**nicht** auf allen Tabellen aktiv — aktuell nur auf `User`, `PasswordResetToken`, `LdapConfig`,
|
||||
@@ -318,12 +319,12 @@ RLS-Policy.
|
||||
**Was ein Entwickler nie vergessen darf:** Bei jeder Query gegen eine Tabelle ohne RLS-Policy muss
|
||||
`tenantId` **manuell** in die `where`-Klausel — die Datenbank filtert hier nichts von selbst. Das
|
||||
ist im Code auch der gelebte Stil: `DkvService.loadConfig()`
|
||||
(`apps/api/src/dkv/dkv.service.ts`) etwa nutzt den plain `PrismaService` (nicht
|
||||
`req.tenantPrisma`) und filtert explizit mit `where: { tenantId }`. Wer bei einer solchen Tabelle
|
||||
das `tenantId`-Filter vergisst, liest oder schreibt mandantenübergreifend — ohne dass RLS das
|
||||
auffängt. Bei den sieben RLS-geschützten Tabellen greift die DB-seitige Absicherung zusätzlich,
|
||||
vorausgesetzt die Query läuft tatsächlich über den `tenantPrisma`-Client aus `req.tenantPrisma`
|
||||
und nicht über den globalen `PrismaService`.
|
||||
(`apps/api/src/dkv/dkv.service.ts`) etwa nutzt den plain, UNGEBUNDENEN `PrismaService` und
|
||||
filtert explizit mit `where: { tenantId }`. Wer bei einer solchen Tabelle das `tenantId`-Filter
|
||||
vergisst, liest oder schreibt mandantenübergreifend — ohne dass RLS das auffängt. Bei den sieben
|
||||
RLS-geschützten Tabellen greift die DB-seitige Absicherung zusätzlich, vorausgesetzt die Query
|
||||
läuft tatsächlich über einen dienst-intern per `forTenant()` gebundenen Client und nicht über
|
||||
den globalen, ungebundenen `PrismaService`.
|
||||
|
||||
## Berechtigungen
|
||||
|
||||
|
||||
@@ -47,6 +47,23 @@ entscheiden, ob die Controller künftig darüber gehen (dann bräuchte es keinen
|
||||
zweiten `forTenant()`-Aufruf je Service-Methode) oder ob der Weg ersatzlos
|
||||
entfällt. Dieser Plan entscheidet das nicht, hält den Befund nur fest.
|
||||
|
||||
**Entschieden (260911-e2s, Aufgabe 2):** ersatzloser Entfall, fuer ALLE
|
||||
Bereiche der Etappe 2, nicht nur fuer `tenant`. Gemessen: außerhalb von
|
||||
`tenant.middleware.ts` und `tenant.guard.ts` gab es KEINEN Leser (Suchumfang
|
||||
oben bestaetigt, ebenso erneut gemessen in 260911-e2s Aufgabe 1, Befund B);
|
||||
`tenant.middleware.ts` war zudem NIRGENDS verdrahtet (kein
|
||||
`MiddlewareConsumer`, kein `configure(` in ganz `apps/api`, gemessen)
|
||||
und hatte — anders als der urspruengliche Befund oben suggerierte — auch
|
||||
KEINE eigenen Tests, ebenso wenig wie der Guard (`ls apps/api/src/tenant/`
|
||||
vor 260911-e2s: einzige Testdatei war `tenant.service.spec.ts`). Entscheidung:
|
||||
`tenant.middleware.ts` ist GELOESCHT, `tenant.guard.ts` setzt nur noch
|
||||
`req.tenantId` und hat keine Prisma-Abhaengigkeit mehr. Grund: alle neun vor
|
||||
diesem Bereich umgestellten Bereiche binden ausnahmslos dienst-intern (ein
|
||||
Klient je Methode) — die Konvention ist durch Praxis entschieden, und tote
|
||||
Verdrahtung, die wie ein Sicherheitsmechanismus aussieht, ist schlimmer als
|
||||
keine. Siehe `docs/mandantentrennung-etappe2-fehlerrichtung.md`, Abschnitt
|
||||
"## Bereich tenant", (n4)(a), fuer Messung und Begruendung im Volltext.
|
||||
|
||||
**WINDOWS #19 — nullbares `tenantId` bei `SearchProvider` und
|
||||
`TenderRssFeedSource` — GESCHLOSSEN (260910-jab, Aufgabe 1/2).** Beide Modelle
|
||||
tragen ein nullbares `tenantId` (`SearchProvider` für admin-gepflegte
|
||||
@@ -125,12 +142,12 @@ autoritative Quelle.
|
||||
| dashboard | 1 | 12 | **war 13/0** — Aufgabe 2/3 (260910-krx) haben `dashboard.service.ts` vollständig umgestellt: `getLayout`/`saveLayout` (gemeinsam gebunden), `getWidgets`/`addWidget`/`updateWidgetConfig`/`removeWidget` sowie `getSearchProviders`/`addSearchProvider`/`removeSearchProvider` laufen über `forTenant()`, je Methode ein Klient. Der eine verbleibende ungebundene Rohtreffer ist bewusst: der Modulkatalog (`Module`) trägt heute keinen Zeilenschutz, eine Bindung wäre heute wirkungslos, nicht katastrophal — katastrophal würde sie erst, WENN Etappe 3 dieser Tabelle eine Regel gibt (Befund E aus `module-registry`, hier übernommen) |
|
||||
| auth | 8 | 5 | unverändert gegenüber dem in Etappe 1 (260909-eor) gemessenen Stand |
|
||||
| calendar | 0 | 12 | **war 12/0** — Aufgabe 2 (260911-cwh) hat `calendar.service.ts` vollständig auf `forTenant()` umgestellt: `getSources`, `addSource`, beide Abfragen von `updateSource`/`deleteSource`, alle drei Abfragen von `testConnection`, Laden plus beide Synchronstatus-Rückschreibungen von `fetchAndCacheEvents` — je Methode ein Klient. Anders als bei den sieben Bereichen davor bleibt KEIN ungebundener Rest übrig: `CalendarSource` trägt eine Pflicht-Mandantenkennung, und kein Pfad dieses Bereichs liest über Mandanten hinweg |
|
||||
| tenant | 8 | 0 | unverändert |
|
||||
| tenant | 8 | 3 | **war 8/0** — 260911-e2s (Aufgabe 3) hat drei gebundene Benutzerzähler in `tenant.controller.ts` eingeführt (Fan-out je Mandant nach dem Muster von `UserService.findAllForPlatformAdmin`, ersetzt die drei vorherigen Relationszähler); die acht `tenant`-Zugriffe selbst BLEIBEN ungebunden — `Tenant` trägt keine Regel in irgendeiner ausgelieferten Migration (260911-e2s Aufgabe 1, Prüfung 1/2), hier ist Ungebundenheit richtig, nicht geduldet |
|
||||
| favorites | 7 | 0 | unverändert |
|
||||
| settings | 4 | 0 | unverändert |
|
||||
| **Summe** | **83** | **159** | Ungebunden: war 118 nach 260910-das, dann 108 nach 260910-exd (module-registry 17→7), dann 107 nach 260910-jab (`tenders` 36→35, `listForUser` gebunden), dann 95 nach 260910-krx (`dashboard` 13→1), jetzt 83 nach 260911-cwh (`calendar` 12→0). Gebunden: war 124, dann 134 nach 260910-exd (zusätzlich 10 in `module-registry`), dann 135 nach 260910-jab (zusätzlich 1 in `tenders`), dann 147 nach 260910-krx (zusätzlich 12 in `dashboard`), jetzt 159 nach 260911-cwh (zusätzlich 12 in `calendar`). Diese Übersicht ist eine Buchführungshilfe; **autoritativ ist die Fundstellentabelle unten**, die `rls-access-inventory.spec.ts` bei jedem Lauf gegen den Quelltext prüft |
|
||||
| **Summe** | **83** | **162** | Ungebunden: war 118 nach 260910-das, dann 108 nach 260910-exd (module-registry 17→7), dann 107 nach 260910-jab (`tenders` 36→35, `listForUser` gebunden), dann 95 nach 260910-krx (`dashboard` 13→1), jetzt 83 nach 260911-cwh (`calendar` 12→0) und unverändert nach 260911-e2s (`tenant` bleibt bei 8 ungebundenen Rohtreffern — nur die Gebunden-Spalte änderte sich). Gebunden: war 124, dann 134 nach 260910-exd (zusätzlich 10 in `module-registry`), dann 135 nach 260910-jab (zusätzlich 1 in `tenders`), dann 147 nach 260910-krx (zusätzlich 12 in `dashboard`), dann 159 nach 260911-cwh (zusätzlich 12 in `calendar`), jetzt 162 nach 260911-e2s (zusätzlich 3 in `tenant`). Diese Übersicht ist eine Buchführungshilfe; **autoritativ ist die Fundstellentabelle unten**, die `rls-access-inventory.spec.ts` bei jedem Lauf gegen den Quelltext prüft |
|
||||
|
||||
## Klassen-Verteilung (nach (Datei, Modell)-Fundstellen, 63 Paare)
|
||||
## Klassen-Verteilung (nach (Datei, Modell)-Fundstellen, 64 Paare)
|
||||
|
||||
Stand 260909-jts (Aufgabe 3): 61 Paare aus dem vorherigen Durchlauf
|
||||
(260909-ipc) plus ein bisher vollstaendig unsichtbares Paar
|
||||
@@ -188,13 +205,22 @@ Vermerk waere von einer vergessenen Nachziehung nicht zu unterscheiden —
|
||||
deshalb steht die Abwesenheit einer Aenderung hier ausdruecklich, statt
|
||||
stillschweigend uebersprungen zu werden.
|
||||
|
||||
**Stand 260911-e2s (Aufgabe 3):** 64 Paare — 63 aus dem vorherigen
|
||||
Durchlauf plus EIN neues Paar (`tenant.controller.ts`/`user`, Klasse
|
||||
`muss-mandantengebunden`, Stand `gebunden`): die drei gebundenen
|
||||
Benutzerzähler des Fan-outs (Befund F/G aus 260911-e2s Aufgabe 1). Keine
|
||||
bestehende Klasse verschiebt sich — die beiden `tenant`-Paare
|
||||
(`tenant.controller.ts`/`tenant`, `tenant.service.ts`/`tenant`) bleiben
|
||||
`keine-mandantengebundene-tabelle`/`ungebunden`, nur ihre Begründung wird
|
||||
fortgeschrieben (siehe Fundstellentabelle unten).
|
||||
|
||||
| Klasse | Anzahl Paare |
|
||||
|---|---|
|
||||
| muss-mandantengebunden | 31 |
|
||||
| muss-mandantengebunden | 32 |
|
||||
| keine-mandantengebundene-tabelle | 17 |
|
||||
| beides | 13 |
|
||||
| bewusst-uebergreifend | 2 |
|
||||
| **Summe** | **63** |
|
||||
| **Summe** | **64** |
|
||||
|
||||
## Der Hintergrunddienst als Falle — fünf Fälle
|
||||
|
||||
@@ -333,6 +359,18 @@ Mandantenkennung der Anfrage, die sie ausgelöst hat, und kann strukturell
|
||||
keine andere haben. Kein Kandidat für diese Liste; dieser Absatz hält die
|
||||
Abwesenheit fest, damit sie nicht wie ein Übersehen aussieht.
|
||||
|
||||
Auch der Bereich `tenant` fügt diesem Abschnitt keinen sechsten Fall hinzu,
|
||||
gemessen statt angenommen (260911-e2s, Aufgabe 1, Befund J). Anweisung:
|
||||
`grep -rn "@Cron\|onModuleInit\|onApplicationBootstrap\|setInterval\|setTimeout\|Scheduler" apps/api/src/tenant --include=*.ts`
|
||||
und `grep -rn '\$transaction(\|\$queryRaw\|\$executeRaw' apps/api/src/tenant --include=*.ts`
|
||||
liefern je null Treffer — kein Hintergrunddienst, kein Roh-SQL, keine
|
||||
Transaktion in diesem Bereich. `TenantService.create` ruft nach dem Anlegen
|
||||
eines Mandanten `groupsService.ensureDefaultGroup(tenant.id)` auf; dieser
|
||||
Aufruf läuft seit 260909-jts bereits vollständig über den gebundenen Weg des
|
||||
Bereichs `groups` (`forTenant()`/`withTenantTransaction()`, siehe
|
||||
Fundstellentabelle unten, `groups.service.ts`/`group`, Stand `gebunden`) —
|
||||
nichts an dieser Übergabe musste in 260911-e2s umgestellt werden.
|
||||
|
||||
## Bestandsaufnahme
|
||||
|
||||
Maschinell ermittelt, `rls-access-inventory.spec.ts` hält Vollständigkeit
|
||||
@@ -341,6 +379,17 @@ oder — seit 260909-ipc, Befund G — in `<gebundener Client>.<Modell>`
|
||||
verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
260909-ipc maschinell gegen den Quelltext geprüft), Begründung.
|
||||
|
||||
**Erkennungslücke, seit 260911-e2s vermessen (Aufgabe 1, (n4)(b)):** die
|
||||
Bestandsaufnahme sieht ausschließlich (Datei, Modell)-Paare über
|
||||
`this.prisma.<Modell>` bzw. `<gebundener Client>.<Modell>` — eine
|
||||
Relationseinbindung (`include:`, Relationszähler `_count`) in eine ZWEITE
|
||||
Tabelle erzeugt kein eigenes Paar und ist für das Werkzeug strukturell
|
||||
unsichtbar. Alle 19 `include:`-Stellen und alle `_count`-Stellen des
|
||||
API-Quelltexts wurden einzeln nachgesehen; die einzige gefährliche
|
||||
Ausprägung (ungebundener äußerer Aufruf auf einer UNGESCHÜTZTEN Tabelle,
|
||||
Einbindung in eine GESCHÜTZTE Tabelle) war `tenant.controller.ts` — hier in
|
||||
Aufgabe 3 behoben (Fan-out ersetzt den Relationszähler).
|
||||
|
||||
| Datei | Modell | Klasse | Stand | Begründung |
|
||||
|---|---|---|---|---|
|
||||
| apps/api/src/auth/auth.service.ts | passwordResetToken | muss-mandantengebunden | gebunden | Kein eigenes `tenantId`, RLS ueber Join auf `User` (Migration 20260618112133). `requestPasswordReset`/`resetPassword` laufen vollstaendig ueber `forTenant()` (Etappe 1, WINDOWS #20, Aufgabe 1) — von der alten, nur `this.prisma.*` erkennenden Suche nie erfasst, weil bereits gebunden; die erweiterte Erkennung aus Aufgabe 2 (260909-ipc) macht diese Fundstelle erstmals sichtbar. |
|
||||
@@ -376,8 +425,9 @@ verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
| apps/api/src/module-registry/module-registry.service.ts | module | keine-mandantengebundene-tabelle | ungebunden | Modulkatalog ist plattformweit. Bleibt bewusst ungebunden (260910-exd, Aufgabe 1, Befund E): keine Regel auf der Tabelle, eine Bindung waere heute wirkungslos, nicht katastrophal — katastrophal erst, WENN Etappe 3 dieser Tabelle eine Regel gibt. `findBySlug` ist zusaetzlich die Stelle, die `ModuleGuard` bei JEDER Modulanfrage aufruft. |
|
||||
| apps/api/src/module-registry/module-registry.service.ts | tenantModuleActivation | muss-mandantengebunden | gebunden | Aktivierung je Mandant. Seit 260910-exd (Aufgabe 3) laufen `findActiveForTenant`, `activateForTenant`, beide Zugriffe von `deactivateForTenant` (ueber EINEN Klienten) und `isModuleActive` ueber `forTenant()`, je Methode EIN Klient. |
|
||||
| apps/api/src/settings/settings.service.ts | smtpConfig | muss-mandantengebunden | ungebunden | SMTP-Zugangsdaten je Mandant, `tenantId`-Spalte vorhanden. |
|
||||
| apps/api/src/tenant/tenant.controller.ts | tenant | keine-mandantengebundene-tabelle | ungebunden | `Tenant` ist die Mandantentabelle selbst — hat keine eigene `tenantId`-Spalte, kann sie per Definition nicht haben (Migration 20260909140000, Gruppe b). |
|
||||
| apps/api/src/tenant/tenant.service.ts | tenant | keine-mandantengebundene-tabelle | ungebunden | Dieselbe Begründung. |
|
||||
| apps/api/src/tenant/tenant.controller.ts | tenant | keine-mandantengebundene-tabelle | ungebunden | `Tenant` ist die Mandantentabelle selbst — hat keine eigene `tenantId`-Spalte, kann sie per Definition nicht haben (Migration 20260909140000, Gruppe b). Gemessen 260911-e2s (Aufgabe 1, Prüfung 1/2): keine Regel auf `Tenant` in irgendeiner der 34 ausgelieferten Migrationen einschließlich `20260910120000_rls_widen_membership_grant_and_platform_read`; gebunden und ungebunden liefern über Roh-SQL UND generierten Client dieselben Zeilen. |
|
||||
| apps/api/src/tenant/tenant.controller.ts | user | muss-mandantengebunden | gebunden | Seit 260911-e2s (Aufgabe 3): `findAll`/`findOne`/`remove` zählen Benutzer je Mandant über drei gebundene Aufrufstellen (`tenantPrisma.user.count`, Fan-out-Muster aus `UserService.findAllForPlatformAdmin`) statt über den früheren Relationszähler (`include: { _count: { select: { users } } }`), der nach dem Scharfschalten unter der Regel von `User` unbemerkt null geliefert hätte (260911-e2s Aufgabe 1, Prüfungen 5-7). `where: { tenantId }` bleibt heute (Rolle mit BYPASSRLS, WINDOWS #18) der einzige wirksame Filter. |
|
||||
| apps/api/src/tenant/tenant.service.ts | tenant | keine-mandantengebundene-tabelle | ungebunden | Dieselbe Begründung. Gemessen 260911-e2s (Aufgabe 1, Prüfung 1/2): keine Regel auf `Tenant` in irgendeiner der 34 ausgelieferten Migrationen einschließlich `20260910120000_rls_widen_membership_grant_and_platform_read`. |
|
||||
| apps/api/src/tenders/adapters/email-alert.adapter.ts | tenderEmailConfig | bewusst-uebergreifend | ungebunden | `fetchTenders()` liest bewusst jede aktive `TenderEmailConfig`-Zeile über ALLE Mandanten in einer Abfrage (Plattform-Scheduler, ein Tick pro Postfach, D-13/D-01) — ausführlich im Dateikopf begründet, darf laut Kommentar niemals in `forTenant()` verpackt werden. |
|
||||
| apps/api/src/tenders/adapters/rss.adapter.ts | tenderRssFeedSource | bewusst-uebergreifend | ungebunden | Fan-out über jeden aktiven Feed, plattformweit UND persönlich, in einer Abfrage (Zeilen 55–83 im Dateikopf begründet) — dieselbe Scheduler-Ebene wie beim E-Mail-Adapter. |
|
||||
| apps/api/src/tenders/tender-dedup.service.ts | tender | keine-mandantengebundene-tabelle | ungebunden | Explizit im Dateikopf: "platform-global, RLS-exempt tables. Never wrap these queries in forTenant()." (D-03) |
|
||||
@@ -409,7 +459,7 @@ verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
|
||||
## Was diese Etappe NICHT entscheidet
|
||||
|
||||
- Ob Controller künftig über `req.tenantPrisma` statt eines erneuten
|
||||
- ~~Ob Controller künftig über `req.tenantPrisma` statt eines erneuten
|
||||
`forTenant()`-Aufrufs im Service gehen (offener Befund oben). Der Bereich
|
||||
`ldap` (260909-ipc) hat sich für den Dienst-internen Weg entschieden —
|
||||
`forTenant(this.prisma, tenantId)` wird in jeder umgestellten Methode neu
|
||||
@@ -425,7 +475,15 @@ verwendet), Klasse, Stand (`gebunden`/`ungebunden`/`gemischt`, seit
|
||||
`calendar` (260911-cwh) hat sich für denselben dienst-internen Weg
|
||||
entschieden — jede der sechs umgestellten Methoden in `calendar.service.ts`
|
||||
erzeugt ihren eigenen `forTenant()`-Aufruf, wie alle acht Bereiche vor ihm.
|
||||
Die Frage bleibt für alle übrigen Bereiche der Etappe 2 offen.
|
||||
Die Frage bleibt für alle übrigen Bereiche der Etappe 2 offen.~~
|
||||
**Aufgelöst (260911-e2s):** die Frage ist für ALLE Bereiche entschieden,
|
||||
nicht nur für `tenant` — dienst-intern, ein Klient je Methode, ist die
|
||||
Konvention. Die Anfrageobjekt-Eigenschaft existiert nicht mehr:
|
||||
`tenant.guard.ts` setzt nur noch `req.tenantId`, `tenant.middleware.ts`
|
||||
(der nie verdrahtete Zwilling mit identischer Logik) ist gelöscht. Siehe
|
||||
Abschnitt "Zwei belegte Befunde" oben und
|
||||
`docs/mandantentrennung-etappe2-fehlerrichtung.md`, "## Bereich tenant",
|
||||
(n4)(a).
|
||||
- ~~Wie die WINDOWS-#19-Policy für `SearchProvider`/`TenderRssFeedSource`
|
||||
am Ende genau lautet — nur, dass sie vor dem Scharfschalten gelöst sein
|
||||
muss.~~ Aufgelöst (260910-jab): `TenderRssFeedSource` bekommt vier nach
|
||||
|
||||
Reference in New Issue
Block a user