Files
proxdeploy/tests/zugang.test.ts
T
florian.wildeandClaude Opus 5.5 9fdced62db feat(api): API-Tokens, kurze Anfragen und OpenAPI für die CLI
- ApiToken (nur SHA-256), Bearer-Anmeldung, POST /auth/token für proxdeploy login, Verwaltung in den Einstellungen
- anfrageErgaenzen: Storage, Bridge, Node, Benutzer und Schlüssel aus Server-Standards, Verbindungstest und Anmeldung – auch beim Template-Bau
- Abschlussbox serverseitig (abschluss), Web und CLI zeigen dieselben Zeilen
- Antwortschemas als zod, daraus /api/v1/openapi.json und cli/api/openapi.json (pnpm openapi)

Refs #013

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 15:08:27 +02:00

67 lines
2.7 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.
import { readdirSync, readFileSync, statSync } from "node:fs";
import { join, relative } from "node:path";
import { describe, expect, it } from "vitest";
import { istOeffentlich, OEFFENTLICH } from "@/proxy";
/**
* Strukturelle Prüfung: jede Seite und jeder Route Handler prüft sein Recht selbst.
* Ausnahmen stehen hier namentlich mit Begründung – ein neuer Handler ohne Prüfung soll
* jemanden zwingen, sie aufzuschreiben.
*/
const AUSNAHMEN: Record<string, string> = {
"src/app/api/v1/auth/anmelden/route.ts": "Anmeldung selbst",
"src/app/api/v1/auth/token/route.ts":
"proxdeploy login: Passwort gegen Token – prüft das Passwort selbst (mit Sperre)",
"src/app/api/v1/auth/abmelden/route.ts": "Abmelden muss ohne gültige Sitzung gehen",
"src/app/api/v1/gesundheit/route.ts": "Healthcheck, verrät nur die Version",
"src/app/anmelden/page.tsx": "Anmeldeseite",
"src/app/kein-recht/page.tsx": "zeigt nur einen Hinweis",
};
function dateien(verzeichnis: string): string[] {
return readdirSync(verzeichnis).flatMap((name) => {
const pfad = join(verzeichnis, name);
return statSync(pfad).isDirectory() ? dateien(pfad) : [pfad];
});
}
const wurzel = process.cwd();
const kandidaten = dateien(join(wurzel, "src/app"))
.map((pfad) => relative(wurzel, pfad))
.filter((pfad) => pfad.endsWith("/page.tsx") || pfad.endsWith("/route.ts"));
describe("Zugangsprüfung", () => {
it.each(kandidaten.filter((pfad) => !(pfad in AUSNAHMEN)))("%s prüft sein Recht", (pfad) => {
const quelltext = readFileSync(join(wurzel, pfad), "utf8");
expect(quelltext).toMatch(/seiteSchuetzen\(|rechtErforderlich\(/);
});
it("jede Ausnahme gibt es noch", () => {
for (const pfad of Object.keys(AUSNAHMEN)) expect(kandidaten).toContain(pfad);
});
it("öffentliche Pfade sind genau die bekannten", () => {
expect(OEFFENTLICH).toEqual([
"/anmelden",
"/api/v1/auth",
"/api/v1/gesundheit",
"/brand",
"/manifest.webmanifest",
]);
expect(istOeffentlich("/api/v1/auth/anmelden")).toBe(true);
expect(istOeffentlich("/api/v1/authentifizieren")).toBe(false);
expect(istOeffentlich("/server")).toBe(false);
});
});
describe("Client-Komponenten", () => {
const clientDateien = dateien(join(wurzel, "src"))
.filter((pfad) => /\.tsx?$/.test(pfad) && !pfad.includes("/generated/"))
.filter((pfad) => /^["']use client["']/m.test(readFileSync(pfad, "utf8")));
it.each(clientDateien.map((p) => relative(wurzel, p)))("%s importiert nichts aus src/server", (pfad) => {
// Auch reine Typimporte nicht: sonst wandert beim nächsten Umbau doch Servercode ins Browser-Bündel.
expect(readFileSync(join(wurzel, pfad), "utf8")).not.toMatch(/from ["']@\/server\//);
});
});