diff --git a/tests/e2e/specs/14-guard.spec.ts b/tests/e2e/specs/14-guard.spec.ts index a370415..344d1af 100644 --- a/tests/e2e/specs/14-guard.spec.ts +++ b/tests/e2e/specs/14-guard.spec.ts @@ -56,8 +56,8 @@ for (const sub of GUARDED) { await page.goto(`https://${host}/`); // The guard resolves (Anubis PoW solves automatically in a real browser) - // and presents authentik's flow UI in place — the user never reaches the - // app without a session. + // and presents authentik's flow UI — either in place or after a redirect + // to the IdP host. Either way, the user never reaches the app content. const uid = page.locator('input[name="uidField"]'); await expect(uid).toBeVisible({ timeout: 90_000 }); @@ -66,9 +66,16 @@ for (const sub of GUARDED) { for (const marker of LEAK_MARKERS[host] ?? []) { expect(body, `${host} leaked app content to an unauthenticated user`).not.toContain(marker); } - // The response is the IdP's flow UI, not a passthrough: it must be served - // by authentik (the flow shell renders "Sign in" / uidField), never the app. - expect(new URL(page.url()).hostname).toBe(host); + // The guard is present: either the flow UI renders in place on the guarded + // vhost, or the browser was redirected to the IdP host (auth.) with the + // guarded host preserved as redirect_uri. Both prove a gate; neither leaks. + const landed = new URL(page.url()).hostname; + expect([host, "auth.franksdank.cloud"], `${host} must gate, not pass through`).toContain(landed); + if (landed === "auth.franksdank.cloud") { + const params = new URL(page.url()).searchParams; + const redirect = params.get("redirect_path") ?? params.get("redirect_uri") ?? ""; + expect(redirect, `${host} must return to the guarded host after auth`).toContain(sub); + } }); }