docs(quick-260907-e8k): complete Zugriffs-Guard-Fix fuer modul-eigene Routen
Beide Tasks des Plans ausgefuehrt, verifiziert und committed (4a23e13,69b6418). Automatisierte Verifikation vollstaendig gruen: Gate-Tests, Layout-Tests, volle Web-Suite (213/213), Typpruefung und Produktionsbau. Browser-Gegenprobe (A/B/C) bleibt laut Plan Aufgabe des Orchestrators nach Docker-Rebuild — WINDOWS #10 bleibt bis dahin offen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
This commit is contained in:
+176
@@ -0,0 +1,176 @@
|
||||
---
|
||||
phase: quick-260907-e8k
|
||||
plan: 01
|
||||
subsystem: auth
|
||||
tags: [nextjs, app-router, server-components, next-intl, vitest, module-access]
|
||||
|
||||
requires:
|
||||
- phase: 15-modul-berechtigungen-gruppen-user-grants
|
||||
provides: checkModuleAccess (module-access-actions.ts) — fails-closed session-cookie-based access check against GET /modules/active
|
||||
provides:
|
||||
- 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
|
||||
affects: [15-modul-berechtigungen-gruppen-user-grants, module-routing, module-marketplace]
|
||||
|
||||
actuals:
|
||||
tokens: 5709
|
||||
tasks: 2
|
||||
commits: 2
|
||||
|
||||
tech-stack:
|
||||
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"
|
||||
|
||||
key-files:
|
||||
created:
|
||||
- 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
|
||||
modified:
|
||||
- apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx
|
||||
- apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx
|
||||
|
||||
key-decisions:
|
||||
- "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"
|
||||
|
||||
patterns-established:
|
||||
- "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"
|
||||
|
||||
requirements-completed: [PERM-04]
|
||||
|
||||
coverage:
|
||||
- id: D1
|
||||
description: "ModuleAccessDenied — shared, translation-free 403 markup component"
|
||||
requirement: "PERM-04"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "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"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D2
|
||||
description: "ModuleAccessGate — server-side access gate calling checkModuleAccess, fails closed on denial and on thrown exceptions, passes children through only on explicit grant"
|
||||
requirement: "PERM-04"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/web/src/components/modules/module-access-gate.test.tsx (4 cases: granted, denied, exception, slug pass-through)"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
- id: D3
|
||||
description: "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"
|
||||
requirement: "PERM-04"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/web/src/app/(portal)/modules/module-layouts.test.tsx#module layouts — ModuleAccessGate slug wiring"
|
||||
status: pass
|
||||
- kind: unit
|
||||
ref: "apps/web/src/app/(portal)/modules/module-layouts.test.tsx#module directory coverage — every module has a layout"
|
||||
status: pass
|
||||
- kind: manual_procedural
|
||||
ref: "Browser-Gegenprobe (Abschnitt A/B/C in 260907-e8k-PLAN.md) — vom Orchestrator nach Docker-Rebuild auszufuehren"
|
||||
status: unknown
|
||||
human_judgment: true
|
||||
rationale: "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: D4
|
||||
description: "Generische Route [category]/[moduleSlug]/page.tsx auf ModuleAccessGate umgestellt, inline-403-Markup entfernt"
|
||||
requirement: "PERM-04"
|
||||
verification:
|
||||
- kind: unit
|
||||
ref: "apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx#ExpandedModulePage — passes slug through to ModuleAccessGate"
|
||||
status: pass
|
||||
human_judgment: false
|
||||
|
||||
duration: 25min
|
||||
completed: 2026-09-07
|
||||
status: complete
|
||||
---
|
||||
|
||||
# Phase quick-260907-e8k Plan 01: Modul-Zugriffs-Guard auf Layout-Ebene Summary
|
||||
|
||||
**Vier fest verdrahtete Modulrouten (cert-manager, dkv-fleet, domaincheck, tender-radar) liefen bislang komplett am 403-Guard vorbei — sie sind jetzt ueber ein layout.tsx pro Modulverzeichnis an dieselbe ModuleAccessGate-Komponente angeschlossen wie die generische Route.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** 25 min
|
||||
- **Started:** 2026-09-07T08:03Z (ca.)
|
||||
- **Completed:** 2026-09-07T08:28Z
|
||||
- **Tasks:** 2/2 completed
|
||||
- **Files modified:** 10 (8 created, 2 modified)
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Das bisher inline in `[category]/[moduleSlug]/page.tsx` stehende 403-Markup existiert jetzt genau einmal, in `ModuleAccessDenied` — eine reine, uebersetzungsfreie Praesentationskomponente.
|
||||
- `ModuleAccessGate` buendelt die Zugriffspruefung als wiederverwendbare Server Component: ruft `checkModuleAccess`, faengt zusaetzlich jede geworfene Ausnahme als "kein Zugriff" ab (zweite Verteidigungslinie ueber die bereits geschlossen ausfallende Funktion), gibt Kinder nur bei explizit gewaehrtem Zugriff durch.
|
||||
- Vier neue `layout.tsx`-Dateien (eine pro Modulverzeichnis) schliessen jetzt auch `tender-radar/my-sources`, `tender-radar/settings`, `dkv-fleet/settings` und `dkv-fleet/vehicles` ab — ohne eigene Datei je Unterroute, weil ein App-Router-Layout automatisch alle verschachtelten Unterrouten mit umschliesst.
|
||||
- Ein Struktur-Test (`module-layouts.test.tsx`) liest das Modulverzeichnis zur Testzeit per `node:fs` aus und verlangt fuer jedes nicht-dynamische Unterverzeichnis eine `layout.tsx` — ein kuenftig hinzugefuegtes Modul ohne Gate faellt damit in der Testsuite auf, nicht erst in Produktion.
|
||||
- Die generische Route ist auf dieselben Bausteine umgestellt; ihr eigener Test prueft jetzt die Weitergabe des Slugs statt die 403-Darstellung erneut zu pruefen (die liegt seit Task 1 vollstaendig in `module-access-gate.test.tsx`).
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Gemeinsame 403-Komponente und ModuleAccessGate mit Tests** - `4a23e13` (feat)
|
||||
2. **Task 2: Vier Modul-Layouts, Strukturtest und Umstellung der generischen Route** - `69b6418` (feat)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `apps/web/src/components/modules/module-access-denied.tsx` - Uebersetzungsfreie 403-Praesentationskomponente (title/body/backToDashboard als Props)
|
||||
- `apps/web/src/components/modules/module-access-gate.tsx` - Server-Component-Gate: ruft checkModuleAccess, faengt Ausnahmen ab, rendert Kinder nur bei explizitem Zugriff
|
||||
- `apps/web/src/components/modules/module-access-gate.test.tsx` - 4 Testfaelle: durchgelassen, verweigert, Ausnahme, Slug-Weitergabe
|
||||
- `apps/web/src/app/(portal)/modules/cert-manager/layout.tsx` - Gate mit Slug "cert-manager"
|
||||
- `apps/web/src/app/(portal)/modules/dkv-fleet/layout.tsx` - Gate mit Slug "dkv-fleet"
|
||||
- `apps/web/src/app/(portal)/modules/domaincheck/layout.tsx` - Gate mit Slug "domaincheck"
|
||||
- `apps/web/src/app/(portal)/modules/tender-radar/layout.tsx` - Gate mit Slug "tender-radar"
|
||||
- `apps/web/src/app/(portal)/modules/module-layouts.test.tsx` - Slug-Verdrahtung je Layout + Verzeichnis-Abdeckungstest
|
||||
- `apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx` - Auf ModuleAccessGate umgestellt, Inline-403 und direkter checkModuleAccess-Aufruf entfernt
|
||||
- `apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx` - Seiten-Testfaelle auf Gate-Weitergabe umgeschrieben, checkModuleAccess- und ModuleShell-Whitelist-Bloecke unveraendert
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- Keine neuen i18n-Schluessel: `modules.accessDenied.{title,body,backToDashboard}` existierten bereits in `de.json`/`en.json` und werden unveraendert weiterverwendet.
|
||||
- Layouts bleiben gewoehnliche synchrone Server Components ohne Client-Direktive, ohne Hooks — reiner Import + Return des Gates.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no external service configuration required.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
Automatisierte Verifikation vollstaendig gruen (Details unten). Die Browser-Gegenprobe aus Abschnitt A/B/C des Plans (drei Konten, acht+ Adressen, Docker-Rebuild) ist laut Plan explizit Aufgabe des Orchestrators und wurde in dieser Ausfuehrung nicht durchgefuehrt — WINDOWS #10 bleibt bis dahin offen.
|
||||
|
||||
## Real verification results
|
||||
|
||||
1. `pnpm --filter @tessera/web exec vitest run src/components/modules/module-access-gate.test.tsx` — **4/4 passed** (Task 1 verify)
|
||||
2. `pnpm --filter @tessera/web type-check` — clean, no output (Task 1 verify)
|
||||
3. `pnpm --filter @tessera/web exec vitest run 'src/app/(portal)/modules/module-layouts.test.tsx' 'src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx'` — **9/9 passed** (Task 2 verify)
|
||||
4. `pnpm --filter @tessera/web exec vitest run` (full suite) — **213/213 passed, 36/36 test files** (Task 2 verify)
|
||||
5. `pnpm --filter @tessera/web type-check` — clean, no output (Task 2 verify)
|
||||
6. `pnpm --filter @tessera/web build` — **succeeded**, all 27 routes generated including the four module routes and the generic route; only pre-existing, unrelated warning (jose Edge Runtime CompressionStream/DecompressionStream, not touched by this plan)
|
||||
7. Browser-Gegenprobe (human-check) — **not run**; per plan and per task instructions this is executed by the orchestrator after a Docker rebuild, not by the executor.
|
||||
|
||||
---
|
||||
*Phase: quick-260907-e8k*
|
||||
*Completed: 2026-09-07*
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
All 10 created/modified source files and this SUMMARY.md verified present on disk. Both task commits (`4a23e13`, `69b6418`) verified present in `git log`.
|
||||
Reference in New Issue
Block a user