(req as any) und @Req() req: any durch AuthenticatedRequest ersetzt in
dashboard, favorites, calendar, groups, module-grants, module-registry,
tenders, dkv, ldap, settings; @CurrentUser() in user.controller auf AuthUser.
Die abwehrenden Pruefungen ("No tenant context", "No user context") bleiben
lebendig, weil user auf dem Anfragetyp wahlfrei ist - genau das beschreibt
den Zustand auf oeffentlichen Wegen.
Nebengewinn ohne neue Zusicherungen: req.tenantId as string | undefined
(dkv, settings), file.buffer as Buffer und file.mimetype as string
(dkv, user) sind weggefallen, weil der Typ sie jetzt traegt.
BEFUND 1 (D-03, gemeldet) dashboard.controller.ts:74 alt: der Handler las
req.user?.role NACH extractContext und gab sie an getWidgets(role: Role)
weiter, das eine Rolle zwingend verlangt. Die Annahme "hier gibt es immer
einen Aufrufer" stimmt - die Pruefung "No user context" erzwingt sie -, aber
sie stand in einer anderen Methode, wo der Compiler sie nicht sehen konnte.
extractContext gibt die Rolle jetzt mit zurueck: keine neue Pruefung, kein
erfundener Wert, gleiche Reihenfolge, gleiche Meldungen.
BEFUND 2 (D-03, gemeldet) tenders.controller.ts:142: resolveRequestingTenantId
erklaerte string | undefined, liest aber req.tenantId, das TenantGuard fuer
einen SUPER_ADMIN ohne Mandanten auf null setzt. Die Erklaerung war also nie
vollstaendig. Erweitert auf string | null | undefined, und buildTenderWhere
nimmt string | null - beides nur Erklaerung, kein Verhalten: die Funktion
entscheidet seit jeher ueber Wahrheitswert und faellt bei beiden zu
(nur global sichtbare Ausschreibungen).
Fixtures in user.controller.spec.ts ergaenzt (username, mustChangePassword,
originalname, size). Testzahlen unveraendert.
noExplicitAny in apps/api/src: 137 -> 66. type-check 4/4, lint 5/5,
apps/api 72/1143, apps/web 73/531, tenant.guard.ts unveraendert.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TPPB4ApQxzSU1rwV2Ffj9J
- ModuleAccessService.getCatalogFlags(tenantId, userId, role) liefert je
aktivem Modul isActiveForTenant + hasAccess in einer Auflösung
- ModuleRegistryController.findCatalog (GET /modules/catalog), erreichbar
für jeden authentifizierten Benutzer wie GET /modules (D-08)
- ADMIN/SUPER_ADMIN: hasAccess immer wahr für aktive Module (D-03)
- 4 neue Tests für getCatalogFlags
- ModuleAccessService.getAccessibleModuleIds(tenantId, userId, role):
ADMIN/SUPER_ADMIN bypass (D-03) via one query, otherwise a single
Promise.all of direct + group ModuleGrant lookups intersected against
active TenantModuleActivation (D-02) — no N+1 over the user's groups
- findAccessibleModules() adds the name-asc sort for stable sidebar order
- ModuleGuard now resolves userId/role from request.user (JWT-sourced,
never body/params) and calls getAccessibleModuleIds instead of the
tenant-only isModuleActive check; caches the result on
request.moduleAccessIds for same-request reuse (D-09, no cross-request
caching)
- ModuleRegistryController.findActive delegates to
ModuleAccessService.findAccessibleModules instead of
findActiveForTenant, which stays untouched for Plan 15-03's
tenant-wide marketplace catalog
- ModuleRegistryModule exports ModuleAccessService for Plan 15-03/15-05
- module-access.service.spec.ts / module.guard.spec.ts cover every case
in the plan's <behavior> list with a hand-rolled Prisma mock
- End-to-end verified against the running local API: a USER without a
grant gets 403 on a @UseModule-protected endpoint and an empty
/modules/active list; the same USER with a direct grant gets 200 plus
the slug in the list; an ADMIN without any grant also gets 200 (D-03)
Controller now falls back to user.tenantId from JWT when
req.tenantId is null (SUPER_ADMIN without x-tenant-id header).
Also added error display to admin modules page.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ModuleRegistryService with findAll, findBySlug, findActiveForTenant, activate/deactivate, seedModule
- ModuleRegistryController with GET /modules, GET /modules/active, POST activate/deactivate
- ModuleGuard + @UseModule() decorator for tenant-scoped module access control
- ActivateModuleDto with UUID validation
- Registered ModuleRegistryModule in AppModule imports