| 15-modul-berechtigungen-gruppen-user-grants |
08 |
ui |
| nextjs |
| next-intl |
| react |
| server-components |
| marketplace |
| module-access |
|
| phase |
plan |
provides |
| 15-modul-berechtigungen-gruppen-user-grants |
01 |
ModuleAccessService.getAccessibleModuleIds als Single Source of Truth (D-01), GET /modules/active auf Benutzer-Ebene |
|
| phase |
plan |
provides |
| 15-modul-berechtigungen-gruppen-user-grants |
03 |
GET /modules/catalog mit isActiveForTenant/hasAccess in einer Antwort (D-08) |
|
| phase |
plan |
provides |
| 15-modul-berechtigungen-gruppen-user-grants |
06 |
i18n-Schluessel modules.accessDenied.*, marketplace.statusNoAccess/toastNoAccess |
|
|
| checkModuleAccess (module-access-actions.ts) — serverseitige Zugriffsprüfung über GET /modules/active, fail-closed |
| Modulseiten-Route als async Server Component mit 403-Zustand direkt als Server-Antwort (D-07), Whitelist-Prüfung unverändert in module-shell.tsx ausgelagert |
| MarketplaceCard mit drittem Zustand hasAccess/Kein-Zugriff-Badge, Klick navigiert oder zeigt Toast (D-08) |
| Marketplace-Katalog (page.tsx, [slug]/page.tsx) auf GET /modules/catalog umgestellt, fuer jeden authentifizierten Benutzer sichtbar — Aktivieren/Deaktivieren bleibt ADMIN/SUPER_ADMIN vorbehalten |
|
|
| tokens |
tasks |
commits |
| 13400 |
2 |
2 |
|
| added |
patterns |
|
|
| Server-Component-Split fuer Modulseiten: page.tsx wird async ohne 'use client', Zugriffscheck ueber Server Action mit Cookie-Weiterleitung (fetchCurrentUser-Muster), unveraenderter Client-Inhalt wandert in eine eigene Client-Huelle (module-shell.tsx) |
| vi.doMock + vi.resetModules() statt statischem vi.mock fuer Module, deren Verhalten zwischen Tests derselben Datei wechseln muss (module-access.test.tsx: page-Tests brauchen ein gemocktes ModuleShell, der Whitelist-Test braucht die echte Implementierung) |
| Karten-Klick delegiert an zwei getrennte Callback-Props (onOpenDetail/onLockedClick) statt eigener Router-Logik in der Karte — haelt MarketplaceCard praesentational und ueber vi.fn() leicht testbar |
|
|
| created |
modified |
| apps/web/src/lib/module-access-actions.ts |
| apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-shell.tsx |
| apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx |
|
| apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx |
| apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.tsx |
| apps/web/src/app/(portal)/marketplace/components/MarketplaceCard.test.tsx |
| apps/web/src/app/(portal)/marketplace/page.tsx |
| apps/web/src/app/(portal)/marketplace/[slug]/page.tsx |
| apps/web/src/app/(portal)/marketplace/marketplace.test.tsx |
| apps/web/src/app/(portal)/marketplace/marketplace-filters.test.tsx |
| apps/web/src/app/(portal)/marketplace/tenant-selector.test.tsx |
| apps/web/src/app/(portal)/marketplace/[slug]/detail.test.tsx |
|
|
| [Rule 2] Marketplace-Seiten (/marketplace, /marketplace/[slug]) waren komplett isAdmin-gated (USER sah nur 'Zugriff verweigert') — das widersprach D-08 (Katalog bleibt Schaufenster fuer jeden authentifizierten Benutzer) und haette das neue Sperr-Badge nie sichtbar gemacht, da nur ADMIN/SUPER_ADMIN je die Seite erreicht haetten und diese das Badge wegen des Rollen-Bypass nie sehen. isAdmin gated jetzt ausschliesslich die Aktivieren/Deaktivieren-Aktion (canManage-Prop), nicht mehr die Sichtbarkeit der Seite |
| MarketplaceCard bekam ausserdem eine neue canManage-Prop (Rule 1): ohne sie haette ein USER auf jeder Karte einen Aktivieren/Deaktivieren-Button gesehen, der serverseitig ohnehin per RolesGuard 403 zurueckgegeben haette — der Button wird jetzt nur fuer ADMIN/SUPER_ADMIN gerendert |
| Karten-Klick-Navigation (onOpenDetail) und Sperr-Toast (onLockedClick) sind neue Callback-Props, die es vor diesem Plan nicht gab — noetig, um den im Plan/UI-SPEC geforderten Kontrast 'Klick navigiert normal / Klick zeigt Toast bei Sperre' ueberhaupt herzustellen |
|
| checkModuleAccess als zweite Server-Action neben fetchCurrentUser, identisches Cookie-Weiterleitungs- und Fail-closed-Muster |
|
|
| id |
description |
requirement |
verification |
human_judgment |
| D1 |
checkModuleAccess prueft serverseitig gegen GET /modules/active, schliesst bei fehlendem Cookie/nicht-ok-Antwort/Fehler im Zweifel (T-15-29) |
PERM-04 |
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx — 'checkModuleAccess — fails closed (T-15-29)' (2 Tests) |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D2 |
Modulseiten-Route rendert bei fehlender Freigabe das 403-Markup direkt als Server-Antwort (kein notFound(), kein Redirect, D-07), bei Freigabe die unveraenderte ModuleShell inkl. Whitelist-Schutz |
PERM-04 |
| kind |
ref |
status |
| unit |
module-access.test.tsx — 'ExpandedModulePage — server access gate' (2 Tests) + 'ModuleShell — whitelist stays independent' (1 Test) |
pass |
|
| kind |
ref |
status |
| other |
grep-Nachweise aus Task 1 acceptance_criteria: 0x 'use client' in page.tsx, 1x in module-shell.tsx, 0x notFound()/redirect(), checkModuleAccess in beiden Dateien, >=2x accessDenied, MODULE_REGISTRY unveraendert in module-shell.tsx |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D3 |
MarketplaceCard drittes Badge 'Kein Zugriff' (Bernstein) bei isActive && !hasAccess, Karte opacity-60/cursor-not-allowed, Klick zeigt Toast statt Navigation; bei Zugriff navigiert der Klick, Badge-Reihe bricht um (flex-wrap) |
PERM-04 |
| kind |
ref |
status |
| unit |
MarketplaceCard.test.tsx (4 neue Tests: Badge+Toast bei Sperre, kein Badge+Navigation bei Zugriff, unveraendertes Verhalten bei nicht aktiviertem Modul, flex-wrap in der Badge-Reihe) |
pass |
|
| kind |
ref |
status |
| other |
grep-Nachweise aus Task 2 acceptance_criteria: hasAccess>=3, statusNoAccess>=1, amber>=2 (Hell-/Dunkelvariante), flex-wrap>=1 |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D4 |
marketplace/page.tsx und marketplace/[slug]/page.tsx beziehen beide Statusflags aus einem GET /modules/catalog-Aufruf statt zwei getrennter Endpoints — kein Zwischenzustand ohne Sperr-Badge |
PERM-04 |
| kind |
ref |
status |
| unit |
marketplace.test.tsx, marketplace-filters.test.tsx, tenant-selector.test.tsx, detail.test.tsx — alle auf GET /modules/catalog-Mock umgestellt, 26 Tests gruen |
pass |
|
| kind |
ref |
status |
| other |
grep-Nachweise: modules/catalog in beiden Dateien, fetch(-Vorkommen in marketplace/page.tsx um 1 gesunken (3 -> 2) |
pass |
|
|
false |
|
| id |
description |
verification |
human_judgment |
rationale |
| D5 |
Manueller Browser-Durchklick aus dem Plan-<verification>-Block: USER ohne Freigabe sieht auf allen vier Ebenen (Sidebar, Modulseite, Marketplace-Badge+Toast, API) dasselbe Bild; ADMIN sieht alle vier Ebenen als zugaenglich |
|
true |
Diese Session hatte kein Browser-/Playwright-Tool zur Verfuegung (nur Read/Write/Edit/Bash/Skill) — der interaktive Durchlauf aus dem Plan-<verification>-Block konnte nicht ausgefuehrt werden, identisch zu 15-06/15-07 in dieser Phase. Abgedeckt ist stattdessen: voller Vitest-Lauf (185/185 gruen, 15 neue/geaenderte Tests fuer diesen Plan), fehlerfreier Produktions-Build inkl. Next.js-Typecheck, und alle grep-basierten acceptance_criteria beider Tasks. In WINDOWS.md als unrun-verify vermerkt (Eintrag #3). |
|
|
30min |
2026-08-04 |
complete |