Docker: deploy/update.sh sichert, baut, migriert und prueft die Erreichbarkeit; der Worker hat jetzt eine Zustandspruefung. Der ganze Stapel wurde durchgespielt - bauen, starten, migrieren, anmelden, Agent-Skript abrufen. Dabei ein echter Fehler aufgefallen: Nach der Anmeldung zeigte die Weiterleitung auf den internen Containernamen. Hinter einem Reverse Proxy waere das eine tote Adresse gewesen. Weiterleitungen nehmen jetzt die eingetragene Adresse, sonst APP_URL, sonst die Proxy-Kopfzeilen. Dazu Skripte nach Art der Proxmox VE Helper-Scripts (Node und PostgreSQL direkt im LXC, ohne Docker). Vorerst intern: Eine Einreichung setzt oeffentlichen Quelltext voraus und laeuft ueber ProxmoxVED. Ausserdem docs/roadmap.md: Bis 1.0.0 fehlt kein Code mehr, sondern Erprobung. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
108 lines
4.2 KiB
TypeScript
108 lines
4.2 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { herkunftStimmt } from "@/app/api/auth/anmelden/route";
|
|
import { eigeneAdresse } from "@/lib/adresse";
|
|
import type { GeleseneSitzung } from "@/lib/sitzung";
|
|
import { sitzungBewerten } from "@/server/auth/sitzungspflicht";
|
|
|
|
const sitzung = (ausgestelltAm: Date): GeleseneSitzung => ({
|
|
benutzerId: "b1",
|
|
email: "admin@example.de",
|
|
name: "Administrator",
|
|
herkunft: "passwort",
|
|
ausgestelltAm,
|
|
});
|
|
|
|
const anfrage = (kopfzeilen: Record<string, string>) =>
|
|
new Request("https://proxledger.example/api/auth/anmelden", { method: "POST", headers: kopfzeilen });
|
|
|
|
describe("Sitzungsprüfung", () => {
|
|
const jetzt = new Date("2026-09-18T12:00:00Z");
|
|
const aktiv = { aktiv: true, sitzungenUngueltigAb: null };
|
|
|
|
it("lässt eine gültige Sitzung eines aktiven Kontos durch", () => {
|
|
expect(sitzungBewerten(sitzung(jetzt), aktiv)).toEqual({ gueltig: true });
|
|
});
|
|
|
|
it("weist ab, wenn gar keine Sitzung da ist", () => {
|
|
expect(sitzungBewerten(null, aktiv).gueltig).toBe(false);
|
|
});
|
|
|
|
it("weist ein gesperrtes Konto ab, obwohl das Token noch gilt", () => {
|
|
const urteil = sitzungBewerten(sitzung(jetzt), { aktiv: false, sitzungenUngueltigAb: null });
|
|
expect(urteil).toEqual({ gueltig: false, grund: "Dieses Konto ist gesperrt." });
|
|
});
|
|
|
|
it("weist ein gelöschtes Konto ab", () => {
|
|
expect(sitzungBewerten(sitzung(jetzt), null).gueltig).toBe(false);
|
|
});
|
|
|
|
it("beendet Sitzungen, die vor dem Widerruf ausgestellt wurden", () => {
|
|
const widerruf = new Date("2026-09-18T13:00:00Z");
|
|
const urteil = sitzungBewerten(sitzung(jetzt), { aktiv: true, sitzungenUngueltigAb: widerruf });
|
|
expect(urteil.gueltig).toBe(false);
|
|
});
|
|
|
|
it("lässt Sitzungen gelten, die nach dem Widerruf entstanden sind", () => {
|
|
const widerruf = new Date("2026-09-18T11:00:00Z");
|
|
expect(sitzungBewerten(sitzung(jetzt), { aktiv: true, sitzungenUngueltigAb: widerruf })).toEqual({
|
|
gueltig: true,
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("Herkunft der Anmeldung", () => {
|
|
it("lässt das eigene Formular durch", () => {
|
|
expect(herkunftStimmt(anfrage({ origin: "https://proxledger.example", host: "proxledger.example" }))).toBe(true);
|
|
});
|
|
|
|
it("weist eine fremde Seite ab", () => {
|
|
expect(herkunftStimmt(anfrage({ origin: "https://boese.example", host: "proxledger.example" }))).toBe(false);
|
|
});
|
|
|
|
it("weist einen unbrauchbaren Origin ab", () => {
|
|
expect(herkunftStimmt(anfrage({ origin: "kein-url", host: "proxledger.example" }))).toBe(false);
|
|
});
|
|
|
|
it("lässt Anfragen ohne Origin zu (curl, alte Browser)", () => {
|
|
expect(herkunftStimmt(anfrage({ host: "proxledger.example" }))).toBe(true);
|
|
});
|
|
|
|
it("erkennt den Host hinter einem Reverse Proxy", () => {
|
|
expect(
|
|
herkunftStimmt(anfrage({ origin: "https://dashboard.kunde.de", "x-forwarded-host": "dashboard.kunde.de" })),
|
|
).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe("Öffentliche Adresse", () => {
|
|
// APP_URL kommt sonst aus der .env der Entwicklungsumgebung und verfälscht die Rückfallstufen.
|
|
beforeEach(() => {
|
|
vi.stubEnv("APP_URL", "");
|
|
});
|
|
afterEach(() => {
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
const anfrageMit = (kopfzeilen: Record<string, string>) =>
|
|
new Request("http://0a5963a591df:3000/api/auth/anmelden", { method: "POST", headers: kopfzeilen });
|
|
|
|
it("nimmt die eingetragene Adresse, nicht die interne des Containers", () => {
|
|
const adresse = eigeneAdresse(anfrageMit({}), "/hosts", "https://proxledger.example.de");
|
|
expect(adresse.toString()).toBe("https://proxledger.example.de/hosts");
|
|
});
|
|
|
|
it("fällt auf die Kopfzeilen des Reverse Proxy zurück", () => {
|
|
const anfrage = anfrageMit({ "x-forwarded-host": "dashboard.kunde.de", "x-forwarded-proto": "https" });
|
|
expect(eigeneAdresse(anfrage, "/").toString()).toBe("https://dashboard.kunde.de/");
|
|
});
|
|
|
|
it("nimmt notfalls die Anfrage selbst", () => {
|
|
expect(eigeneAdresse(anfrageMit({}), "/").host).toBe("0a5963a591df:3000");
|
|
});
|
|
|
|
it("übergeht einen unbrauchbaren Eintrag, statt zu scheitern", () => {
|
|
const anfrage = anfrageMit({ "x-forwarded-host": "dashboard.kunde.de" });
|
|
expect(eigeneAdresse(anfrage, "/", "keine-adresse").host).toBe("dashboard.kunde.de");
|
|
});
|
|
});
|