From b5630539661a520690f657d080c7b0cc921f1bd9 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 3 Sep 2026 17:00:34 -0700 Subject: [PATCH] test(e2e): accept IdP redirect as a valid gate in the direct-nav guard spec Live run against the fleet showed the outpost redirects direct navigation to auth.franksdank.cloud with the guarded host preserved as redirect_uri, rather than rendering the flow UI in place. The security property under test is that an unauthenticated visitor never sees app content; assert that shape (uidField visible + no leak markers + gate is either in-place or a redirect carrying the return path) instead of hostname preservation. --- tests/e2e/specs/14-guard.spec.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) 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); + } }); }