| quick-260907-e8k |
01 |
auth |
| nextjs |
| app-router |
| server-components |
| next-intl |
| vitest |
| module-access |
|
| phase |
provides |
| 15-modul-berechtigungen-gruppen-user-grants |
checkModuleAccess (module-access-actions.ts) — fails-closed session-cookie-based access check against GET /modules/active |
|
|
| Shared ModuleAccessDenied 403 markup component (translation-free, props-driven) |
| Reusable ModuleAccessGate server component wrapping checkModuleAccess with a second fail-closed layer |
| layout.tsx access gate for each of the four module-owned route trees (cert-manager, dkv-fleet, domaincheck, tender-radar) |
| Generic [category]/[moduleSlug]/page.tsx route rewired onto the shared gate |
| module-layouts.test.tsx guarding against future module directories missing a layout |
|
| 15-modul-berechtigungen-gruppen-user-grants |
| module-routing |
| module-marketplace |
|
| tokens |
tasks |
commits |
| 5709 |
2 |
2 |
|
| added |
patterns |
|
|
| Server-side access gate as a reusable component (ModuleAccessGate) consumed both via App Router layout.tsx (covers whole route subtrees) and directly in a page — single source of truth for the 403 decision and markup |
| Fail-closed defense in depth: checkModuleAccess already fails closed; ModuleAccessGate adds a second try/catch layer so a future change to the check function cannot silently open the gate |
|
|
| created |
modified |
| apps/web/src/components/modules/module-access-denied.tsx |
| apps/web/src/components/modules/module-access-gate.tsx |
| apps/web/src/components/modules/module-access-gate.test.tsx |
| apps/web/src/app/(portal)/modules/cert-manager/layout.tsx |
| apps/web/src/app/(portal)/modules/dkv-fleet/layout.tsx |
| apps/web/src/app/(portal)/modules/domaincheck/layout.tsx |
| apps/web/src/app/(portal)/modules/tender-radar/layout.tsx |
| apps/web/src/app/(portal)/modules/module-layouts.test.tsx |
|
| apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx |
| apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx |
|
|
| 403 markup and translation lookup live once in ModuleAccessGate/ModuleAccessDenied; the generic route and all four module-owned layouts render the same component instead of each having its own inline check |
| ModuleAccessDenied stays translation-free (props for title/body/backToDashboard) — only ModuleAccessGate calls getTranslations, keeping the presentational component trivially testable without mocking next-intl |
| A layout.tsx per module directory is used instead of per-page checks, because a Next.js App Router layout automatically wraps every nested subroute — my-sources/settings/vehicles close without any file of their own |
|
| Module directory coverage test: module-layouts.test.tsx reads the modules/ directory via node:fs at test time and requires a layout.tsx for every non-dynamic subdirectory, so a future module added without a gate fails CI instead of silently reopening the vulnerability |
|
|
| id |
description |
requirement |
verification |
human_judgment |
| D1 |
ModuleAccessDenied — shared, translation-free 403 markup component |
PERM-04 |
| kind |
ref |
status |
| unit |
apps/web/src/components/modules/module-access-gate.test.tsx#renders the 403 heading, body, and dashboard link and not the children when access is denied |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
| D2 |
ModuleAccessGate — server-side access gate calling checkModuleAccess, fails closed on denial and on thrown exceptions, passes children through only on explicit grant |
PERM-04 |
| kind |
ref |
status |
| unit |
apps/web/src/components/modules/module-access-gate.test.tsx (4 cases: granted, denied, exception, slug pass-through) |
pass |
|
|
false |
|
| id |
description |
requirement |
verification |
human_judgment |
rationale |
| D3 |
layout.tsx for cert-manager, dkv-fleet, domaincheck, tender-radar — each wraps its whole route subtree (including nested routes like my-sources, settings, vehicles) in ModuleAccessGate with the correct slug |
PERM-04 |
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/module-layouts.test.tsx#module layouts — ModuleAccessGate slug wiring |
pass |
|
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/module-layouts.test.tsx#module directory coverage — every module has a layout |
pass |
|
| kind |
ref |
status |
| manual_procedural |
Browser-Gegenprobe (Abschnitt A/B/C in 260907-e8k-PLAN.md) — vom Orchestrator nach Docker-Rebuild auszufuehren |
unknown |
|
|
true |
Der eigentliche Behebungserfolg (403 als Serveranwort statt Modulseite, keine Ueberdeckung fuer berechtigte Nutzer/Admins) ist nur im echten Browser gegen die Docker-Instanz mit drei realen Konten pruefbar — WINDOWS #10 bleibt laut Plan explizit offen, bis diese Gegenprobe bestanden ist. |
|
| id |
description |
requirement |
verification |
human_judgment |
| D4 |
Generische Route [category]/[moduleSlug]/page.tsx auf ModuleAccessGate umgestellt, inline-403-Markup entfernt |
PERM-04 |
| kind |
ref |
status |
| unit |
apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx#ExpandedModulePage — passes slug through to ModuleAccessGate |
pass |
|
|
false |
|
|
25min |
2026-09-07 |
complete |