15 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 15-modul-berechtigungen-gruppen-user-grants | 03 | auth |
|
|
|
|
|
|
|
|
|
|
32min | 2026-08-04 | complete |
Phase 15 Plan 03: Modul-Freigaben — Schreibseite, Matrix, Benutzer-Detail, Katalog Summary
ModuleGrantsService/ModuleGrantsController für Gruppen- und Direkt-Grants mit einer eigens etablierten Cross-Tenant-Gegenprüfung vor jedem Insert (assertTargetBelongsToTenant, T-15-01), die Datenlieferung für Freigabe-Matrix (D-15) und Benutzer-Detail (D-16), und GET /modules/catalog, das dem Marketplace beide Statusflags (isActiveForTenant, hasAccess) in einer Antwort liefert (D-08) — alles end-to-end gegen die laufende lokale API bewiesen, inklusive des sofortigen Entzugs ohne Zwischenschritt (D-09).
Performance
- Duration: 32 min
- Started: 2026-08-04T16:28:00Z
- Completed: 2026-08-04T18:41:00Z (inkl. manueller E2E-Verifikation)
- Tasks: 3
- Files modified: 8
Accomplishments
ModuleGrantsServicemitgrant,revoke,getMatrix,getUserAccessund der privatenassertTargetBelongsToTenant— der zentrale Schutz gegen mandantenübergreifende Freigaben (T-15-01). Für dieses Muster gab es kein Vorbild im Bestandscode: bisherige Ownership-Checks (DashboardService.removeWidget,GroupsService.findOwned) prüfen nur direktes Eigentum, nicht eine zweite Mandantengrenze über eine referenzierte Relationgrantprüft in fester Reihenfolge: Entweder-oder vongroupId/userId(D-04,BadRequestExceptionmit Klartext statt rohem Postgres-Constraint-Namen), dann die Mandanten-Gegenprüfung, dann die aktiveTenantModuleActivation(D-02), danncreate. EinP2002aus dem partiellen Unique-Index (zwei parallele Klicks auf dieselbe Matrix-Zelle) wird abgefangen und liefert perfindFirstden bestehenden Datensatz zurück — kein HTTP 500 bei Doppelklick oder echter NebenläufigkeitrevokenutztdeleteManymittenantIdimwhereals IDOR-Schutz (T-15-02) — ein Ziel eines fremden Mandanten trifft null Zeilen, kein vorheriger Lookup nötiggetMatrix(tenantId)liefert{ modules, groups, grants }in einer Antwort: aktive Module nach category+name, Gruppen nach name, Gruppen-Grants als Paare aus moduleId/groupId — deterministisch über wiederholte AufrufegetUserAccess(tenantId, userId)liefert je aktivem Modul die Namen der Gruppen, über die der Benutzer erbt, und ein Direkt-Grant-Kennzeichen — Gruppen-Grant und Direkt-Grant auf dasselbe Modul erscheinen gleichzeitig, keiner verdrängt den anderen (Union statt Vorrang)ModuleGrantsControllerbildet vier rollengeschützte Routen unter/module-grantsab (GET matrix,GET users/:userId,POST,DELETE);matrixist vorusers/:userIddeklariert (Beschattungsfehler-Vermeidung, Projekt hatte diesen Fehler schon einmal)ModuleAccessService.getCatalogFlags(tenantId, userId, role)undModuleRegistryController.findCatalog(GET /modules/catalog) liefern je registriertem Modul beide Statusflags in einer Antwort (D-08) — für ADMIN/SUPER_ADMIN isthasAccessbei jedem aktiven Modul wahr (D-03), weilgetAccessibleModuleIdsden Rollen-Kurzschluss anwendet- Jede erfolgreiche
grant-/revoke-Operation schreibt eine Logzeile überthis.logger(D-23) — keine Audit-Tabelle, keine Ansicht im Admin-UI - D-04 vollständig eingehalten: der Datensatz trägt kein Rechtestufen-Feld, und der Service bietet keine Methode, die eines setzen könnte
- End-to-End-Nachweis gegen die laufende lokale API (
dist/maingegen die DB-Container-IP,DATABASE_URL/JWT_SECRETwie in vorherigen Plänen dieser Phase):GET /module-grants/matrixals ADMIN → 200 mitmodules/groups/grants, als USER → 403;POST /module-grantsmitgroupIdeines eigens angelegten Test-Fremdmandanten → 404, kein Datensatz angelegt;GET /modules/catalogals USER (ohne Gruppen-Mitgliedschaft) zeigttender-radarmitisActiveForTenant:true, hasAccess:false, derselbe Aufruf als ADMIN zeigt beide Flagstrue; Testbenutzer der Gruppe „Alle Benutzer“ hinzugefügt →GET /modules/tender-radar200; Grant entzogen → derselbe Aufruf sofort 403 ohne Zwischenschritt (D-09);GET /module-grants/users/:userIdzeigtviaGroups:['Alle Benutzer'], direct:false
Task Commits
Jeder Task wurde atomar committet:
- Task 1: ModuleGrantsService — Freigaben setzen und entziehen mit Mandanten-Gegenprüfung -
5e256db(feat) - Task 2: ModuleGrantsController und Einbindung in GroupsModule -
072fb7f(feat) - Task 3: GET /modules/catalog — beide Statusflags in einer Antwort für den Marketplace -
1c32543(feat)
Plan metadata: siehe Commit dieser SUMMARY.md (docs: complete plan)
Files Created/Modified
apps/api/src/groups/module-grants.service.ts-ModuleGrantsService(neu): grant/revoke/getMatrix/getUserAccess/assertTargetBelongsToTenantapps/api/src/groups/module-grants.service.spec.ts- 20 Tests, hand-rolled In-Memory-Prisma-Fake (Projekt-Konvention)apps/api/src/groups/dto/create-module-grant.dto.ts-CreateModuleGrantDto(moduleId required, groupId/userId optional)apps/api/src/groups/module-grants.controller.ts-ModuleGrantsController(neu), vier rollengeschützte/module-grants-Routenapps/api/src/groups/groups.module.ts-ModuleGrantsController/ModuleGrantsServicein controllers/providers/exports ergänztapps/api/src/module-registry/module-access.service.ts-getCatalogFlagsergänztapps/api/src/module-registry/module-access.service.spec.ts- 4 neuegetCatalogFlags-Testsapps/api/src/module-registry/module-registry.controller.ts-findCatalog-Handler (GET /modules/catalog) ergänzt
Decisions Made
- Kein Import von
ModuleRegistryModuleinGroupsModule— der Plan-Action-Text formulierte dies als „falls der Service dessen Registry-Methoden benötigt“;ModuleGrantsServiceinjiziert ausschließlichPrismaService, der Import wäre unbenutzte Kopplung gewesen grantprüft die Modul-Aktivierung ausschließlich übertenantModuleActivation.findUnique, ohne separatenmodule.findUnique-Existenzcheck — eine nicht existierendemoduleIdlandet bereits über die fehlende Aktivierung in derselbenBadRequestException, ein zweiter Query wäre redundant gewesen und ist im<behavior>-Block auch nicht gefordert
Deviations from Plan
1. [Sonstiges — Akzeptanzkriterium-Diskrepanz, keine Rule 1-4] DTO-Grep-Kriterium zählt 2 statt 3
- Found during: Task 1, beim Verifizieren der Akzeptanzkriterien nach dem Schreiben von
create-module-grant.dto.ts - Issue: Das Akzeptanzkriterium
grep -cE '(moduleId|groupId|userId)[?]?: string' ... gibt 3 auserwartet implizit ein Pflichtfeld ohne Definite-Assignment-Assertion (moduleId: string). Das Projekt erzwingt aberstrict: true(inkl.strictPropertyInitialization) intsconfig.base.json, und jede bestehende DTO im Projekt (CreateGroupDto,AddGroupMembersDto,CreateWidgetDtou. a.) schreibt Pflichtfelder deshalb alsfeld!: string. MitmoduleId!: string;liefert der Grep nur 2 Treffer (groupId?:,userId?:), weil das!zwischen Feldname und:steht und die Regex[?]?nur ein optionales?erlaubt - Warum keine Codeänderung:
moduleId: string;ohne!brichttsc --noEmitmitTS2564: Property 'moduleId' has no initializer— das zweite, härtere Akzeptanzkriterium desselben Tasks. Die beiden Kriterien stehen im Widerspruch; die Projekt-Konvention (!für Pflichtfelder) und ein sauberertype-checkwiegen schwerer als der wörtliche Grep-Zähler eines einzelnen Akzeptanzkriteriums - Verifiziert stattdessen:
pnpm --filter @tessera/api run type-checkläuft fehlerfrei, alle 20 Service-Tests inkl. der Entweder-oder-/XOR-Fälle sind grün, undgrep -cE '^\s+[a-zA-Z]+[?]?:' ...zeigt weiterhin genau die drei erwarteten Feldnamen (kein viertes Feld) - Files modified:
apps/api/src/groups/dto/create-module-grant.dto.ts - Commit:
5e256db
Issues Encountered
- Für den End-to-End-Nachweis lief zunächst ein veralteter
dist/main-Prozess aus einer vorherigen Sitzung (Build-Stand von 15-05, 15:36 Uhr) auf Port 3001 — beendet,pnpm --filter @tessera/api buildneu ausgeführt, mit denselbenDATABASE_URL/JWT_SECRET-Werten wie die vorherige Sitzung (Container-IP172.19.0.2,tessera:tessera_dev) neu gestartet - Für den Fremdmandanten-Nachweis (T-15-01) wurde per
psqlein zweiter, isolierter Test-Tenant samt Gruppe und Benutzer angelegt (analog zum in 15-02 etablierten Verfahren) — nach dem Nachweis vollständig wieder aus der DB entfernt - Für den D-09-Nachweis (sofortiger Entzug) wurde vorübergehend der einzige produktionsnahe Gruppen-Grant (aus dem D-06-Backfill: „Alle Benutzer“ →
tender-radar) entzogen und danach exakt wiederhergestellt — die lokale DB enthält nach diesem Plan denselbenModuleGrant-Datensatz wie zuvor (neueid, identischetenantId/moduleId/groupId) - Zwei Testbenutzer (
test-admin-1503,test-user-1503) und ein Test-Fremdmandant (test-foreign-1503samt Tenant/Gruppe) wurden ausschließlich für die Dauer der Verifikation angelegt und danach vollständig entfernt —SELECT * FROM "User"/"Tenant"zeigt nach dem Aufräumen wieder exakt den Ausgangsstand (1 Benutzer, 1 Mandant) isDefaultder Gruppe „Alle Benutzer“ war bereits vor diesem Planfalse(siehelocal_db_noteim Auftrag) — dieser Plan hat daran nichts geändert und war davon nicht betroffen, dagrant/revoke/getMatrix/getUserAccessnicht aufisDefaultzugreifen
User Setup Required
None - keine externe Service-Konfiguration nötig.
Next Phase Readiness
ModuleGrantsService.getImpact-Äquivalent für Freigaben existiert nicht in diesem Plan (nicht gefordert) — Plan 15-06/15-07 bauen die Admin-Oberflächen (Matrix-Seite, Benutzer-Detail) direkt aufgetMatrix/getUserAccess/grant/revokeaufGET /modules/catalogist bereit fürMarketplaceCard(Plan 15-08) — beide Flags kommen in einer Antwort, keine Karte kann kurzzeitig ohne Sperrhinweis klickbar erscheinen- Kein Bestandsverhalten gebrochen: volle API-Testsuite (493/493) grün,
type-checkfehlerfrei - Kein offener Blocker aus diesem Plan
Phase: 15-modul-berechtigungen-gruppen-user-grants Completed: 2026-08-04
Self-Check: PASSED
Alle in dieser SUMMARY genannten Dateien existieren auf der Platte, alle genannten Commit-Hashes (5e256db, 072fb7f, 1c32543) sind im Git-Log auffindbar.