Ohne authentik gab es nur den Notfall-Admin – ein geteiltes Konto ohne Rollen und ohne erkennbare Akteure im Audit-Log. Admins legen jetzt lokale Konten an; sshkv erzeugt ein Einmal-Passwort, das bei der ersten Anmeldung ersetzt wird, danach ist TOTP Pflicht. Rollen, Sperren (beendet Sitzungen sofort) und Zurücksetzen unter Einstellungen → Benutzer, nur für Admins mit frischer Bestätigung. SSHKV_LOKALE_KONTEN=0 schaltet lokale Konten ab; der Notfall-Admin bleibt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47 lines
2.0 KiB
TypeScript
47 lines
2.0 KiB
TypeScript
import { readFileSync, readdirSync, statSync } from "node:fs";
|
||
import path from "node:path";
|
||
import { describe, expect, it } from "vitest";
|
||
|
||
/**
|
||
* Struktureller Test (#004, Muster aus Laufzettel): jede Seite und jeder Route Handler
|
||
* prueft selbst, wer zugreift. Die Ausnahmen stehen hier namentlich mit Begruendung – ein
|
||
* neuer ungepruefter Handler soll jemanden zwingen, sie aufzuschreiben.
|
||
*/
|
||
const AUSNAHMEN: Record<string, string> = {
|
||
"src/app/anmelden/page.tsx": "Anmeldeseite selbst",
|
||
"src/app/anmelden/totp/page.tsx": "nur mit Einrichtungs-Cookie aus richtigem Passwort erreichbar",
|
||
"src/app/anmelden/passwort/page.tsx": "nur mit Cookie aus richtigem Einmal-Passwort erreichbar (#035)",
|
||
"src/app/api/auth/abmelden/route.ts": "Abmelden braucht keine Pruefung",
|
||
"src/app/api/auth/oidc/start/route.ts": "Beginn der Anmeldung",
|
||
"src/app/api/auth/oidc/rueckruf/route.ts": "Ende der Anmeldung; prueft state, nonce und PKCE",
|
||
"src/app/api/gesundheit/route.ts": "Healthcheck, verraet nur die Version",
|
||
};
|
||
|
||
const PRUEFUNGEN = /sitzungErforderlich\(|rechtErforderlich\(|totpEinrichtungLesen\(/;
|
||
|
||
function dateien(verzeichnis: string): string[] {
|
||
return readdirSync(verzeichnis).flatMap((name) => {
|
||
const pfad = path.join(verzeichnis, name);
|
||
if (statSync(pfad).isDirectory()) return dateien(pfad);
|
||
return /^(page\.tsx|route\.ts)$/.test(name) ? [pfad] : [];
|
||
});
|
||
}
|
||
|
||
describe("Zugangspruefung", () => {
|
||
const alle = dateien("src/app").map((p) => p.split(path.sep).join("/"));
|
||
|
||
it("findet Seiten und Handler", () => {
|
||
expect(alle.length).toBeGreaterThan(10);
|
||
});
|
||
|
||
it.each(alle)("%s prueft selbst oder steht in den Ausnahmen", (datei) => {
|
||
if (AUSNAHMEN[datei]) return;
|
||
const inhalt = readFileSync(datei, "utf8");
|
||
expect(PRUEFUNGEN.test(inhalt), `${datei} prueft keine Sitzung`).toBe(true);
|
||
});
|
||
|
||
it("listet keine Ausnahme fuer eine Datei, die es nicht mehr gibt", () => {
|
||
for (const datei of Object.keys(AUSNAHMEN)) expect(alle).toContain(datei);
|
||
});
|
||
});
|