From 39923e0e68ad98007892c483d422288db1ea47c0 Mon Sep 17 00:00:00 2001 From: Niek Otten Date: Mon, 13 Jul 2026 13:37:31 +0200 Subject: [PATCH] fix(e2e): treat the http portal origin as secure so DigiD PKCE login works (refs #68) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The walking-skeleton e2e timed out waiting for the Keycloak login form (`#username`). Root cause: in the compose network the portal is served over plain HTTP on a non-localhost origin (http://self-service), which is not a secure context, so Web Crypto (`crypto.subtle`) is undefined. angular-auth- oidc-client needs SubtleCrypto to build the PKCE code challenge, so `authorize()` threw ("Cannot read properties of undefined (reading 'digest')") and the login redirect never fired. Production serves the portal over HTTPS, where this works. Instead of terminating TLS in the throwaway e2e stack, tell Chromium to treat the origin as secure via --unsafely-treat-insecure-origin-as-secure. The flag is only honoured by the full Chromium build (new headless), not Playwright's default headless-shell, so pin channel: 'chromium'. Verified against a minimal in-network stack (keycloak + self-service): login redirect now reaches the Keycloak form, and the full login → token exchange → authenticated portal renders with no console errors. Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/frontend-decisions.md | 8 ++++++++ tests/e2e/playwright.config.ts | 14 +++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/frontend-decisions.md b/docs/frontend-decisions.md index 8a54823..9c31125 100644 --- a/docs/frontend-decisions.md +++ b/docs/frontend-decisions.md @@ -91,5 +91,13 @@ with the submit form (S-08c, #67); any deviation from NL DS will be recorded her runtime, so there's no Playwright-image-version pinning to keep in sync. The spec is copied in (`docker cp`), not mounted, so it leaves nothing root-owned on the host. Wired as `verify-e2e` in the `verify-stack` CI job. +- **e2e treats the portal origin as secure.** In-network the portal is served over plain HTTP on a + non-localhost origin (`http://self-service`), which is **not a secure context**, so Web Crypto + (`crypto.subtle`) is unavailable. angular-auth-oidc-client needs it for the PKCE code challenge, so + `authorize()` throws and the login redirect never fires. Production runs behind HTTPS where this is + a non-issue; rather than terminate TLS in the throwaway stack, the Playwright config passes + `--unsafely-treat-insecure-origin-as-secure` (honoured only by the full `channel: 'chromium'` + build, not the default headless-shell). This emulates the production HTTPS secure context without + touching the app or its production config. - `tests/e2e` is a standalone Playwright project (its own `package.json`), not an Nx project — it's a live-stack check like the other `verify-*` runners, not part of the `frontend` unit lane. diff --git a/tests/e2e/playwright.config.ts b/tests/e2e/playwright.config.ts index 45a448f..6f6a37d 100644 --- a/tests/e2e/playwright.config.ts +++ b/tests/e2e/playwright.config.ts @@ -2,6 +2,8 @@ import { defineConfig, devices } from '@playwright/test'; // The e2e runs inside the compose network (infra/run-e2e-check.sh); baseURL defaults to the // self-service service. Keep timeouts generous — the first navigation triggers the DigiD flow. +const baseURL = process.env.SELF_SERVICE_URL ?? 'http://self-service'; + export default defineConfig({ testDir: '.', timeout: 90_000, @@ -9,8 +11,18 @@ export default defineConfig({ retries: 1, reporter: [['list']], use: { - baseURL: process.env.SELF_SERVICE_URL ?? 'http://self-service', + baseURL, trace: 'on-first-retry', + // The portal is served over plain HTTP on a non-localhost origin (http://self-service) inside the + // compose network, so it is NOT a secure context — and Web Crypto (`crypto.subtle`) is undefined + // there. angular-auth-oidc-client needs SubtleCrypto to build the PKCE code challenge, so + // `authorize()` throws and the login redirect never fires (the login form never appears). In + // production the portal runs behind HTTPS, where this works. Rather than terminate TLS in the + // throwaway e2e stack, tell Chromium to treat this origin as secure — which faithfully emulates + // the production HTTPS context. This flag is only honoured by the full Chromium build (new + // headless), not Playwright's default headless-shell, so pin `channel: 'chromium'`. + channel: 'chromium', + launchOptions: { args: [`--unsafely-treat-insecure-origin-as-secure=${baseURL}`] }, }, projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], });