17 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | actuals | plan_head_before | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260911-fh9 | 01 | auth |
|
|
|
|
|
4c3172b5a5 |
|
|
|
|
|
|
40min | 2026-09-11 | complete |
Quick Task 260911-fh9: Bereich auth der Mandantentrennung Etappe 2 Summary
getMe, changePassword, adminResetPassword binden je über genau einen Klienten tenantPrisma an den Mandanten aus dem signierten Sitzungsnachweis; adminResetPassword schließt sowohl die Rechteausweitung über die Mandantengrenze (T-FH9-01) als auch innerhalb des Mandanten (T-FH9-04); der Anmeldeweg (drei SECURITY-DEFINER-Funktionen) bleibt unangetastet.
Performance
- Duration: ~40 min
- Started: 2026-09-11 (Baseline-Messung: 911 Tests grün, Werkzeug 110/110)
- Completed: 2026-09-11T10:01:47Z
- Tasks: 3
- Files modified: 9 (8 geändert, 1 neu)
Accomplishments
- Die Grenze zwischen Anmeldeweg (drei
SECURITY DEFINER-Funktionen,20260909160000_auth_lookup_functions, unverändert) und Nach-Anmeldung (drei gebundene Methoden) ist gemessen und im Werkzeug (runAuthAreaChecks, 10 neue Prüfungen, 120/120 insgesamt) und in der Kritikschrift (docs/mandantentrennung-etappe2-fehlerrichtung.md, Abschnitt "## Bereich auth", (h1)–(h5)) festgehalten. getMe,changePassword,adminResetPasswordnehmen den Mandanten als ersten Parameter und laufen je über genau EINEN KliententenantPrisma; die drei$queryRaw-Anmeldesuchen bleiben unverändert auf dem ungebundenen Klienten.adminResetPasswordverweigert einem Nicht-SUPER_ADMIN das Kennwort eines SUPER_ADMIN (T-FH9-04);auth.controller.tslöst den Mandanten der obersten Rolle über den gebundenen Fan-outUserService.findByIdForPlatformAdminauf (Präzedenzfalluser.controller.tsresolveTargetUser).- Die Testlage hat keine Identitätsattrappe mehr —
auth.service.spec.ts(29 Fälle) und die neueauth.controller.spec.ts(23 Fälle) nageln Bindung, Rollenverzweigung und Metadaten fest; sieben Falsifizierungsnachweise durchgeführt und zurückgenommen. - Fünf handgepflegte Dokumentstellen der Klassifikation nachgezogen und derivativ gegatet; zwei neue offene Ledger-Einträge (#28, #29).
Task Commits
- Aufgabe 1: Fehlerrichtung messen und aufschreiben —
9782bea(docs) - Aufgabe 2: Die drei Methoden binden —
92aa8c4(feat) - Aufgabe 3: Mandantenquelle festnageln, Klassifikation nachziehen, Ledger —
f68beb3(docs)
Plan metadata: wird vom Orchestrator nach dieser SUMMARY committet.
Files Created/Modified
apps/api/scripts/rls-scratch-check.mjs— dreizehnter AbschnittrunAuthAreaChecks, neuer HelferreadSchemaModelScalarFieldNamesdocs/mandantentrennung-etappe2-fehlerrichtung.md— Abschnitt## Bereich auth(h1)–(h5) vor## Verweisapps/api/src/auth/auth.service.ts—getMe(tenantId, userId),changePassword(tenantId, userId, ...),adminResetPassword(tenantId, callerRole, userId, ...)apps/api/src/auth/auth.service.spec.ts— Zwei-Klienten-Nachbau (__makeBoundClient), 29 Fälleapps/api/src/auth/auth.controller.ts—resolveTargetTenantId,me/changePassword/adminResetPasswordreichen das Claim durchapps/api/src/auth/auth.module.ts— importiertUserModuleapps/api/src/auth/auth.controller.spec.ts— NEU, 23 Fälledocs/mandantentrennung-zugriffsklassifikation.md— Übersichtszeile, Summenzeile, Bestandsaufnahme-Zeile, Klassen-Verteilung-Vermerk, Hintergrunddienst-Vermerk, Etappe-3-Punkt.planning/WINDOWS.md— Einträge #28, #29
Decisions Made
- Mandantenquelle für Selbstbedienung: das JWT-Claim (
@CurrentUser().tenantId), nichtreq.tenantId— siehe key-decisions oben. adminResetPasswords Mandant für die oberste Rolle: gebundener Fan-out überUserService.findByIdForPlatformAdmin, nichtreq.tenantId/x-tenant-id.- Rollengrenze innerhalb des Mandanten in
adminResetPasswordgeschlossen; SchwesterwegPATCH /users/:idbewusst NICHT angefasst (außerhalb der Erlaubnisliste) — Ledger-Eintrag #29 statt Reparatur.
Tatsächlich gezählte Prüfungs- und Testzahlen
- Wegwerf-Werkzeug: 120/120 Prüfungen bestanden (110 bisherige + 10 neue, wie im Plan gezählt:
auth-anmeldefunktionen-security-definer-unveraendert,auth-wegwerftabelle-user-deckt-alle-spalten-des-generierten-clients,auth-anmeldesuche-findet-benutzer-weiterhin-mit-festem-spaltensatz,auth-getme-generierter-client-ungebunden-liefert-null,auth-getme-generierter-client-gebunden-eigener-mandant-findet-benutzer,auth-getme-generierter-client-gebunden-fremder-mandant-liefert-null,auth-changepassword-generierter-client-ungebundenes-update-scheitert-laut,auth-changepassword-generierter-client-gebundenes-update-eigener-mandant-gelingt,auth-adminreset-generierter-client-gebundenes-update-fremder-mandant-scheitert-laut,auth-fan-out-je-mandant-gebunden-loest-mandant-der-kennung-auf). - Testsuite: Baseline 911 Tests/59 Dateien → nach Aufgabe 2: 928 Tests (927 grün, 1 erwartungsgemäß rot — siehe unten) → nach Aufgabe 3: 951 Tests grün in 60 Dateien (29 Fälle in
auth.service.spec.ts, 23 Fälle in der neuenauth.controller.spec.ts, 927 + 24 = 951). - Typprüfung: sauber nach jeder Aufgabe.
Abweichung von den Planungsbefunden (ausdrücklich benannt)
Zwischenzeitlich rot: rls-access-inventory.spec.ts, zwischen Aufgabe 2 und Aufgabe 3. Der Plan sagt in Aufgabe 3 voraus: "Ohne Schritt 3 ist rls-access-inventory.spec.ts am Ende dieser Aufgabe rot (Stand-Vergleich)." Das galt nicht nur für Aufgabe 3, sondern bereits ab dem Ende von Aufgabe 2: sobald auth.service.ts keinen ungebundenen user-Zugriff mehr enthielt, maß die Prüfung den Stand für apps/api/src/auth/auth.service.ts::user als gebunden, während das Klassifikationsdokument (noch nicht nachgezogen, das ist Aufgabe 3) weiterhin gemischt führte — ein Fehlschlag von genau einem Test (der eingetragene Stand stimmt mit dem im Quelltext gemessenen überein), 927/928 grün. Dasselbe Muster zeigt sich bereits im Klassifikationsdokument selbst für den Vorgänger-Plan 260911-cwh ("Aufgabe 2 (260911-cwh) ändert nur seine Stand-Spalte … nicht seine Klasse" — im Abschnitt zu Aufgabe 3 dokumentiert, obwohl die Codeänderung in Aufgabe 2 lag). Behandlung: nicht als Blocker gewertet, weil (a) der Fehlschlag exakt einen einzigen, im Plan selbst vorausgesagten Test betraf, (b) er keine Datei außerhalb der für Aufgabe 2 erlaubten vier Dateien berührte, und (c) Aufgabe 3 unmittelbar im selben Lauf folgte und die Baseline innerhalb von Minuten wiederherstellte (951/951). Der Commit von Aufgabe 2 dokumentiert das ausdrücklich als "bekannt und erwartet". Kein Datenverlust, keine stillschweigende Planabweichung — nur eine Klarstellung, dass "Baseline gehalten nach jeder Aufgabe" hier als "nach dem vollständigen Plan, mit einem im Plan selbst vorausgesagten Zwischenzustand" zu lesen ist, nicht als literarische Bedingung jedes einzelnen Aufgaben-<verify>-Blocks.
Testfall-Namensraumkollision im Gate forTenant: vi.fn((p (Aufgabe 2). Die neue Mock-Signatur forTenant: vi.fn((prisma: any, tenantId: string) => prisma.__makeBoundClient(tenantId)) — wortgleich mit dem Muster aus user.service.spec.ts/tenant.controller.spec.ts — erfüllte unbeabsichtigt das BRE-Suchmuster forTenant: vi.fn((p des Gates, das die ALTE Identitäts-Attrappe forTenant: vi.fn((p) => p) ausschließen sollte (vi.fn((p ist ein Präfix von vi.fn((prisma). Behoben durch Umbenennung des ersten Parameters auf unboundClient statt prisma. Keine Verhaltensänderung, nur eine Namenswahl, die das Gate nicht fälschlich trifft.
Alle übrigen Zahlen, Codeaussagen (Befunde B, C, D, E, J, K) und die Modulgraph-Messung stimmten bei der erneuten Ausführung zur Ausführungszeit exakt mit den Planungsbefunden überein — keine weiteren Abweichungen.
Falsifizierungsnachweise (alle durchgeführt, zurückgenommen, wörtlich notiert)
Aufgabe 2 (drei, im Dienst):
getMeprobeweise auf den ungebundenen Klienten zurückgebaut (const tenantPrisma = this.prisma as any;): 4 Tests wurden rot (AuthService.getMe— alle vier Fälle), jeweils mitTypeError: Cannot read properties of undefined (reading 'findUnique'). Erwartete Form bestätigt: der Nachbau hat kein ungebundenes Benutzermodell.validateUserprobeweise auf den gebundenen Klienten verschoben (const probeBoundClient = forTenant(this.prisma, 'falsification-probe') as any; const rows = await probeBoundClient.$queryRaw...): 9 Tests wurden rot, darunter der eigens für die Grenze geschriebene Fall (AuthService.validateUser — lokales Kennwort > sucht die Anmeldedaten exakt EINMAL ungebunden...) mitTypeError: probeBoundClient.$queryRaw is not a function. Erwartete Form bestätigt: der gebundene Nachbau hat kein$queryRaw.- SUPER_ADMIN-Riegel in
adminResetPasswordprobeweise entfernt: genau 1 Test wurde rot (AuthService.adminResetPassword > Aufrufer ADMIN, Ziel SUPER_ADMIN im SELBEN Mandanten: ForbiddenException (T-FH9-04)...), mitAssertionError: promise resolved "undefined" instead of rejecting.
Aufgabe 3 (eine, am Controller):
resolveTargetTenantIdprobeweise aufreturn currentUser.tenantId;(auch für SUPER_ADMIN) reduziert: genau die beiden Fan-out-Fälle wurden rot —expected "spy" to be called 1 times, but got 0 times(findByIdForPlatformAdmin nicht aufgerufen) undpromise resolved "{ message: ... }" instead of rejecting(die Null-Fan-out-BadRequestException griff nicht mehr).
Aufgabe 3 (zwei, an den Dokument-Gates):
- Bestandsaufnahme-Zeile
auth.service.ts | userprobeweise aufgemischtzurückgesetzt:rls-access-inventory.spec.tswurde rot mitAssertionError: Abweichender Stand (Dokument vs. Quelltext): apps/api/src/auth/auth.service.ts::user — dokumentiert=gemischt, gemessen=gebunden. - Übersichtszeile
authprobeweise auf99 | 10gesetzt: das herleitende Shell-Gate schlug fehl mitUEBERSICHTSZEILE auth nennt nicht die neu gemessenen Zahlen 3/10.
Deviations from Plan
Keine inhaltlichen Abweichungen von den vier Dateien/Aufgaben des Plans — beide oben dokumentierten Punkte sind Klarstellungen zur Ausführungsreihenfolge bzw. eine Namenswahl, keine Scope- oder Verhaltensänderung. Kein Rule-1/2/3/4-Auto-Fix war nötig; alle Codeaussagen aus den Planungsbefunden wurden bei erneuter Ausführung bestätigt.
Issues Encountered
Keine ungelösten Probleme. Das einzige während der Ausführung aufgetretene technische Detail (Gate-Namenskollision, siehe Abweichungen oben) wurde sofort behoben.
Etappe-3-Vorbehalt (ein Absatz)
Sobald Anmeldenamen je Mandant eindeutig werden (Etappe-3-Entscheidung (1)), braucht der Anmeldeweg den Mandanten VOR der Benutzersuche: auth_lookup_user_by_username(p_username) muss auf (p_tenant_id, p_username) umgestellt werden — die Funktion wird dabei ENGER (zwei Gleichheitsbedingungen statt einer), nicht weiter — und local.strategy.ts braucht eine Mandantenangabe vor der Suche. Dieser Plan ist dafür neutral: die Bindung der drei Nach-Anmeldungs-Methoden hängt ausschließlich am JWT-Claim tenantId und an User.id (plattformweite UUID, Kette aus 260911-cwh), nicht an username/email. Nichts in diesem Plan hat den künftigen Umbau schwerer gemacht.
User Setup Required
None — keine externe Dienstkonfiguration nötig.
Next Phase Readiness
- Elf der zwölf Bereiche der Etappe 2 sind umgestellt. Laut Plan ist der nächste Lauf
favorites(7 ungebundene Rohtreffer) undsettings(4) als EIN Durchlauf — danach ist Etappe 2 vollständig. - Kein Blocker für diesen nächsten Lauf. Zwei neue offene WINDOWS-Einträge (#28 Frontend-Leere, #29 Schwesterweg-Rechteausweitung) sind dokumentiert und unabhängig von
favorites/settings. DATABASE_URLzeigt unverändert auf die Rolletessera(Schalter aus); Schema, Migrationen, die drei Anmeldefunktionen, Active Directory: alle unangetastet.
Phase: quick-260911-fh9 Completed: 2026-09-11
Self-Check: PASSED
Alle neun in key-files genannten Dateien plus diese SUMMARY existieren auf der Platte; alle drei Task-Commits (9782bea, 92aa8c4, f68beb3) sind in git log --oneline --all auffindbar. Keine fehlenden Elemente.