| 15-modul-berechtigungen-gruppen-user-grants |
01 |
auth |
| prisma |
| postgres |
| nestjs |
| rls |
| module-access |
| guards |
| vitest |
|
| phase |
provides |
| 03-module-marketplace |
Module/TenantModuleActivation-Modelle, ModuleGuard/ModuleRegistryService/@UseModule als bestehendes Zugriffs-Grundgerüst |
|
|
| Group/GroupMembership/ModuleGrant-Datenmodelle mit DB-erzwungener Entweder-oder- und Ein-Default-Invariante |
| D-06-Backfill (Standardgruppe je Mandant, Bestandsbenutzer, Bestandsgrants) als Teil von `prisma migrate deploy` |
| ModuleAccessService als Single Source of Truth für Modulzugriff (D-01) |
| ModuleGuard und GET /modules/active auf Benutzer-Ebene (statt nur Mandanten-Aktivierung) umgestellt |
| RLS-Policies für die drei neuen Tabellen (T-15-11, defense-in-depth) |
|
| 15-02 |
| 15-03 |
| 15-04 |
| 15-05 |
| 15-06 |
| 15-07 |
| 15-08 |
|
| tokens |
tasks |
commits |
| 10800 |
3 |
3 |
|
| added |
patterns |
|
|
| Hand-editierte migration.sql für DB-Invarianten, die Prisma nicht ausdrücken kann (CHECK num_nonnulls, partielle Unique-Indizes) — Fortführung des in 20260618112133_rls_policies/20260721150000_tender_cpv_divisions_backfill etablierten Verfahrens |
| Migrations-Backfill mit WHERE-NOT-EXISTS-Wächtern statt separatem TS-Skript, weil er beim automatischen Container-Start (prisma migrate deploy) unbeaufsichtigt laufen muss |
| Single-Source-of-Truth Access Resolution: eine Methode (getAccessibleModuleIds) speist Guard UND Listing-Endpoint, keine zweite Implementierung |
|
|
| created |
modified |
| apps/api/prisma/migrations/20260804130130_add_groups_and_module_grants/migration.sql |
| apps/api/prisma/migrations/20260804130918_groups_rls_policies/migration.sql |
| apps/api/src/module-registry/module-access.service.ts |
| apps/api/src/module-registry/module-access.service.spec.ts |
| apps/api/src/module-registry/module.guard.spec.ts |
| apps/api/src/groups/migration-sql.spec.ts |
|
| apps/api/prisma/schema.prisma |
| apps/api/src/module-registry/module.guard.ts |
| apps/api/src/module-registry/module-registry.controller.ts |
| apps/api/src/module-registry/module-registry.module.ts |
|
|
| D-01/D-05/D-06/D-02 wie in 15-CONTEXT.md gesperrt umgesetzt (Nutzer-Checkpoint mit 'proceed' bestätigt) |
| RLS für Group/GroupMembership/ModuleGrant aktiviert (T-15-11) statt sie wie Tender* RLS-frei zu lassen — sie steuern Zugriff wie die Auth-Kerntabellen |
| grantedIds.length===0 kurzschließt die zweite Query in getAccessibleModuleIds (kein leerer IN-Filter gegen tenantModuleActivation) — Optimierung, keine Verhaltensänderung |
|
| Pattern 1 aus 15-RESEARCH.md (Single-Source-of-Truth Access Resolution) 1:1 umgesetzt |
| Pattern 2 (Per-Request-Memoisierung über request.moduleAccessIds statt Scope.REQUEST) im Guard verankert |
|
|
| id |
description |
requirement |
verification |
human_judgment |
| D1 |
Group/GroupMembership/ModuleGrant-Schema mit DB-erzwungener Entweder-oder-Beziehung (CHECK num_nonnulls) und Ein-Default-pro-Mandant (partieller Unique-Index) |
PERM-06 |
| kind |
ref |
status |
| unit |
apps/api/src/groups/migration-sql.spec.ts — add_groups_and_module_grants migration.sql (6 Tests) |
pass |
|
| kind |
ref |
status |
| manual_procedural |
psql gegen lokale DB: INSERT ohne groupId/userId schlägt mit ModuleGrant_group_xor_user fehl; SELECT count(*) FROM "Group" WHERE isDefault=true entspricht count(*) FROM "Tenant" |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D2 |
D-06-Migrations-Backfill: pro Mandant eine Standardgruppe mit allen Bestandsbenutzern und Grants für alle aktiven Module, idempotent bei Wiederholungslauf |
PERM-06 |
| kind |
ref |
status |
| unit |
apps/api/src/groups/migration-sql.spec.ts — gen_random_uuid()/NOT EXISTS/Reihenfolge-Tests |
pass |
|
| kind |
ref |
status |
| manual_procedural |
Migration gegen lokale DB angewendet: 1 Tenant -> 1 Default-Gruppe, 1 User -> 1 Membership, 1 aktive Aktivierung -> 1 Grant |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D3 |
ModuleAccessService.getAccessibleModuleIds als Single Source of Truth (D-01) — ADMIN/SUPER_ADMIN-Kurzschluss, Direkt- und Gruppen-Grants, Schnittmenge mit aktiven Aktivierungen (D-02) |
PERM-04 |
| kind |
ref |
status |
| unit |
apps/api/src/module-registry/module-access.service.spec.ts (13 Tests, deckt alle <behavior>-Fälle inkl. adjacency/empty/idempotency/concurrency) |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D4 |
ModuleGuard erzwingt getAccessibleModuleIds statt der alten tenant-only isModuleActive-Prüfung; GET /modules/active nutzt dieselbe Auflösung |
PERM-05 |
| kind |
ref |
status |
| unit |
apps/api/src/module-registry/module.guard.spec.ts (7 Tests) |
pass |
|
| kind |
ref |
status |
| e2e |
curl gegen laufende lokale API: USER ohne Grant -> 403 auf GET /modules/tender-radar + [] in GET /modules/active; USER mit Direkt-Grant -> 200 + Slug in der Liste; ADMIN ohne Grant -> 200 + Slug in der Liste (vor UND nach der RLS-Migration in Task 3 identisch reproduziert) |
pass |
|
|
false |
|
| id |
description |
verification |
human_judgment |
| D5 |
RLS-Policies für Group/GroupMembership/ModuleGrant (T-15-11), defense-in-depth analog Auth-Kerntabellen |
| kind |
ref |
status |
| unit |
apps/api/src/groups/migration-sql.spec.ts — groups_rls_policies migration.sql (4 Tests) |
pass |
|
| kind |
ref |
status |
| manual_procedural |
Task-2-E2E-Nachweis nach Anwenden der RLS-Migration unverändert reproduziert (kein Verhaltensbruch) |
pass |
|
|
false |
|
|
24min |
2026-08-04 |
complete |