Der Kernfund dieser Etappe war ein Defekt im Fundament: forTenant() setzte den Mandantenkontext auf der Transaktionsverbindung, dispatchte die Abfrage aber ueber den aeusseren Client. Empirisch reproduziert statt hergeleitet — set_config auf Backend-PID 254999, Abfrage auf 255000, Kontext dort NULL. Damit hat die Mandantentrennung nie funktioniert, auch nicht dort, wo sie scheinbar benutzt wurde. Nach einem Scharfschalten haette sich das umgekehrt: die betroffenen Abfragen liefern dann null Zeilen, und der LDAP-Loeschzweig haette das als "Gruppe im Verzeichnis verschwunden" gedeutet und sie samt Mitgliedschaften und Modulfreigaben geloescht. Aufgefallen, weil vor dem Umbau geprueft wurde, ob das Fundament traegt. Der Anmeldeweg bekam eine bewusst schmale Ausnahme: drei SECURITY-DEFINER- Funktionen mit fester Spaltenliste, Gleichheitsbedingung und LIMIT 1. Eine Policy waere hier untauglich — sie ist ein Zeilenpraedikat und haette zwangslaeufig die ganze Tabelle freigegeben. Die Klassifikation macht die restliche Arbeit planbar: 227 Zugriffe in 59 Einheiten, davon 31 umzustellen, 9 teilweise, 16 ohne Mandantenbezug und 3 bewusst uebergreifend. Ein Test haelt die Einteilung gegen Abdriften fest. Browser-Gegenprobe lokal bestanden. 701 Tests gruen. Der Schalter bleibt aus; #18, #19 und #20 bleiben offen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FYZcd3SSmo14QTqWx2KKzU
18 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, tech-stack, key-files, key-decisions, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | actuals | tech-stack | key-files | key-decisions | requirements-completed | coverage | duration | completed | status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| quick-260909-eor | 01 | database |
|
|
|
|
|
|
|
|
|
|
~55min | 2026-09-09 | complete |
Quick Task 260909-eor: Anmeldeweg mandantenfaehig machen und alle Datenbankzugriffe klassifizieren Summary
Repariert einen zuvor unbemerkten Verbindungsfehler in forTenant() (WINDOWS #20), gibt dem Anmeldeweg eine schmale SECURITY-DEFINER-Ausnahme fuer die drei Lesezugriffe vor bekanntem Mandanten, und klassifiziert alle 227 Datenbankzugriffe im API-Quelltext maschinell abgesichert — schaltet die Mandantentrennung selbst aber weiterhin NICHT scharf.
Performance
- Duration: ~55 min
- Tasks: 4/4
- Files modified: 11 (5 neu, 6 geaendert), plus ein Nachtrag zu
.planning/WINDOWS.mdin einem eigenen fuenften Commit
Accomplishments
forTenant()repariert (Aufgabe 1, WINDOWS #20). Die bisherige interaktive Callback-Form von$transactionfuehrte die eigentliche Abfrage auf einer ANDEREN Datenbankverbindung aus alsset_config()— gemessen:set_configauf Backend-PID 254999, die Abfrage auf 255000, Mandantenkontext dortNULL. Ersetzt durch die Array-Form (prisma.$transaction([setTenantContext, query(args)])), Prismas eigenes vorgesehenes RLS-ueber-Extensions-Muster. Injektionsfestigkeit (T-02-05) bleibt ueber ein getaggtes$executeRaw-Template erhalten. Live nachgewiesen gegen eine Wegwerf-Datenbank: gleiche Backend-Verbindung, gesetzter Kontext, keine Fremdmandanten-Zeilen, keine Zeilen ohne Kontext — alle 5 Pruefungen bestanden.- Anmeldeweg mandantenfaehig (Aufgabe 2, WINDOWS #18). Drei enge
SECURITY DEFINER-Funktionen (STABLE, fester Suchpfadpublic, pg_temp,LIMIT 1, Ausfuehrungsrecht ausschliesslichtessera_app) ersetzen die drei Lesezugriffe inauth.service.ts, die vor bekanntem Mandanten stattfinden:auth_lookup_user_by_username,auth_lookup_user_by_email,auth_lookup_reset_token. Alle nachfolgenden Schreibzugriffe (lastLoginAt, passwordHash, Reset-Token) laufen ueberforTenant(), gebunden an den soeben gefundenen Benutzer. Live nachgewiesen: Anmeldesuche findet den Benutzer, unbekannter Name liefert nichts ohne zu werfen, gewoehnlicherSELECT * FROM "User"liefert unter derselben Rolle null Zeilen. - Vollstaendige Klassifikation (Aufgabe 3).
docs/mandantentrennung-zugriffsklassifikation.mdordnet alle 227this.prisma.*-Fundstellen (32 Dateien, zusammengefasst zu 59 Datei-Modell-Paaren) einer von vier Klassen zu: 31muss-mandantengebunden, 16keine-mandantengebundene-tabelle, 9beides(Hintergrunddienst-Sonderfall: uebergreifend lesen, je Zeile mandantengebunden schreiben —ldap.service.ts,tender-digest.scheduler.ts,tender-matching.service.ts), 3bewusst-uebergreifend.rls-access-inventory.spec.tsermittelt die Fundstellen bei jedem Testlauf neu und scheitert bei jeder Abweichung — im Zuge der Arbeit selbst getestet (eine Zeile aus dem Dokument geloescht, Fehlschlag bestaetigt, zurueckgesetzt). - Zwei belegte Befunde festgehalten:
req.tenantPrismawird vontenant.middleware.ts/tenant.guard.tsgesetzt, aber im gesamtenapps/api/srcvon niemandem gelesen — Entscheidung fuer Etappe 2. WINDOWS #19 (nullbarestenantIdbeiSearchProvider/TenderRssFeedSource) bleibt benannter Blocker fuer Etappe 3. - Browser-Gegenprobe bestanden (Aufgabe 4). Lokale Dienste mit dem neuen Stand neu gebaut (
docker compose build api && up -d --force-recreate api), dann im Browser gegenlocalhost:3000geprueft: Anmeldung mit lokalem Benutzer erfolgreich, falsches Kennwort abgelehnt ohne Feld-Hinweis, Kennwort-vergessen-Formular ohne sichtbaren Fehler. Details siehe Abschnitt "Browser-Gegenprobe" unten.
Task Commits
Each task was committed atomically:
- Task 1: forTenant() auf eine Verbindung zwingen und die Wirkung live nachweisen -
bbf1795(fix) - Task 2: Anmeldeweg ueber drei eng geschnittene Datenbankfunktionen mandantenfaehig machen -
de50297(feat) - Task 3: Alle 227 Datenbankzugriffe klassifizieren und die Klassifikation maschinell absichern -
5f3a39c(docs) - Task 4: Anmeldung lokal gegenpruefen - kein eigener Code-Commit (Checkpoint), Nachtrag zur Bewertung von WINDOWS #20 in
da0ac04(fix)
Aufgabe 2 lief nach TDD: Testdateien (auth-lookup-functions.spec.ts, erweitertes auth.service.spec.ts) zusammen mit der Implementierung im selben Commit, rot-vor-gruen waehrend der Entwicklung bestaetigt, keine separate RED-Phase committet.
Files Created/Modified
apps/api/src/prisma/prisma-tenant.extension.ts- Array-Form von$transaction, getaggtes$executeRaw, ausfuehrlicher Kopfkommentar mit gemessenen Backend-PIDs und benannten Grenzfaellen fuer Etappe 2apps/api/src/prisma/prisma-tenant.extension.spec.ts- prueft die Form des Aufrufs (Array mit zwei Eintraegen, Rueckgabewert ist der zweite) ohne laufende Datenbankapps/api/scripts/rls-scratch-check.mjs- Wegwerf-Datenbank, 8 Live-Pruefungen (5 fuer forTenant(), 3 fuer die Anmelde-Funktionen), fest verdrahteter Datenbankname, nutztprisma db execute --filefuer mehrteilige Migrationsskripteapps/api/prisma/migrations/20260909160000_auth_lookup_functions/migration.sql- drei SECURITY-DEFINER-Funktionen mit ausgeschriebener Entscheidungsbegruendung im Kopfapps/api/src/prisma/auth-lookup-functions.spec.ts- 11 Tests gegen den Migrationstext (Suchpfad, STABLE, LIMIT 1, Rechteentzug/-vergabe, kein Kennwort)apps/api/src/auth/auth.service.ts- drei Lesezugriffe auf$queryRaw-Funktionsaufrufe umgestellt, fuenf Schreibzugriffe aufforTenant()apps/api/src/auth/auth.service.spec.ts- Mocks fuerforTenant()und$queryRaw, neue Testfaelle fuer alle drei Funktionsaufrufeapps/api/src/prisma/rls-access-inventory.spec.ts- ermittelt (Datei, Modell)-Fundstellen aus dem Quelltext, vergleicht gegen die Dokument-Tabelledocs/mandantentrennung-zugriffsklassifikation.md- vollstaendige Bestandsaufnahme, Mengentabelle, Hintergrunddienst-Abschnitt, zwei belegte Befundedocs/mandantentrennung-datenbankrolle.md- verlinkt das neue Dokument, korrigiert die ueberholte Zahl 182 auf 227/59, ergaenzt den WINDOWS-#20-Sperrgrund.planning/WINDOWS.md- #18/#19 um Nachtrag ergaenzt, #20 zunaechst faelschlich alsfixedmarkiert und auf Rueckmeldung wieder aufopengesetzt (siehe Deviations)
Decisions Made
- WINDOWS #20 bleibt
open, nichtfixed— siehekey-decisionsoben und Abschnitt "Deviations". getMe/changePassword/adminResetPasswordinauth.service.tsbewusst nicht angefasst, bereits alsmuss-mandantengebundenin der Klassifikation fuer Etappe 2 eingetragen.SearchProviderin der Klassifikation alsmuss-mandantengebundeneingestuft (nichtkeine-mandantengebundene-tabelle), mit Fussnote zu WINDOWS #19 — die Spalte ist nullbar, aber laut 05-02-Entscheidung kommen die tatsaechlichen Vorgaben aus Konstanten, nicht aus der Datenbank.
Deviations from Plan
Auto-fixed Issues
1. [Rule 3 - Blocking issue] Eigentreffer der Bestandsaufnahme-Pruefung durch woertlichen Code-Text im Kommentar
- Found during: Task 3 (Vorbereitung der Bestandsaufnahme-Spec)
- Issue: Der Kopfkommentar von
validateUser()inauth.service.ts(aus Aufgabe 2) enthielt woertlichthis.prisma.user.findUniqueals erklaerenden Text — die grep-basierte Bestandsaufnahme-Spec haette das als echte Fundstelle gezaehlt. - Fix: Kommentar auf "this dot prisma dot user dot findUnique" umformuliert, inhaltlich identisch, textuell nicht mehr treffend fuer den Regex.
- Files modified: apps/api/src/auth/auth.service.ts
- Commit:
5f3a39c
2. [Rule 1 - Bug] WINDOWS-Ledger-Tabelle und JSON-Block liefen auseinander
- Found during: Auf Rueckmeldung des Koordinators zu Task 4 (nach Task 3 bereits committet)
- Issue: Die WINDOWS.md-Bearbeitung in Task 3 hat nur die Markdown-Tabelle von Hand angepasst (Eintrag #20 auf
fixed,#18/#19um Nachtrag ergaenzt) und dabei den massgeblichen JSON-Block am Dateiende — die eigentliche Quelle der Wahrheit fuergsd-tools windows status— nicht mitgezogen.gsd-tools windows statusscheiterte seitdem mit "Ledger counts disagree with entries". - Fix: Auf ausdruecklichen Wunsch des Koordinators sollte #20 ohnehin OPEN bleiben statt
fixed(siehe Rationale incoverage/D4-Nachbarschaft). Ledger aus der Vorversion ueber diebroken-windows.cjs-Bibliothek (parseLedger/renderLedger) neu aufgebaut, dabei Tabelle und JSON-Block wieder synchron gehalten und #20 korrekt aufopenbelassen. - Files modified: .planning/WINDOWS.md
- Commit:
da0ac04
Total deviations: 2 (beide Rule 1/3, keine Architekturentscheidung noetig) Impact on plan: Kein inhaltlicher Einfluss auf die drei Aufgaben-Ergebnisse — beide Korrekturen betrafen Nebenprodukte (Kommentartext, Ledger-Konsistenz), nicht den geprueften Datenbank-/Anwendungscode.
Issues Encountered
Keine blockierenden Probleme jenseits der beiden oben dokumentierten Deviations. Alle Verify-Kommandos aus dem Plan liefen gruen: npm run test (701/701 Tests, 53 Dateien), npm run type-check sauber, rls-scratch-check.mjs 8/8 Pruefungen bestanden.
Browser-Gegenprobe (Aufgabe 4)
Durchgefuehrt gegen localhost:3000 (Server 192.168.13.12 nicht angefasst), nach lokalem Neubau des API-Containers mit dem Stand aus allen drei Code-Commits:
- Anmeldung mit lokalem Benutzer (admin): erfolgreich, Portal laedt, Seitenleiste und Dashboard erscheinen wie zuvor.
- Falsches Kennwort: abgelehnt mit "Benutzername oder Passwort ungueltig" — verraet nicht, welches Feld falsch war (T-02-01 eingehalten).
- Kennwort-vergessen-Seite: Formular abgeschickt, Seite antwortet ohne sichtbaren Fehler.
docker compose logs api zeigte genau einen Protokolleintrag waehrend der Pruefung:
[MailService] Failed to send password reset email to admin@tessera.local
Error: getaddrinfo ENOTFOUND mailhog
Als umgebungsbedingt eingestuft, nicht dem Umbau zuzurechnen: lokal existiert kein mailhog-Container (docker compose ps bestaetigt nur api/db/web). Entscheidend fuer die Bewertung ist die Reihenfolge — der Fehler kommt aus dem MailService, also NACH dem Datenbankzugriff. Der neue SECURITY-DEFINER-Weg fuer den Token-Lookup (auth_lookup_reset_token) wurde durchlaufen und hat den passenden Datensatz gefunden; gescheitert ist erst der Mailversand an einen lokal nicht existierenden Server. Keine Meldung ueber verweigerte Rechte, keine Prisma-Ausnahme, kein Hinweis auf eine leere Ergebnismenge.
User Setup Required
Keine sofortige Handlung noetig. DATABASE_URL ist unveraendert, der Server 192.168.13.12 wurde nicht angefasst, keine Migration auf eine Live-Datenbank angewendet.
Known Stubs
Keine — alle Bausteine sind vollstaendig implementiert und getestet, keine leeren Rueckgabewerte, kein Platzhaltertext.
Next Phase Readiness
Bereit fuer Etappe 2 (Umbau der 31 muss-mandantengebunden- und 9 beides-Fundstellen auf forTenant()), sobald diese in eigenen Plaenen geschnitten wird — siehe <next_stages> im Plan 260909-eor-PLAN.md fuer die vorgeschlagene Reihenfolge (tenders 62, groups 37, ldap 21, dkv 21, user 17, module-registry 17, dashboard 13, calendar 12, favorites 7, settings 4). auth (8) ist durch diese Etappe bereits erledigt, tenant (8) faellt weitgehend unter Etappe 3.
Blocker: keiner fuer diesen Vorgang selbst. Die Umstellung (DATABASE_URL auf tessera_app) bleibt weiterhin blockiert, bis Etappe 2 und Etappe 3 (benannter Systemkontext fuer die 9 beides- und 3 bewusst-uebergreifend-Fundstellen, plus WINDOWS #19) abgeschlossen sind.
WINDOWS #18, #19 und #20 bleiben open — dieser Vorgang hat das Fundament repariert und die Landkarte gezeichnet, vollzieht die Umstellung selbst aber nicht.
Self-Check: PASSED
- FOUND: apps/api/src/prisma/prisma-tenant.extension.ts
- FOUND: apps/api/src/prisma/prisma-tenant.extension.spec.ts
- FOUND: apps/api/scripts/rls-scratch-check.mjs
- FOUND: apps/api/prisma/migrations/20260909160000_auth_lookup_functions/migration.sql
- FOUND: apps/api/src/prisma/auth-lookup-functions.spec.ts
- FOUND: apps/api/src/auth/auth.service.ts
- FOUND: apps/api/src/auth/auth.service.spec.ts
- FOUND: apps/api/src/prisma/rls-access-inventory.spec.ts
- FOUND: docs/mandantentrennung-zugriffsklassifikation.md
- FOUND: docs/mandantentrennung-datenbankrolle.md
- FOUND: .planning/WINDOWS.md
- FOUND commit
bbf1795,de50297,5f3a39c,da0ac04
Quick Task: 260909-eor Completed: 2026-09-09