Files
Florian-netz/tests/e2e/routes.manifest.ts
Matthias Hochmeister c099b3acd9 Workstream 11: Tests & Sicherheitshärtung (Phase 7)
Beweist die Auth-Gating-Garantie und härtet das System ab (Definition of
Done #1, #2, #3, #7, #8):

- Routen-Manifest (tests/e2e/routes.manifest.ts) als einzige Quelle der
  Wahrheit; anonyme Seite -> Redirect /login, anonyme API -> 401.
- Kritische auth-gating.spec.ts: genau ein Fall je Manifest-Eintrag, ohne
  Daten-Leak.
- Driftschutz (routes.manifest.spec.ts + tests/unit/routes-manifest.test.ts):
  keine ungetestete neue Route unter src/app/**.
- Default-Deny-Beweis für Server Actions (server-actions-guard.spec.ts +
  tests/unit/server-actions-guard.test.ts): jede "use server"-Funktion ruft
  als erste Anweisung einen Guard; Login-Actions per Allowlist ausgenommen.
- Wiederverwendbare reine Scanner unter tests/support (route-scan, guard-scan)
  — offline lauffähig, in Vitest und Playwright geteilt.
- rbac-scoping, search-eta, login-ratelimit, security-headers Specs (gegen
  geseedeten Server; in der Sandbox deferred, per test.skip abgesichert).
- global-setup (Migration + Seed) und auth.setup (Login je Konto ->
  storageState); Playwright-Projekte setup -> chromium verdrahtet.
- src/lib/security/headers.test.ts: statischer Beleg für CSP, HSTS,
  X-Frame-Options DENY, nosniff, Permissions-Policy.
- vitest.config.ts: Coverage-Schwellen (>=90 %) für src/lib/search + src/lib/geo.
- package.json: Scripts test:unit, test:coverage, test:e2e, test:e2e:gating.
- docs/reference/sicherheitshaertung-checkliste.md: jeder Härtungspunkt mit
  Test/Befehl und Negativ-Probe.

Offline verifiziert: tsc --noEmit (0), vitest run (229 passed / 7 db-skipped),
drizzle-kit check (ok), next build (exit 0), next lint (0 Fehler),
playwright --list (98 Tests, 15 Dateien). DB-/Server-/Browser-abhängige
E2E-Läufe sind deferred (kein Postgres/Server in der Sandbox).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 14:17:10 +02:00

111 lines
4.3 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Kanonisches Routen-Manifest für die Auth-Gating-Garantie (Definition of
* Done #1, Querschnittsstandard 13).
*
* EINZIGE Quelle der Wahrheit darüber, welche Routen geschützt sind und wie ein
* ANONYMER Aufruf sich verhalten muss:
* - Seiten -> "redirect" (auf /login, mit callbackUrl)
* - APIs -> "401" (ohne Daten-Leak)
*
* Der Driftschutz (routes.manifest.spec.ts + tests/unit/routes-manifest.test.ts)
* stellt sicher, dass jede neue Route unter src/app/** entweder hier steht oder
* in PUBLIC_ALLOWLIST. Ungetestete Routen sind damit ausgeschlossen.
*
* Beispiel-UUIDs für dynamische Segmente: anonyme Aufrufe werden VOR jeder
* DB-Abfrage abgewiesen, daher müssen diese IDs nicht existieren.
*/
export { PUBLIC_ALLOWLIST } from "../support/route-scan";
export type AnonExpectation = "redirect" | "401";
export interface RouteEntry {
/** Konkreter URL-Pfad (dynamische Segmente bereits aufgelöst). */
path: string;
/** Erwartetes Verhalten bei anonymem Zugriff. */
expectWhenAnon: AnonExpectation;
/** true für API-Routen (request statt page-Navigation). */
api?: boolean;
}
const EX_VEHICLE = "00000000-0000-0000-0000-0000000000a1";
const EX_EQUIP = "00000000-0000-0000-0000-0000000000a2";
const EX_BRIGADE = "00000000-0000-0000-0000-0000000000a3";
const EX_TEMPLATE = "00000000-0000-0000-0000-0000000000a4";
const EX_CATEGORY = "00000000-0000-0000-0000-0000000000a5";
export const ROUTES: readonly RouteEntry[] = [
// (app) Lese-Oberflächen
{ path: "/", expectWhenAnon: "redirect" },
{ path: "/start", expectWhenAnon: "redirect" },
{ path: "/fahrzeuge", expectWhenAnon: "redirect" },
{ path: `/fahrzeuge/${EX_VEHICLE}`, expectWhenAnon: "redirect" },
{ path: "/geraete", expectWhenAnon: "redirect" },
{ path: `/geraete/${EX_EQUIP}`, expectWhenAnon: "redirect" },
{ path: "/wehren", expectWhenAnon: "redirect" },
{ path: `/wehren/${EX_BRIGADE}`, expectWhenAnon: "redirect" },
// (app)/verwaltung Wehr-Bereich
{ path: "/verwaltung/benutzer", expectWhenAnon: "redirect" },
{ path: "/verwaltung/profil", expectWhenAnon: "redirect" },
{ path: "/verwaltung/fahrzeuge", expectWhenAnon: "redirect" },
{ path: "/verwaltung/fahrzeuge/neu", expectWhenAnon: "redirect" },
{ path: `/verwaltung/fahrzeuge/${EX_VEHICLE}`, expectWhenAnon: "redirect" },
{ path: "/verwaltung/geraete", expectWhenAnon: "redirect" },
{ path: "/verwaltung/geraete/neu", expectWhenAnon: "redirect" },
{ path: `/verwaltung/geraete/${EX_EQUIP}`, expectWhenAnon: "redirect" },
// (admin) Plattform-Verwaltung
{ path: "/admin", expectWhenAnon: "redirect" },
{ path: "/admin/audit", expectWhenAnon: "redirect" },
{ path: "/admin/merkmale", expectWhenAnon: "redirect" },
{ path: "/admin/merkmale/proposals", expectWhenAnon: "redirect" },
{ path: "/admin/vorlagen", expectWhenAnon: "redirect" },
{ path: `/admin/vorlagen/${EX_TEMPLATE}`, expectWhenAnon: "redirect" },
{ path: "/admin/geraete-kategorien", expectWhenAnon: "redirect" },
{
path: `/admin/geraete-kategorien/${EX_CATEGORY}`,
expectWhenAnon: "redirect",
},
{ path: "/admin/wehren", expectWhenAnon: "redirect" },
{ path: "/admin/wehren/neu", expectWhenAnon: "redirect" },
{ path: `/admin/wehren/${EX_BRIGADE}`, expectWhenAnon: "redirect" },
// APIs (kein /api/auth, /api/health -> öffentlich/Allowlist)
{ path: "/api/geo/geocode", expectWhenAnon: "401", api: true },
{ path: "/api/geo/health", expectWhenAnon: "401", api: true },
];
/**
* Die Routen-Vorlagen (dynamische Segmente als Platzhalter), wie sie im
* Dateisystem erscheinen. Wird vom Driftschutz mit discoverAppRoutes()
* abgeglichen.
*/
export const DECLARED_ROUTE_TEMPLATES: ReadonlySet<string> = new Set([
"/",
"/start",
"/fahrzeuge",
"/fahrzeuge/[id]",
"/geraete",
"/geraete/[id]",
"/wehren",
"/wehren/[id]",
"/verwaltung/benutzer",
"/verwaltung/profil",
"/verwaltung/fahrzeuge",
"/verwaltung/fahrzeuge/neu",
"/verwaltung/fahrzeuge/[id]",
"/verwaltung/geraete",
"/verwaltung/geraete/neu",
"/verwaltung/geraete/[id]",
"/admin",
"/admin/audit",
"/admin/merkmale",
"/admin/merkmale/proposals",
"/admin/vorlagen",
"/admin/vorlagen/[id]",
"/admin/geraete-kategorien",
"/admin/geraete-kategorien/[id]",
"/admin/wehren",
"/admin/wehren/neu",
"/admin/wehren/[id]",
"/api/geo/geocode",
"/api/geo/health",
]);