Accept a verification or recovery code pasted with whitespace around it
CI / full-gate (push) Failing after 16s
CI / full-gate (push) Failing after 16s
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# Full browser E2E — the real Playwright UI flow against the live stack: password + mocked-SSO
|
||||
# login, menu filtering by permission, users/groups/OAuth2-clients CRUD + permission granting, a plugin page, logout. A
|
||||
# login, emailed-code verification, menu filtering by permission, users/groups/OAuth2-clients CRUD + permission granting, a plugin page, logout. A
|
||||
# tiny same-origin gateway (proxy, e2e-tests/proxy.ts) fronts web + Kratos on one host so the browser's cookies
|
||||
# round-trip (ory/kratos/e2e-proxy.yml points Kratos at it); a mock OIDC provider backs the SSO test.
|
||||
# docker compose -f compose.yml -f e2e-tests/compose.full.yml run --user "$(id -u):$(id -g)" --build --rm e2e
|
||||
@@ -79,6 +79,17 @@ services:
|
||||
timeout: 4s
|
||||
retries: 15
|
||||
|
||||
# Catches the courier's mail (kratos.yml → smtp://mailpit:1025), so a test can read an emailed code.
|
||||
mailpit:
|
||||
image: axllent/mailpit:v1.33.0
|
||||
mem_limit: 128m
|
||||
memswap_limit: 128m
|
||||
healthcheck:
|
||||
test: ["CMD", "/mailpit", "readyz"]
|
||||
interval: 2s
|
||||
timeout: 4s
|
||||
retries: 15
|
||||
|
||||
# Same-origin gateway: Kratos-owned paths → kratos, everything else → web (e2e-tests/proxy.ts).
|
||||
proxy:
|
||||
image: node:24.22.2-alpine3.24
|
||||
@@ -103,6 +114,8 @@ services:
|
||||
dockerfile: e2e-tests/Dockerfile
|
||||
command: ["npx", "playwright", "test", "full-flow.spec.ts"]
|
||||
depends_on:
|
||||
mailpit:
|
||||
condition: service_healthy
|
||||
mock-oidc:
|
||||
condition: service_healthy
|
||||
proxy:
|
||||
@@ -110,5 +123,6 @@ services:
|
||||
environment:
|
||||
BASE_URL: http://proxy
|
||||
KRATOS_ADMIN_URL: http://kratos:4434
|
||||
MAILPIT_URL: http://mailpit:8025
|
||||
volumes:
|
||||
- ./e2e-tests/artifacts:/e2e-tests/artifacts
|
||||
|
||||
@@ -14,6 +14,8 @@ const ADMIN_EMAIL = "admin@plainpages.local"; // seeded by bootstrap, holds the
|
||||
const ADMIN_PASSWORD = "admin";
|
||||
const SSO_EMAIL = "sso-user@plainpages.local"; // minted by the mock OIDC provider on first SSO login
|
||||
const suffix = randomUUID().slice(0, 8); // unique per run so re-runs don't collide on names
|
||||
const KRATOS_ADMIN = process.env.KRATOS_ADMIN_URL ?? "http://kratos:4434";
|
||||
const MAILPIT = process.env.MAILPIT_URL ?? "http://mailpit:8025";
|
||||
|
||||
// Drive the themed password login form → Kratos → /auth/complete → dashboard, signed in.
|
||||
async function loginPassword(page: Page): Promise<void> {
|
||||
@@ -274,3 +276,36 @@ test("mocked SSO login: the provider button signs a user in via OIDC", async ({
|
||||
// A fresh SSO identity holds no permissions, so the gated Admin section stays hidden.
|
||||
await expect(page.locator('.sidebar a[href="/admin/users"]')).toHaveCount(0);
|
||||
});
|
||||
|
||||
// The newest one-time code Kratos' courier mailed to `to`, read from the mail catcher (compose.full.yml).
|
||||
async function mailedCode(to: string): Promise<string> {
|
||||
let code = "";
|
||||
await expect.poll(async () => {
|
||||
const search = (await (await fetch(`${MAILPIT}/api/v1/search?query=${encodeURIComponent(`to:"${to}"`)}`)).json()) as { messages: { ID: string }[] };
|
||||
const id = search.messages[0]?.ID;
|
||||
if (!id) return "";
|
||||
const message = (await (await fetch(`${MAILPIT}/api/v1/message/${id}`)).json()) as { Text: string };
|
||||
code = /\b(\d{6})\b/.exec(message.Text)?.[1] ?? "";
|
||||
return code;
|
||||
}, { timeout: 30_000 }).not.toBe("");
|
||||
return code;
|
||||
}
|
||||
|
||||
test("verification: a code pasted with whitespace around it is accepted", async ({ page }) => {
|
||||
test.setTimeout(90_000);
|
||||
const email = `verify-${suffix}@plainpages.local`;
|
||||
const created = await fetch(`${KRATOS_ADMIN}/admin/identities`, {
|
||||
body: JSON.stringify({ schema_id: "default", traits: { email } }),
|
||||
headers: { "content-type": "application/json" },
|
||||
method: "POST",
|
||||
});
|
||||
expect(created.status).toBe(201);
|
||||
|
||||
await page.goto("/verification");
|
||||
await page.fill('input[name="email"]', email);
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
await expect(page.locator('input[name="code"]')).toBeVisible();
|
||||
await page.fill('input[name="code"]', ` ${await mailedCode(email)}\t `); // as a paste from an email often arrives
|
||||
await page.getByRole("button", { name: "Continue" }).click();
|
||||
await expect(page.getByText("You successfully verified your email address.")).toBeVisible();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user