import { navigateWorkspace } from "./workspace-controls"; import { expect, test, type Page } from "@playwright/test"; const LAN_WARNING = "Anyone who can reach this BenchLedger address can view inventory, change records, and change workspace security settings. Use LAN-open mode only on a trusted network. Enable a password before using guest Wi-Fi, port forwarding, internet exposure, or a public reverse proxy."; type AccessMode = "lan_open" | "password"; function inventoryItem(id: string, name: string) { return { id, name, kind: "tool", quantity: 1, availableQuantity: 1, unit: "each", tags: [], links: [], evidence: { state: "physically_counted" }, createdAt: "2026-09-01T10:00:00.000Z", updatedAt: "2026-09-01T10:00:00.000Z", version: 1 }; } async function mockWorkspaceAccess(page: Page) { let mode: AccessMode = "lan_open"; let version = 1; let workspaceCredential = "old-password-please"; let signedIn = false; let itemCount = 0; let lanSessionCount = 0; let loseNextResponse = false; let malformedNextResponse = false; const accessRequestKeys: string[] = []; const idempotentResponses = new Map(); let currentItems = [inventoryItem("tool-1", "Digital caliper")]; await page.route("**/api/v1/auth/access", async (route) => { if (route.request().method() === "GET") { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ mode, passwordConfigured: mode === "password", version }) }); return; } const body = route.request().postDataJSON() as { operation?: "enable" | "disable" | "change_password"; expectedVersion?: number; currentPassword?: string; newPassword?: string }; const idempotencyKey = route.request().headers()["idempotency-key"] ?? ""; accessRequestKeys.push(idempotencyKey); const requestBody = JSON.stringify(body); const replay = idempotentResponses.get(idempotencyKey); if (replay) { if (replay.body !== requestBody) { await route.fulfill({ status: 409, contentType: "application/json", body: JSON.stringify({ error: { code: "idempotency_conflict", message: "The request key was already used for a different change." } }) }); return; } signedIn = true; await route.fulfill({ status: 200, contentType: "application/json", body: replay.response }); return; } const expectedVersion = Number(route.request().headers()["if-match"]); if (expectedVersion !== version || body.expectedVersion !== version) { await route.fulfill({ status: 409, contentType: "application/json", body: JSON.stringify({ error: { code: "version_conflict", message: "Workspace settings changed elsewhere." } }) }); return; } if (body.operation !== "enable" && body.operation !== "disable" && body.operation !== "change_password") { await route.fulfill({ status: 400, contentType: "application/json", body: JSON.stringify({ error: { code: "invalid_operation", message: "A canonical workspace security operation is required." } }) }); return; } if ((body.operation === "disable" || body.operation === "change_password") && body.currentPassword !== workspaceCredential) { await route.fulfill({ status: 401, contentType: "application/json", body: JSON.stringify({ error: { code: "invalid_credentials", message: "The current password is not correct." } }) }); return; } if ((body.operation === "enable" || body.operation === "change_password") && !body.newPassword) { await route.fulfill({ status: 400, contentType: "application/json", body: JSON.stringify({ error: { code: "invalid_password", message: "A new password is required." } }) }); return; } if (body.newPassword) workspaceCredential = body.newPassword; mode = body.operation === "disable" ? "lan_open" : "password"; version += 1; signedIn = true; const responseBody = JSON.stringify({ mode, passwordConfigured: mode === "password", version, authenticated: true, actor: "admin", csrfToken: `csrf-${version}`, expiresAt: "2026-09-01T18:00:00.000Z" }); idempotentResponses.set(idempotencyKey, { body: requestBody, response: responseBody }); if (malformedNextResponse) { malformedNextResponse = false; signedIn = false; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ access: { mode, passwordConfigured: mode === "password", version }, session: { authenticated: true } }) }); return; } if (loseNextResponse) { loseNextResponse = false; signedIn = false; await route.abort("failed"); return; } await route.fulfill({ status: 200, contentType: "application/json", body: responseBody }); }); await page.route("**/api/v1/auth/lan-session", async (route) => { lanSessionCount += 1; signedIn = true; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ authenticated: true, actor: "admin", csrfToken: "csrf-lan", expiresAt: "2026-09-01T18:00:00.000Z" }) }); }); await page.route("**/api/v1/auth/login", async (route) => { const body = route.request().postDataJSON() as { password?: string }; if (body.password !== workspaceCredential) { await route.fulfill({ status: 401, contentType: "application/json", body: JSON.stringify({ error: { code: "invalid_credentials", message: "Email or password is invalid" } }) }); return; } signedIn = true; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ authenticated: true, actor: "admin", csrfToken: "csrf-login", expiresAt: "2026-09-01T18:00:00.000Z" }) }); }); await page.route("**/api/v1/auth/session", async (route) => { if (!signedIn) { await route.fulfill({ status: 401, contentType: "application/json", body: JSON.stringify({ error: { code: "unauthenticated", message: "Authentication is required" } }) }); return; } await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ authenticated: true, actor: "admin", source: "ui", scopes: ["read", "write"] }) }); }); await page.route("**/api/v1/auth/logout", async (route) => { signedIn = false; await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ authenticated: false }) }); }); await page.route("**/api/v1/health", async (route) => { await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ status: "ok", service: "benchledger", version: "test", demo: false, now: "2026-09-01T10:00:00.000Z" }) }); }); await page.route("**/api/v1/workspace", async (route) => { await route.fulfill({ status: signedIn ? 200 : 401, contentType: "application/json", body: JSON.stringify(signedIn ? { source: "api", fetchedAt: "2026-09-01T10:00:00.000Z", inventory: currentItems, projects: [], offers: [] } : { error: { code: "unauthenticated", message: "Authentication is required" } }) }); }); await page.route("**/api/v1/projects?*", async (route) => { if (route.request().method() !== "GET") return route.continue(); await route.fulfill({ status: signedIn ? 200 : 401, contentType: "application/json", body: JSON.stringify(signedIn ? { data: [], limit: 200, total: 0 } : { error: { code: "unauthenticated", message: "Authentication is required" } }) }); }); await page.route("**/api/v1/inventory?*", async (route) => { if (route.request().method() !== "GET") return route.continue(); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ data: currentItems, limit: 25, total: currentItems.length }) }); }); await page.route("**/api/v1/inventory/categories*", async (route) => { if (route.request().method() !== "GET") return route.continue(); await route.fulfill({ status: 200, contentType: "application/json", body: JSON.stringify({ data: [{ id: "category-tools", name: "Tools", sortOrder: 0, archived: false, createdAt: "2026-09-01T10:00:00.000Z", updatedAt: "2026-09-01T10:00:00.000Z", version: 1 }], limit: 200, total: 1 }) }); }); await page.route("**/api/v1/inventory", async (route) => { if (route.request().method() !== "POST") return route.continue(); if (!signedIn) { await route.fulfill({ status: 401, contentType: "application/json", body: JSON.stringify({ error: { code: "unauthenticated", message: "Authentication is required" } }) }); return; } const body = route.request().postDataJSON() as { name: string; quantity: number; unit: string }; itemCount += 1; const item = inventoryItem(`new-tool-${itemCount}`, body.name); currentItems = [item, ...currentItems]; await route.fulfill({ status: 201, contentType: "application/json", body: JSON.stringify({ data: item }) }); }); return { setPasswordRequired: () => { signedIn = false; }, loseNextAccessResponse: () => { loseNextResponse = true; }, malformedNextAccessResponse: () => { malformedNextResponse = true; }, state: () => ({ mode, version, signedIn, credential: workspaceCredential, itemCount, lanSessionCount, accessRequestKeys: [...accessRequestKeys] }) }; } test("completes the LAN-open to password and back access journey without exposing secrets", async ({ page }) => { const harness = await mockWorkspaceAccess(page); await page.goto("/"); await expect(page.getByRole("heading", { name: "Projects", exact: true })).toBeVisible(); await page.getByRole("button", { name: "Open workspace settings" }).click(); await expect(page.getByRole("heading", { name: "Workspace access" })).toBeVisible(); await expect(page.getByText("LAN open", { exact: true })).toBeVisible(); await expect(page.getByRole("alert").filter({ hasText: LAN_WARNING })).toContainText(LAN_WARNING); await expect(page.getByRole("button", { name: "Sign out" })).toHaveCount(0); await page.getByLabel("New workspace password", { exact: true }).fill("new-password-please"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("new-password-please"); await page.getByRole("button", { name: "Enable password" }).click(); await expect(page.getByText("Password protection is enabled.")).toBeVisible(); expect(harness.state()).toMatchObject({ mode: "password", version: 2, signedIn: true }); harness.setPasswordRequired(); await page.reload(); await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible(); await expect(page.getByLabel("Workspace password")).toBeVisible(); await page.getByLabel("Workspace password").fill("new-password-please"); await page.getByRole("button", { name: "Sign in" }).click(); await expect(page.getByRole("heading", { name: "Settings", exact: true })).toBeVisible(); await page.getByLabel("Current workspace password", { exact: true }).fill("new-password-please"); await page.getByLabel("New workspace password", { exact: true }).fill("replacement-password"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("replacement-password"); await page.getByRole("button", { name: "Change password" }).click(); await expect(page.getByText("Password changed. Your current session remains active.")).toBeVisible(); await expect(page.getByLabel("Current workspace password", { exact: true })).toHaveValue(""); await expect(page.getByLabel("New workspace password", { exact: true })).toHaveValue(""); await expect(page.getByLabel("Confirm new workspace password", { exact: true })).toHaveValue(""); await expect(page.getByLabel("Current workspace password to disable protection", { exact: true })).toHaveValue(""); await page.getByLabel("Current workspace password to disable protection").fill("replacement-password"); await page.getByRole("button", { name: "Disable password protection" }).click(); await expect(page.getByText("Password protection is disabled. BenchLedger is LAN open.")).toBeVisible(); await expect(page.getByText("LAN open", { exact: true })).toBeVisible(); await expect(page.getByRole("button", { name: "Sign out" })).toHaveCount(0); await navigateWorkspace(page, "Inventory"); await page.getByRole("button", { name: "Add item", exact: true }).click(); await page.getByLabel("What are you adding?").selectOption("tool"); await expect(page.getByLabel("Category (required)")).toHaveCount(0); await page.getByRole("button", { name: "Continue", exact: true }).click(); await page.getByLabel("Name", { exact: true }).fill("LAN-open write check"); await page.getByRole("button", { name: "Add item", exact: true }).last().click(); await expect(page.getByRole("dialog").getByRole("heading", { name: "LAN-open write check", exact: true })).toBeVisible(); expect(harness.state()).toMatchObject({ mode: "lan_open", itemCount: 1 }); await expect(page.locator("body")).not.toContainText("replacement-password"); }); test("keeps the access warning and password controls usable on mobile", async ({ page }) => { await mockWorkspaceAccess(page); await page.setViewportSize({ width: 390, height: 844 }); await page.goto("/"); await page.getByRole("button", { name: "Open workspace settings" }).click(); await expect(page.getByRole("heading", { name: "Workspace access" })).toBeVisible(); await expect(page.getByRole("alert").filter({ hasText: LAN_WARNING })).toHaveText(LAN_WARNING); await expect(page.getByLabel("New workspace password", { exact: true })).toBeVisible(); expect(await page.evaluate(() => document.documentElement.scrollWidth)).toBeLessThanOrEqual(390); await expect(page.getByRole("button", { name: "Enable password" })).toBeVisible(); }); test("retries a malformed access response immediately with the same request key", async ({ page }) => { const harness = await mockWorkspaceAccess(page); await page.goto("/"); await page.getByRole("button", { name: "Open workspace settings" }).click(); await page.getByLabel("New workspace password", { exact: true }).fill("malformed-response-password"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("malformed-response-password"); harness.malformedNextAccessResponse(); await page.getByRole("button", { name: "Enable password" }).click(); await expect(page.getByText("We could not confirm the change. It may have been saved; reload settings before trying again.")).toBeVisible(); await expect(page.getByText("A previous request was not confirmed.")).toBeVisible(); const firstKey = harness.state().accessRequestKeys.at(-1); await page.getByRole("button", { name: "Enable password" }).click(); await expect(page.getByText("Password protection is enabled.")).toBeVisible(); expect(harness.state().accessRequestKeys.at(-1)).toBe(firstKey); await expect(page.locator("body")).not.toContainText("malformed-response-password"); }); test("retries a lost access response immediately with the same request key", async ({ page }) => { const harness = await mockWorkspaceAccess(page); await page.goto("/"); await page.getByRole("button", { name: "Open workspace settings" }).click(); await page.getByLabel("New workspace password", { exact: true }).fill("immediate-retry-password"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("immediate-retry-password"); harness.loseNextAccessResponse(); await page.getByRole("button", { name: "Enable password" }).click(); await expect(page.getByText("We could not confirm the change. It may have been saved; reload settings before trying again.")).toBeVisible(); const firstKey = harness.state().accessRequestKeys.at(-1); await page.getByRole("button", { name: "Enable password" }).click(); await expect(page.getByText("Password protection is enabled.")).toBeVisible(); expect(harness.state().accessRequestKeys.at(-1)).toBe(firstKey); await expect(page.locator("body")).not.toContainText("immediate-retry-password"); }); test("replays a lost enable response after reauthentication without retaining the password", async ({ page }) => { const harness = await mockWorkspaceAccess(page); await page.goto("/"); await page.getByRole("button", { name: "Open workspace settings" }).click(); await page.getByLabel("New workspace password", { exact: true }).fill("lost-enable-password"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("lost-enable-password"); harness.loseNextAccessResponse(); await page.getByRole("button", { name: "Enable password" }).click(); await expect(page.getByText("We could not confirm the change. It may have been saved; reload settings before trying again.")).toBeVisible(); await expect(page.getByText("A previous request was not confirmed.")).toBeVisible(); const storedBeforeReload = await page.evaluate(() => sessionStorage.getItem("benchledger:workspace-access-retry:v1")); expect(storedBeforeReload).not.toContain("lost-enable-password"); expect(harness.state()).toMatchObject({ mode: "password", version: 2, signedIn: false }); await page.reload(); await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible(); await page.getByLabel("Workspace password").fill("lost-enable-password"); await page.getByRole("button", { name: "Sign in" }).click(); await page.getByRole("button", { name: "Open workspace settings" }).click(); await expect(page.getByRole("form", { name: "Retry enabling workspace password" })).toBeVisible(); await page.getByLabel("New workspace password", { exact: true }).fill("lost-enable-password"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("lost-enable-password"); await page.getByRole("button", { name: "Retry enable" }).click(); await expect(page.getByText("Password protection is enabled.")).toBeVisible(); expect(harness.state()).toMatchObject({ mode: "password", version: 2, signedIn: true }); await expect(page.locator("body")).not.toContainText("lost-enable-password"); }); test("replays a lost password change after logging in with the replacement", async ({ page }) => { const harness = await mockWorkspaceAccess(page); await page.goto("/"); await page.getByRole("button", { name: "Open workspace settings" }).click(); await page.getByLabel("New workspace password", { exact: true }).fill("first-change-password"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("first-change-password"); await page.getByRole("button", { name: "Enable password" }).click(); await expect(page.getByText("Password protection is enabled.")).toBeVisible(); await page.getByLabel("Current workspace password", { exact: true }).fill("first-change-password"); await page.getByLabel("New workspace password", { exact: true }).fill("second-change-password"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("second-change-password"); harness.loseNextAccessResponse(); await page.getByRole("button", { name: "Change password" }).click(); await expect(page.getByText("We could not confirm the change. It may have been saved; reload settings before trying again.")).toBeVisible(); expect(harness.state()).toMatchObject({ mode: "password", version: 3, signedIn: false }); await page.reload(); await expect(page.getByRole("heading", { name: "Sign in" })).toBeVisible(); await page.getByLabel("Workspace password").fill("second-change-password"); await page.getByRole("button", { name: "Sign in" }).click(); await page.getByRole("button", { name: "Open workspace settings" }).click(); await page.getByLabel("Current workspace password", { exact: true }).fill("first-change-password"); await page.getByLabel("New workspace password", { exact: true }).fill("second-change-password"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("second-change-password"); await page.getByRole("button", { name: "Change password" }).click(); await expect(page.getByText("Password changed. Your current session remains active.")).toBeVisible(); expect(harness.state()).toMatchObject({ mode: "password", version: 3, signedIn: true, credential: "second-change-password" }); await expect(page.locator("body")).not.toContainText("second-change-password"); }); test("replays a lost disable response after LAN bootstrap", async ({ page }) => { const harness = await mockWorkspaceAccess(page); await page.goto("/"); await page.getByRole("button", { name: "Open workspace settings" }).click(); await page.getByLabel("New workspace password", { exact: true }).fill("disable-password-please"); await page.getByLabel("Confirm new workspace password", { exact: true }).fill("disable-password-please"); await page.getByRole("button", { name: "Enable password" }).click(); await expect(page.getByText("Password protection is enabled.")).toBeVisible(); await page.getByLabel("Current workspace password to disable protection", { exact: true }).fill("disable-password-please"); harness.loseNextAccessResponse(); await page.getByRole("button", { name: "Disable password protection" }).click(); await expect(page.getByText("We could not confirm the change. It may have been saved; reload settings before trying again.")).toBeVisible(); expect(harness.state()).toMatchObject({ mode: "lan_open", version: 3, signedIn: false }); await page.reload(); await expect(page.getByRole("heading", { name: "Settings", exact: true })).toBeVisible(); expect(harness.state().lanSessionCount).toBeGreaterThanOrEqual(2); await expect(page.getByRole("form", { name: "Enable workspace password" })).toHaveCount(0); await expect(page.getByLabel("Current workspace password to retry disabling protection")).toBeVisible(); await page.getByLabel("Current workspace password to retry disabling protection").fill("disable-password-please"); await page.getByRole("button", { name: "Retry disable" }).click(); await expect(page.getByText("Password protection is disabled. BenchLedger is LAN open.")).toBeVisible(); expect(harness.state()).toMatchObject({ mode: "lan_open", version: 3, signedIn: true }); await expect(page.getByRole("button", { name: "Sign out" })).toHaveCount(0); await expect(page.locator("body")).not.toContainText("disable-password-please"); });