/* No public sign-up, and user administration. * Checks the interface and, separately, that the server refuses on its own — * a hidden button is not access control. */ import { BASE, OUT, api, check, launch, newPage, report, signIn } from "./lib.mjs"; const ADMIN = { user: process.env.INKNOTE_ADMIN || "marc", pass: process.env.INKNOTE_ADMIN_PASS || "geheim123", }; const NEW = { user: "kollege" + Math.floor(Math.random() * 1e6), pass: "startpass1", pass2: "zweitpass9", }; const openAdmin = async (p) => { await p.getByTitle("Account").click(); await p.waitForTimeout(500); await p.getByRole("button", { name: "Administration" }).click(); await p.waitForTimeout(1200); }; const browser = await launch(); try { /* ── 1. sign-up is gone ── */ const p = await newPage(browser); await p.goto(BASE, { waitUntil: "networkidle" }); await p.waitForTimeout(800); const labels = await p.$$eval("button, a", (els) => els.map((e) => e.innerText.trim()).filter(Boolean)); check(!labels.some((t) => /sign ?up|create account/i.test(t)), "no sign-up control on the login screen"); await p.screenshot({ path: `${OUT}/admin-login.png` }); const reg = await p.evaluate(async () => { const r = await fetch("/api/auth/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "sneaky" + Date.now(), password: "hunter22" }), }); return r.status; }); check(reg === 404, "POST /api/auth/register is gone", "status " + reg); /* ── 2. an admin can create ── */ await signIn(p, ADMIN.user, ADMIN.pass); await openAdmin(p); check((await p.locator("text=Administration").count()) > 0, "admin panel opens"); await p.getByRole("button", { name: "New account" }).click(); await p.waitForTimeout(400); await p.getByPlaceholder("Username").fill(NEW.user); await p.getByPlaceholder("Display name (optional)").fill("Kollege"); await p.getByPlaceholder("Initial password").fill(NEW.pass); await p.getByRole("button", { name: "Create account" }).click(); await p.waitForTimeout(1800); check((await p.locator(`[data-user="${NEW.user}"]`).count()) === 1, "created user appears in the list"); await p.screenshot({ path: `${OUT}/admin-panel.png` }); /* ── 3. the account works, and is not privileged ── */ const p2 = await newPage(browser); await signIn(p2, NEW.user, NEW.pass); check(true, "created user can sign in"); const seesAdmin = await p2.evaluate(async () => { document.querySelector('[title="Account"]')?.click(); await new Promise((r) => setTimeout(r, 600)); return [...document.querySelectorAll("button")].some((b) => /Administration/.test(b.innerText)); }); check(!seesAdmin, "ordinary user sees no Administration entry"); check((await api(p2, "GET", "/admin/users")).status === 403, "GET /admin/users refused for non-admin"); check( (await api(p2, "POST", "/admin/users", { username: "elev" + Date.now(), password: "hunter22" })).status === 403, "POST /admin/users refused for non-admin" ); /* ── 4. password reset ── */ await p.reload({ waitUntil: "networkidle" }); await p.waitForTimeout(1500); await openAdmin(p); const row = p.locator(`[data-user="${NEW.user}"]`); await row.getByRole("button", { name: "Reset password" }).click(); await p.waitForTimeout(400); await row.getByPlaceholder(`New password for ${NEW.user}`).fill(NEW.pass2); await row.getByRole("button", { name: "Set password", exact: true }).click(); await p.waitForTimeout(1500); const p3 = await newPage(browser); await p3.goto(BASE, { waitUntil: "networkidle" }); await p3.getByPlaceholder("Username").fill(NEW.user); await p3.getByPlaceholder("Password").fill(NEW.pass); await p3.getByRole("button", { name: "Sign In" }).click(); await p3.waitForTimeout(1500); check((await p3.locator("text=Invalid credentials").count()) > 0, "the old password stopped working"); await p3.getByPlaceholder("Password").fill(NEW.pass2); await p3.getByRole("button", { name: "Sign In" }).click(); await p3.waitForSelector("text=Dashboard", { timeout: 20000 }); check(true, "the new password works"); /* ── 5. deletion, and its guards ── */ await p.reload({ waitUntil: "networkidle" }); await p.waitForTimeout(1500); await openAdmin(p); const me = await api(p, "GET", "/auth/me"); check( (await api(p, "DELETE", `/admin/users/${me.body.id}`)).status === 400, "the server refuses an admin deleting themselves" ); check( (await p.locator(`[data-user="${ADMIN.user}"] button[title^="Delete"]`).count()) === 0, "no delete button on the admin's own row" ); await p.locator(`button[title="Delete ${NEW.user}"]`).click(); await p.waitForTimeout(500); check( await p.getByRole("button", { name: "Delete permanently" }).isDisabled(), "delete stays disabled until the name is typed back" ); await p.getByPlaceholder(NEW.user).fill(NEW.user); await p.waitForTimeout(300); await p.getByRole("button", { name: "Delete permanently" }).click(); await p.waitForTimeout(1800); // The success banner repeats the name, so match the row itself, not any text. check((await p.locator(`[data-user="${NEW.user}"]`).count()) === 0, "the user is gone from the list"); const p4 = await newPage(browser); await p4.goto(BASE, { waitUntil: "networkidle" }); await p4.getByPlaceholder("Username").fill(NEW.user); await p4.getByPlaceholder("Password").fill(NEW.pass2); await p4.getByRole("button", { name: "Sign In" }).click(); await p4.waitForTimeout(1500); check((await p4.locator("text=Invalid credentials").count()) > 0, "the deleted user cannot sign in"); } catch (e) { check(false, "suite ran to the end", e.message); } finally { const code = report(); await browser.close(); process.exit(code); }