import { expect, test } from "./console-guard.ts"; // The from-scratch dev experience the banner advertises: `docker compose up`, open the printed // login URL, sign in as the seeded admin, land on the dashboard. A host-scoped Kratos CSRF cookie // cannot cross `localhost`↔`127.0.0.1`, so a cross-host login POST loses it and Kratos redirects to // its error sink; APP_URL canonicalises every off-host visitor onto one cookie host instead. // // The runner is on the host network against the plain `docker compose up` topology, so it sees // http://localhost:3000 and http://127.0.0.1:4433 exactly as a host browser does. The proxied // full-flow suite cannot catch this — it fronts web + Kratos on one origin. const ADMIN_EMAIL = "admin@plainpages.local"; // seeded by bootstrap const ADMIN_PASSWORD = "admin"; async function signIn(page: import("@playwright/test").Page): Promise { await page.fill('input[name="identifier"]', ADMIN_EMAIL); await page.fill('input[name="password"]', ADMIN_PASSWORD); await page.locator('.auth-form button[type="submit"]').click(); } test("seeded admin logs in from the advertised URL (http://localhost:3000) and reaches the dashboard", async ({ page }) => { test.setTimeout(90_000); // Open the app at the URL the first-run banner prints, then follow the landing's "Sign in" action. await page.goto("/"); await page.locator("#main-content").getByRole("link", { name: "Sign in" }).click(); await signIn(page); // Signed in on the app — NOT dumped on the Kratos /error "Page not found" page. await expect(page).not.toHaveURL(/\/error(\?|$)/); await expect(page.locator("h1"), 'must not land on the "Page not found" 404 view').not.toHaveText("Page not found"); await expect(page.locator(".profile-mail")).toHaveText(ADMIN_EMAIL); }); test("entering on the wrong host (http://127.0.0.1:3000) is canonicalised to APP_URL and login still works", async ({ page }) => { test.setTimeout(90_000); // The exact trigger from the bug report: a user types 127.0.0.1 instead of the advertised localhost. // The canonical-host redirect sends them to localhost before the flow starts, so the CSRF cookie // and the cross-origin Kratos POST share one host and login succeeds. await page.goto("http://127.0.0.1:3000/login"); await expect(page).toHaveURL(/^http:\/\/localhost:3000\//); // 308'd onto the canonical host await signIn(page); await expect(page).not.toHaveURL(/\/error(\?|$)/); await expect(page.locator(".profile-mail")).toHaveText(ADMIN_EMAIL); });