import { beforeEach, describe, expect, it } from "vitest"; import { POST as anmeldenPost } from "@/app/api/v1/auth/anmelden/route"; import { GET as ichGet } from "@/app/api/v1/ich/route"; import { passwortHashen } from "@/lib/passwort"; import { ersterAdminFallsLeer, sperrenZuruecksetzen } from "@/server/services/anmeldung"; import { datenbankLeeren, datenbankVerfuegbar, testPrisma } from "./helpers/db"; function anmeldung(benutzername: string, passwort: string): Request { return new Request("http://localhost/api/v1/auth/anmelden", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ benutzername, passwort }), }); } describe.runIf(datenbankVerfuegbar)("Anmeldung", () => { beforeEach(async () => { await datenbankLeeren(); sperrenZuruecksetzen(); await testPrisma.benutzer.create({ data: { benutzername: "florian", anzeigename: "Florian", rolle: "OPERATOR", passwortHash: await passwortHashen("richtiges-passwort"), }, }); }); it("setzt bei richtigem Passwort ein HttpOnly-Cookie und /ich kennt den Benutzer", async () => { const antwort = await anmeldenPost(anmeldung("Florian", "richtiges-passwort")); expect(antwort.status).toBe(200); const cookie = antwort.headers.get("set-cookie") ?? ""; expect(cookie).toMatch(/pd_sitzung=/); expect(cookie).toMatch(/HttpOnly/i); // Über http (ohne Proxy) darf das Cookie nicht Secure sein, sonst schickt der Browser es nie zurück. expect(cookie).not.toMatch(/Secure/i); const ich = await ichGet( new Request("http://localhost/api/v1/ich", { headers: { cookie: cookie.split(";")[0]! } }), ); expect(ich.status).toBe(200); expect(await ich.json()).toMatchObject({ benutzername: "florian", rolle: "OPERATOR" }); }); it("meldet falsches Passwort und unbekannten Benutzer gleich", async () => { const falsch = await anmeldenPost(anmeldung("florian", "falsch")); const unbekannt = await anmeldenPost(anmeldung("niemand", "falsch")); expect(falsch.status).toBe(401); expect(await falsch.json()).toEqual(await unbekannt.json()); }); it("weist deaktivierte Benutzer ab", async () => { await testPrisma.benutzer.update({ where: { benutzername: "florian" }, data: { aktiv: false } }); expect((await anmeldenPost(anmeldung("florian", "richtiges-passwort"))).status).toBe(401); }); it("sperrt nach zehn Fehlversuchen, auch für das richtige Passwort", async () => { for (let i = 0; i < 10; i += 1) await anmeldenPost(anmeldung("florian", "falsch")); const antwort = await anmeldenPost(anmeldung("florian", "richtiges-passwort")); expect(antwort.status).toBe(429); expect((await antwort.json()).fehler.hinweis).toMatch(/15 Minuten/); }); it("protokolliert Erfolg und Fehlschlag", async () => { await anmeldenPost(anmeldung("florian", "falsch")); await anmeldenPost(anmeldung("florian", "richtiges-passwort")); const aktionen = (await testPrisma.protokoll.findMany({ orderBy: { id: "asc" } })).map((e) => e.aktion); expect(aktionen).toEqual(["Anmeldung fehlgeschlagen", "Angemeldet"]); }); it("antwortet auf kaputtes JSON mit 400 statt 500", async () => { const antwort = await anmeldenPost( new Request("http://localhost/api/v1/auth/anmelden", { method: "POST", body: "{kaputt" }), ); expect(antwort.status).toBe(400); }); it("/ich ohne Sitzung ist 401", async () => { expect((await ichGet(new Request("http://localhost/api/v1/ich"))).status).toBe(401); }); }); describe.runIf(datenbankVerfuegbar)("Erster Administrator", () => { beforeEach(datenbankLeeren); it("wird nur angelegt, solange es keinen Benutzer gibt", async () => { process.env["ADMIN_BENUTZER"] = "Admin"; process.env["ADMIN_PASSWORT"] = "ein-langes-startpasswort"; await ersterAdminFallsLeer(); await ersterAdminFallsLeer(); const alle = await testPrisma.benutzer.findMany(); expect(alle).toHaveLength(1); expect(alle[0]).toMatchObject({ benutzername: "admin", rolle: "ADMIN" }); }); it("legt bei zu kurzem Passwort niemanden an", async () => { process.env["ADMIN_PASSWORT"] = "kurz"; await ersterAdminFallsLeer(); expect(await testPrisma.benutzer.count()).toBe(0); }); });