feat(benutzer): Benutzerverwaltung, Passwort ändern, Audit-Log-Ansicht

Admins verwalten Benutzer (anlegen, Rolle, sperren, Passwort setzen); jeder ändert sein Passwort; das Audit-Log ist filterbar einsehbar. Sperre und Passwortwechsel beenden ältere Sitzungen über Benutzer.sitzungenAb. Der letzte Admin und das eigene Konto sind geschützt.

Closes #015

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-12 16:49:47 +02:00
co-authored by Claude Opus 5.5
parent 7f6cf00a0e
commit 99357d59bd
21 changed files with 1292 additions and 22 deletions
+42
View File
@@ -0,0 +1,42 @@
import { expect, test } from "@playwright/test";
import { anmelden, E2E_PRAEFIX, keinQuerscrollen } from "./helfer";
test("Admin legt einen Benutzer an, stuft ihn hoch, sperrt ihn; das Audit-Log zeigt es", async ({ page }) => {
const name = `${E2E_PRAEFIX}-benutzer-${test.info().project.name}`;
await anmelden(page, "/einstellungen");
const vorhanden = (await (await page.request.get("/api/v1/benutzer")).json()) as Array<{
id: string;
benutzername: string;
}>;
const alt = vorhanden.find((b) => b.benutzername === name);
if (alt) {
// Vom letzten Lauf: zurück auf Operator und aktiv.
await page.request.patch(`/api/v1/benutzer/${alt.id}`, { data: { rolle: "OPERATOR", aktiv: true } });
await page.reload();
} else {
await page.locator("#b-name").fill(name);
await page.locator("#b-anzeige").fill("E2E Test");
await page.locator("#b-passwort").fill("e2e-passwort-lang");
await page.getByRole("button", { name: "Benutzer anlegen" }).click();
await expect(page.getByText(`${name} angelegt.`)).toBeVisible();
}
await keinQuerscrollen(page);
await page.getByLabel(`Rolle von ${name}`).selectOption("ADMIN");
await expect(page.getByText(`${name} ist jetzt Admin.`)).toBeVisible();
await page.getByRole("button", { name: `${name} sperren` }).click();
await expect(page.getByText(`${name} gesperrt – Sitzungen beendet.`)).toBeVisible();
await expect(page.getByRole("button", { name: `${name} entsperren` })).toBeVisible();
// Am Handy steckt die Navigation in der Schublade.
const menue = page.getByRole("button", { name: "Navigation öffnen" });
if (await menue.isVisible()) await menue.click();
await page.getByRole("link", { name: "Audit-Log" }).filter({ visible: true }).click();
await expect(page).toHaveURL(/\/protokoll/);
await page.getByLabel("Suche").fill(`benutzer:${name}`);
await page.getByRole("button", { name: "Filtern" }).click();
await expect(page).toHaveURL(new RegExp(`suche=benutzer%3A${name}`));
await expect(page.getByText("Benutzer gesperrt").first()).toBeVisible();
await keinQuerscrollen(page);
});