docs(quick-260907-e8k): plan module access gate for module-owned routes
WINDOWS #10: der Zugriffs-Guard sitzt nur in der generischen Route modules/[category]/[moduleSlug]/page.tsx. Die vier fest verdrahteten Modulverzeichnisse laufen daran vorbei. Plan: gemeinsame 403-Komponente + ModuleAccessGate (Server Component), je ein layout.tsx pro Modulverzeichnis (deckt Unterrouten mit ab), generische Route auf dieselben Bausteine umgestellt. Zwei Tasks, Browser-Gegenprobe als human-check. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K5jtbGzC5Sf9npJ3JCjKhq
This commit is contained in:
+360
@@ -0,0 +1,360 @@
|
|||||||
|
---
|
||||||
|
quick_id: 260907-e8k
|
||||||
|
slug: zugriffs-guard-greift-nicht-auf-modul-ei
|
||||||
|
date: 2026-09-07
|
||||||
|
status: planned
|
||||||
|
relates_to: 15-modul-berechtigungen-gruppen-user-grants
|
||||||
|
windows_ref: 10
|
||||||
|
severity: high
|
||||||
|
|
||||||
|
phase: quick-260907-e8k
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_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
|
||||||
|
autonomous: true
|
||||||
|
requirements: [PERM-04]
|
||||||
|
|
||||||
|
estimate:
|
||||||
|
tokens: 45000
|
||||||
|
raw_tokens: 45000
|
||||||
|
tasks: 2
|
||||||
|
confidence: low
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Ein USER ohne Modulfreigabe bekommt auf /modules/tender-radar, /modules/tender-radar/my-sources, /modules/tender-radar/settings, /modules/dkv-fleet, /modules/dkv-fleet/settings, /modules/dkv-fleet/vehicles, /modules/cert-manager und /modules/domaincheck die 403-Seite als Serverantwort statt der Modulseite."
|
||||||
|
- "Ein USER MIT Freigabe sowie jeder ADMIN/SUPER_ADMIN sieht genau dieselben Adressen unveraendert vollstaendig — die Sperre trifft niemanden zu viel."
|
||||||
|
- "Die 403-Darstellung ist die Serverantwort selbst: keine Weiterleitung, kein Not-Found (D-07). Der Nutzer erfaehrt, dass das Modul existiert und ihm die Freigabe fehlt."
|
||||||
|
- "Die Zugriffspruefung bleibt geschlossen bei fehlendem Sitzungscookie, nicht-ok-Antwort der API, Netzfehler UND geworfener Ausnahme."
|
||||||
|
- "Die 403-Markierung steht genau einmal im Code — generische Route und modul-eigene Routen rendern dieselbe Komponente."
|
||||||
|
artifacts:
|
||||||
|
- apps/web/src/components/modules/module-access-denied.tsx
|
||||||
|
- apps/web/src/components/modules/module-access-gate.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/components/modules/module-access-gate.test.tsx
|
||||||
|
- apps/web/src/app/(portal)/modules/module-layouts.test.tsx
|
||||||
|
key_links:
|
||||||
|
- "layout.tsx jedes Modulverzeichnisses -> ModuleAccessGate mit dem Slug, der exakt dem Verzeichnisnamen entspricht (cert-manager, dkv-fleet, domaincheck, tender-radar sind zugleich die DB-Slugs)"
|
||||||
|
- "ModuleAccessGate -> checkModuleAccess -> GET /modules/active — dieselbe Zugriffsaufloesung wie Sidebar und ModuleGuard (D-01), keine zweite Rollenlogik im Frontend"
|
||||||
|
- "Ein Layout deckt in Next.js App Router alle verschachtelten Unterrouten mit ab — genau darueber werden my-sources, settings und vehicles ohne eigene Datei geschlossen"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Der serverseitige Zugriffs-Guard fuer Module greift heute nur auf der generischen
|
||||||
|
Route `modules/[category]/[moduleSlug]/page.tsx`. Die vier Module mit fest
|
||||||
|
verdrahteten eigenen Routen laufen daran vorbei und rendern fuer einen USER ohne
|
||||||
|
Freigabe die volle Seite (WINDOWS #10, gemessen am 2026-09-07).
|
||||||
|
|
||||||
|
Dieser Plan zieht die Pruefung in ein wiederverwendbares Gate und haengt sie ueber
|
||||||
|
je ein `layout.tsx` vor jedes Modulverzeichnis. Ein Layout deckt alle
|
||||||
|
Unterrouten mit ab — damit schliessen sich `my-sources`, `settings` und
|
||||||
|
`vehicles` ohne eigene Datei.
|
||||||
|
|
||||||
|
Purpose: PERM-04 gilt bisher nur fuer eine von zwei Routenformen. Es werden zwar
|
||||||
|
keine Daten preisgegeben (die API antwortet ueberall mit 403), aber der Nutzer
|
||||||
|
sieht bedienbare Knoepfe, die ins Leere laufen, und rohe englische Fehlertexte
|
||||||
|
mitten in der deutschen Oberflaeche.
|
||||||
|
Output: Eine gemeinsame 403-Komponente, ein Zugriffs-Gate als Server Component,
|
||||||
|
vier Layout-Dateien, die zugehoerigen Tests und die auf die gemeinsamen Bausteine
|
||||||
|
umgestellte generische Route.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@~/.claude/gsd-core/workflows/execute-plan.md
|
||||||
|
@~/.claude/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/STATE.md
|
||||||
|
@CLAUDE.md
|
||||||
|
@.planning/phases/15-modul-berechtigungen-gruppen-user-grants/15-UAT-2026-09-07.md
|
||||||
|
|
||||||
|
@apps/web/src/lib/module-access-actions.ts
|
||||||
|
@apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx
|
||||||
|
@apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="tracer" tdd="true">
|
||||||
|
<name>Task 1: Gemeinsame 403-Komponente und ModuleAccessGate mit Tests</name>
|
||||||
|
<files>
|
||||||
|
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
|
||||||
|
</files>
|
||||||
|
<read_first>
|
||||||
|
apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/page.tsx (das heute inline stehende 403-Markup, wird 1:1 uebernommen),
|
||||||
|
apps/web/src/lib/module-access-actions.ts (checkModuleAccess, faellt bereits geschlossen aus — T-15-29),
|
||||||
|
apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx (Mock-Muster fuer next-intl/server und vi.doMock, wird uebernommen)
|
||||||
|
</read_first>
|
||||||
|
<behavior>
|
||||||
|
- Gate mit gewaehrter Freigabe: rendert die uebergebenen Kinder, und die 403-Ueberschrift kommt nicht vor.
|
||||||
|
- Gate mit verweigerter Freigabe: rendert Ueberschrift, Erlaeuterungstext und Rueckweg der 403-Seite, und die Kinder kommen NICHT vor.
|
||||||
|
- Gate, wenn die Zugriffspruefung eine Ausnahme wirft: rendert die 403-Seite, nicht die Kinder (geschlossen fallen).
|
||||||
|
- Gate uebergibt den Slug unveraendert an die Zugriffspruefung: bei Aufruf mit "tender-radar" wird die Pruefung genau einmal und genau mit "tender-radar" aufgerufen.
|
||||||
|
- Die erwarteten deutschen Texte stehen im Test handgeschrieben ("Kein Zugriff auf dieses Modul", "Zur Startseite") und werden NICHT ueber dieselbe Uebersetzungsfunktion gebildet, die der Produktivcode nutzt — der Uebersetzungszugriff wird wie in module-access.test.tsx mit einer handgepflegten Schluesseltabelle nachgebildet.
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
Erstelle `apps/web/src/components/modules/module-access-denied.tsx` mit der
|
||||||
|
Komponente `ModuleAccessDenied`. Sie ist bewusst synchron und uebersetzungsfrei:
|
||||||
|
sie nimmt die drei fertigen Texte als Eigenschaften `title`, `body` und
|
||||||
|
`backToDashboard` entgegen und rendert das 403-Markup, das heute inline in
|
||||||
|
`[category]/[moduleSlug]/page.tsx` steht — dieselbe Aussenhuelle, dasselbe
|
||||||
|
Schloss-Symbol, derselbe `Link` auf `/`. Kopiere das Markup unveraendert, damit
|
||||||
|
die bereits im Browser bestaetigte Ebene 2 der UAT optisch identisch bleibt.
|
||||||
|
Die Komponente traegt keine Client-Direktive am Dateikopf.
|
||||||
|
|
||||||
|
Erstelle `apps/web/src/components/modules/module-access-gate.tsx` mit der
|
||||||
|
asynchronen Server-Komponente `ModuleAccessGate({ moduleSlug, children })`. Sie
|
||||||
|
ruft `checkModuleAccess(moduleSlug)` aus `@/lib/module-access-actions` auf. Bei
|
||||||
|
gewaehrtem Zugriff gibt sie die Kinder zurueck. Andernfalls holt sie die
|
||||||
|
Uebersetzungen ueber `getTranslations('modules')` aus `next-intl/server` und
|
||||||
|
rendert `ModuleAccessDenied` mit `accessDenied.title`, `accessDenied.body` und
|
||||||
|
`accessDenied.backToDashboard`. Es entstehen KEINE neuen i18n-Schluessel — die
|
||||||
|
drei existieren bereits in `src/messages/de.json` und `src/messages/en.json`
|
||||||
|
unter `modules.accessDenied`.
|
||||||
|
|
||||||
|
Umschliesse den Aufruf der Zugriffspruefung mit einer Fehlerbehandlung, die jede
|
||||||
|
geworfene Ausnahme als "kein Zugriff" wertet. `checkModuleAccess` faengt heute
|
||||||
|
selbst ab und wirft nicht; die Absicherung im Gate ist die zweite Verteidigungslinie,
|
||||||
|
damit ein spaeterer Umbau der Pruefung das Gate nicht versehentlich oeffnet. Die
|
||||||
|
Bedingung ist ausdruecklich "nur bei explizit gewaehrtem Zugriff durchlassen",
|
||||||
|
niemals "nur bei explizit verweigertem Zugriff sperren". Keine Rollenabfrage im
|
||||||
|
Frontend: ADMIN und SUPER_ADMIN loesen ueber `GET /modules/active` ohnehin als
|
||||||
|
zugriffsberechtigt auf (D-01). Das Gate leitet nicht weiter und ruft kein
|
||||||
|
Not-Found auf — die 403-Darstellung ist die Serverantwort (D-07). Die Datei
|
||||||
|
traegt keine Client-Direktive am Dateikopf.
|
||||||
|
|
||||||
|
Erstelle `apps/web/src/components/modules/module-access-gate.test.tsx` nach dem
|
||||||
|
Muster von `module-access.test.tsx`: `next-intl/server` per `vi.mock` mit einer
|
||||||
|
handgeschriebenen Schluesseltabelle, `@/lib/module-access-actions` per
|
||||||
|
`vi.doMock` je Testfall, `afterEach` mit `cleanup`, `vi.resetModules` und
|
||||||
|
`vi.doUnmock`. Da das Gate eine asynchrone Server-Komponente ist, wird es im Test
|
||||||
|
als Funktion aufgerufen und das Ergebnis abgewartet, bevor es an `render`
|
||||||
|
uebergeben wird — genau so, wie der bestehende Test die Seitenkomponente
|
||||||
|
behandelt. Decke die vier Faelle aus dem Verhaltensblock ab.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run src/components/modules/module-access-gate.test.tsx</automated>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web type-check</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
`module-access-gate.test.tsx` ist gruen mit mindestens vier Faellen (durchgelassen,
|
||||||
|
verweigert, Ausnahme, Slug-Weitergabe). `ModuleAccessDenied` enthaelt das
|
||||||
|
403-Markup, `ModuleAccessGate` enthaelt die Zugriffslogik, und die Typpruefung
|
||||||
|
des Web-Pakets ist fehlerfrei.
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Vier Modul-Layouts, Strukturtest und Umstellung der generischen Route</name>
|
||||||
|
<files>
|
||||||
|
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
|
||||||
|
</files>
|
||||||
|
<read_first>
|
||||||
|
apps/web/src/components/modules/module-access-gate.tsx (aus Task 1),
|
||||||
|
apps/web/src/app/(portal)/modules/[category]/[moduleSlug]/module-access.test.tsx (die beiden Testfaelle der Seitenkomponente werden umgeschrieben, die Bloecke zu checkModuleAccess und ModuleShell bleiben unangetastet)
|
||||||
|
</read_first>
|
||||||
|
<behavior>
|
||||||
|
- Jedes der vier Layouts gibt ein ModuleAccessGate zurueck, dessen Slug exakt dem Verzeichnisnamen entspricht: cert-manager, dkv-fleet, domaincheck, tender-radar. Die erwarteten Slugs stehen im Test handgeschrieben.
|
||||||
|
- Jedes Layout reicht die empfangenen Kinder unveraendert an das Gate weiter.
|
||||||
|
- Jedes Unterverzeichnis von modules/, dessen Name nicht mit einer eckigen Klammer beginnt, besitzt eine layout.tsx — ein kuenftig hinzugefuegtes Modulverzeichnis ohne Layout laesst den Test rot werden.
|
||||||
|
- Die generische Route uebergibt dem Gate den Slug aus den Routenparametern und als Kind die ModuleShell mit Kategorie und Slug.
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
Lege je eine `layout.tsx` in `apps/web/src/app/(portal)/modules/cert-manager/`,
|
||||||
|
`dkv-fleet/`, `domaincheck/` und `tender-radar/` an. Jede Datei exportiert eine
|
||||||
|
gewoehnliche, synchrone Standardfunktion, die `children` entgegennimmt und ein
|
||||||
|
`ModuleAccessGate` mit dem als Zeichenkette fest eingetragenen Slug des jeweiligen
|
||||||
|
Verzeichnisses zurueckgibt. Die Verzeichnisnamen sind zugleich die Modul-Slugs in
|
||||||
|
der Datenbank — nicht umbenennen, nicht ableiten, nicht aus dem Pfad berechnen.
|
||||||
|
Die Layouts bleiben Server Components: keine Client-Direktive am Dateikopf, keine
|
||||||
|
Hooks, keine Ereignisbehandler. Die Datei enthaelt ausser dem Import des Gates und
|
||||||
|
der Funktion nichts weiter; alle bestehenden Seiten bleiben unveraendert.
|
||||||
|
|
||||||
|
Der entscheidende Effekt: ein Layout im App Router umschliesst automatisch alle
|
||||||
|
verschachtelten Unterrouten. `tender-radar/layout.tsx` schliesst damit auch
|
||||||
|
`tender-radar/my-sources` und `tender-radar/settings`, `dkv-fleet/layout.tsx`
|
||||||
|
auch `dkv-fleet/settings` und `dkv-fleet/vehicles`. Es werden keine weiteren
|
||||||
|
Layout-Dateien in den Unterverzeichnissen angelegt.
|
||||||
|
|
||||||
|
Erstelle `apps/web/src/app/(portal)/modules/module-layouts.test.tsx`. Der Test
|
||||||
|
importiert die vier Layouts relativ, ruft jedes als Funktion mit einem
|
||||||
|
Platzhalter-Kind auf und prueft am zurueckgegebenen Element, dass die Eigenschaft
|
||||||
|
`moduleSlug` dem handgeschriebenen Erwartungswert entspricht und die Kinder
|
||||||
|
durchgereicht werden. Ergaenze einen zweiten Fall, der das Modulverzeichnis mit
|
||||||
|
`node:fs` ausliest (Pfad ueber `import.meta.url`, die Testdatei liegt selbst im
|
||||||
|
Modulverzeichnis), alle Unterverzeichnisse einsammelt, deren Name nicht mit einer
|
||||||
|
eckigen Klammer beginnt, und fuer jedes das Vorhandensein einer `layout.tsx`
|
||||||
|
verlangt. Damit faellt ein kuenftig hinzugefuegtes Modulverzeichnis ohne Gate auf.
|
||||||
|
|
||||||
|
Stelle `[category]/[moduleSlug]/page.tsx` auf die gemeinsamen Bausteine um: die
|
||||||
|
Seite liest weiterhin die Routenparameter und gibt danach ein `ModuleAccessGate`
|
||||||
|
mit dem Slug zurueck, in dem die `ModuleShell` als Kind steht. Das bisher inline
|
||||||
|
stehende 403-Markup und der direkte Aufruf der Zugriffspruefung entfallen dort
|
||||||
|
ersatzlos, ebenso die dann ungenutzten Importe. Der erklaerende Kommentarkopf der
|
||||||
|
Datei wird auf den neuen Aufbau angepasst und behaelt den Hinweis auf D-07 und
|
||||||
|
die gemeinsame Zugriffsaufloesung.
|
||||||
|
|
||||||
|
Passe `module-access.test.tsx` an: die beiden Faelle der Seitenkomponente pruefen
|
||||||
|
jetzt die Weitergabe statt der Darstellung — die Seite gibt ein Element zurueck,
|
||||||
|
dessen `moduleSlug` dem Slug aus den Routenparametern entspricht und dessen Kind
|
||||||
|
die ModuleShell mit derselben Kategorie und demselben Slug ist. Die Faelle zu
|
||||||
|
`checkModuleAccess` (faellt geschlossen) und zur ModuleShell-Whitelist bleiben
|
||||||
|
unveraendert erhalten; das Verhalten von 403 gegenueber Durchlassen ist seit
|
||||||
|
Task 1 im Gate-Test abgedeckt und wird hier nicht doppelt geprueft.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && 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</automated>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web exec vitest run</automated>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web type-check</automated>
|
||||||
|
<automated>cd /home/vicolab/projects/tessera-ctl && pnpm --filter @tessera/web build</automated>
|
||||||
|
<human-check>Browser-Gegenprobe nach Neubau des Web-Containers — die vollstaendige Adress- und Kontenmatrix steht unten im Abschnitt "Browser-Gegenprobe". Wird vom Orchestrator ausgefuehrt, nicht vom Ausfuehrenden.</human-check>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
Die vier Layout-Dateien existieren, tragen jeweils den korrekten Slug und keine
|
||||||
|
Client-Direktive. `module-layouts.test.tsx` und `module-access.test.tsx` sind
|
||||||
|
gruen, die vollstaendige Web-Testsuite ist gruen, Typpruefung und
|
||||||
|
Produktionsbau des Web-Pakets laufen fehlerfrei durch. Das 403-Markup steht nur
|
||||||
|
noch in `module-access-denied.tsx`.
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Browser -> Next.js Server Component | Der Nutzer bestimmt die Adresse frei. Sidebar und Marktplatz sind Bequemlichkeit, keine Zugriffskontrolle — ein Lesezeichen oder eine getippte Adresse umgeht beide. |
|
||||||
|
| Next.js Server Component -> NestJS API | Das Gate leitet das Sitzungscookie an `GET /modules/active` weiter; die verbindliche Entscheidung faellt in der API (ModuleAccessService), nicht im Frontend. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
||||||
|
| T-e8k-01 | Elevation of Privilege | modul-eigene Routen unter `(portal)/modules/{cert-manager,dkv-fleet,domaincheck,tender-radar}` samt Unterseiten | high | mitigate | Je ein `layout.tsx` pro Modulverzeichnis ruft `ModuleAccessGate`; das Layout umschliesst alle verschachtelten Unterrouten automatisch (Task 2) |
|
||||||
|
| T-e8k-02 | Information Disclosure | `ModuleAccessGate` bei Netzfehler oder geworfener Ausnahme der Zugriffspruefung | medium | mitigate | Fehlerbehandlung im Gate wertet jede Ausnahme als "kein Zugriff"; durchgelassen wird nur bei explizit gewaehrtem Zugriff (Task 1, Testfall 3) |
|
||||||
|
| T-e8k-03 | Tampering | Falscher Slug in einem der vier Layouts (Kopierfehler zwischen fast identischen Dateien) | medium | mitigate | `module-layouts.test.tsx` prueft je Verzeichnis den handgeschriebenen Erwartungs-Slug (Task 2) |
|
||||||
|
| T-e8k-04 | Elevation of Privilege | Kuenftig hinzugefuegtes Modulverzeichnis ohne Layout laeuft erneut am Gate vorbei | medium | mitigate | `module-layouts.test.tsx` liest das Modulverzeichnis aus und verlangt fuer jedes nicht-dynamische Unterverzeichnis eine `layout.tsx` (Task 2) |
|
||||||
|
| T-e8k-05 | Denial of Service | Ueberschiessende Sperre trifft ADMIN/SUPER_ADMIN oder freigegebene USER | medium | mitigate | Keine Rollenlogik im Frontend; das Gate fragt ausschliesslich `checkModuleAccess` (D-01). Browser-Gegenprobe deckt `admin` und `nutzer1` ausdruecklich ab |
|
||||||
|
|
||||||
|
Keine Paketinstallationen in diesem Plan — das Package-Legitimacy-Gate entfaellt.
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
|
||||||
|
## Automatisiert
|
||||||
|
|
||||||
|
1. `pnpm --filter @tessera/web exec vitest run` — vollstaendige Web-Suite gruen,
|
||||||
|
inklusive der neuen Dateien `module-access-gate.test.tsx` und
|
||||||
|
`module-layouts.test.tsx`.
|
||||||
|
2. `pnpm --filter @tessera/web type-check` — fehlerfrei.
|
||||||
|
3. `pnpm --filter @tessera/web build` — der Produktionsbau muss durchlaufen. Das
|
||||||
|
ist zugleich die Probe darauf, dass die Layouts echte Server Components
|
||||||
|
geblieben sind: eine Client-Direktive wuerde am Import der Server-Aktion
|
||||||
|
scheitern.
|
||||||
|
|
||||||
|
## Browser-Gegenprobe (human-check, vom Orchestrator auszufuehren)
|
||||||
|
|
||||||
|
Die Weboberflaeche laeuft aus einem gebauten Docker-Image. Vor jeder Pruefung
|
||||||
|
neu bauen und ersetzen, ein blosses Hochfahren baut nicht neu:
|
||||||
|
|
||||||
|
docker compose build web && docker compose up -d --force-recreate web
|
||||||
|
|
||||||
|
Danach `http://localhost:3000` im echten Browser. Zwischen den Konten jeweils
|
||||||
|
sauber abmelden. **Jede Adresse direkt eintippen**, nicht ueber die Sidebar
|
||||||
|
navigieren — geprueft wird genau der Weg an der Sidebar vorbei. Bei jedem
|
||||||
|
Adresswechsel innerhalb desselben Moduls einmal neu laden.
|
||||||
|
|
||||||
|
**A — `nutzer2` / `Test1234!` (USER, KEINE Freigabe fuer tender-radar, HAT
|
||||||
|
cert-manager ueber die Gruppe "Alle Benutzer")**
|
||||||
|
|
||||||
|
| Adresse | Erwartung |
|
||||||
|
|---|---|
|
||||||
|
| `/modules/procurement/tender-radar` | 403-Seite "Kein Zugriff auf dieses Modul" (unveraendert gegenueber heute) |
|
||||||
|
| `/modules/tender-radar` | 403-Seite statt Trefferliste — der eigentliche Befund |
|
||||||
|
| `/modules/tender-radar/my-sources` | 403-Seite statt Postfach- und Feed-Formular |
|
||||||
|
| `/modules/tender-radar/settings` | 403-Seite |
|
||||||
|
| `/modules/dkv-fleet` | 403-Seite, insbesondere ohne den Knopf "Jetzt pruefen" |
|
||||||
|
| `/modules/dkv-fleet/settings` | 403-Seite |
|
||||||
|
| `/modules/dkv-fleet/vehicles` | 403-Seite |
|
||||||
|
| `/modules/cert-manager` | **volle Seite** — hier besteht eine Freigabe, die Sperre darf nicht ueberschiessen |
|
||||||
|
| `/modules/domaincheck` | Ergebnis muss zu dem passen, was die Freigaben-Matrix in der Administration fuer `nutzer2` und domaincheck ausweist: mit Freigabe volle Seite, ohne Freigabe die 403-Seite |
|
||||||
|
|
||||||
|
Zusaetzlich auf allen 403-Seiten pruefen: die Adresszeile bleibt unveraendert
|
||||||
|
stehen (keine Weiterleitung, D-07), die Seite ist deutsch, und es erscheinen
|
||||||
|
keine rohen englischen Techniktexte.
|
||||||
|
|
||||||
|
**B — `nutzer1` / `Test1234!` (USER, HAT tender-radar ueber Gruppe und Direkt-Grant)**
|
||||||
|
|
||||||
|
| Adresse | Erwartung |
|
||||||
|
|---|---|
|
||||||
|
| `/modules/tender-radar` | volle Seite mit Trefferliste |
|
||||||
|
| `/modules/tender-radar/my-sources` | volle Seite mit Postfach- und Feed-Formular |
|
||||||
|
| `/modules/tender-radar/settings` | Seite laedt (keine 403-Seite); die bereits bestehende Rollenunterscheidung innerhalb der Seite bleibt davon unberuehrt |
|
||||||
|
|
||||||
|
**C — `admin` / `admin123` (SUPER_ADMIN)**
|
||||||
|
|
||||||
|
| Adresse | Erwartung |
|
||||||
|
|---|---|
|
||||||
|
| `/modules/tender-radar` | volle Seite |
|
||||||
|
| `/modules/tender-radar/my-sources` | volle Seite |
|
||||||
|
| `/modules/dkv-fleet` | volle Seite |
|
||||||
|
| `/modules/cert-manager` | volle Seite |
|
||||||
|
| `/modules/domaincheck` | volle Seite |
|
||||||
|
|
||||||
|
Faellt hier auch nur eine Seite auf 403, ist die Aenderung zu scharf und muss
|
||||||
|
zurueck — SUPER_ADMIN umgeht Grants (D-01).
|
||||||
|
|
||||||
|
## Ledger
|
||||||
|
|
||||||
|
`WINDOWS.md` #10 bleibt offen, bis Abschnitt A, B und C im Browser bestanden
|
||||||
|
sind. Erst dann auf geschlossen setzen, mit Datum und Belegbild.
|
||||||
|
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
|
||||||
|
- Ein USER ohne Freigabe erhaelt auf allen acht in Abschnitt A gelisteten
|
||||||
|
Adressen die 403-Seite als Serverantwort, nicht die Modulseite.
|
||||||
|
- Ein USER mit Freigabe und jeder SUPER_ADMIN erreichen dieselben Adressen
|
||||||
|
unveraendert vollstaendig.
|
||||||
|
- Adresszeile bleibt bei der 403-Seite stehen: keine Weiterleitung, kein
|
||||||
|
Not-Found (D-07).
|
||||||
|
- Die Zugriffspruefung faellt weiterhin geschlossen aus — fehlendes Cookie,
|
||||||
|
nicht-ok-Antwort, Netzfehler und geworfene Ausnahme fuehren alle zur 403-Seite.
|
||||||
|
- Das 403-Markup existiert genau einmal im Code; die generische Route und die
|
||||||
|
vier modul-eigenen Routen rendern dieselbe Komponente.
|
||||||
|
- Vollstaendige Web-Testsuite, Typpruefung und Produktionsbau sind gruen.
|
||||||
|
- Ein neu angelegtes Modulverzeichnis ohne `layout.tsx` laesst
|
||||||
|
`module-layouts.test.tsx` fehlschlagen.
|
||||||
|
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Erstelle `.planning/quick/260907-e8k-zugriffs-guard-greift-nicht-auf-modul-ei/260907-e8k-SUMMARY.md`, wenn fertig.
|
||||||
|
</output>
|
||||||
Reference in New Issue
Block a user