S-08d — serves the self-service portal in the stack and adds the walking-skeleton Playwright
e2e: mock DigiD login → submit → confirmation, in a real browser. This closes the walking skeleton
end-to-end (portal → BFF → domain → Flowable → ACL → OpenZaak; openbaar reads the projection) and
finishes Iteration 1.
I could not validate this slice locally — no browser here, and it needs the live stack + a
container-run browser. The nginx image builds and starts cleanly, the app builds with the runtime
config, and the frontend unit lane is green; but the e2e + serving are CI-first and may well need a
round or two. Key design choices that de-risk it:
Same-origin serving: nginx serves the app and reverse-proxies /self-service + /openbaar to
the BFF. The api-client uses relative URLs → no CORS, no BFF change. nginx resolves the BFF at
request time so it starts before the BFF.
Issuer alignment (the long-standing gap): the e2e runs inside the compose network, so the
browser reaches Keycloak as keycloak:8080 — the same authority the BFF validates against. This is
what finally makes the real submit return 202 instead of 401.
Runtime config.json: one build; dev → localhost:8180, compose image → keycloak:8080.
No Playwright-image pinning: the e2e runs in node:24 and installs Chromium at runtime, copied
in via docker cp (no host pollution), matching the repo's verify-* pattern.
Most likely CI fiddles: Keycloak login selectors, the redirect/CORS dance, or browser-install time.
I'll iterate on the logs.
## What & why
**S-08d** — serves the self-service portal in the stack and adds the **walking-skeleton Playwright
e2e**: mock DigiD login → submit → confirmation, in a real browser. This closes the walking skeleton
end-to-end (portal → BFF → domain → Flowable → ACL → OpenZaak; openbaar reads the projection) and
finishes **Iteration 1**.
Closes #68
## Definition of Done
- [x] Conventional Commits (`refs #68`).
- [x] CI: new **`verify-e2e`** step in the `verify-stack` job; `frontend` lane still green (5 projects).
- [x] `docker compose up` serves the portal (`self-service`, health-checked, `:8140`).
- [x] Docs — `docs/frontend-decisions.md` + walking-skeleton demo note.
## Notes for reviewers — and a big honesty caveat
**I could not validate this slice locally** — no browser here, and it needs the live stack + a
container-run browser. The nginx image builds and starts cleanly, the app builds with the runtime
config, and the frontend unit lane is green; but **the e2e + serving are CI-first and may well need a
round or two.** Key design choices that de-risk it:
- **Same-origin serving:** nginx serves the app and reverse-proxies `/self-service` + `/openbaar` to
the BFF. The api-client uses relative URLs → **no CORS**, no BFF change. nginx resolves the BFF at
request time so it starts before the BFF.
- **Issuer alignment (the long-standing gap):** the e2e runs **inside the compose network**, so the
browser reaches Keycloak as `keycloak:8080` — the same authority the BFF validates against. This is
what finally makes the real submit return **202** instead of 401.
- **Runtime `config.json`:** one build; dev → `localhost:8180`, compose image → `keycloak:8080`.
- **No Playwright-image pinning:** the e2e runs in `node:24` and installs Chromium at runtime, copied
in via `docker cp` (no host pollution), matching the repo's `verify-*` pattern.
Most likely CI fiddles: Keycloak login selectors, the redirect/CORS dance, or browser-install time.
I'll iterate on the logs.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
The app loads /config.json at startup (main.ts) so the OIDC authority is set per
environment from one build; appConfig becomes a factory and derives redirectUrl +
secureApiOrigin from the app origin (same-origin as the BFF). A multi-stage
Dockerfile builds the app and serves it via nginx, reverse-proxying /self-service
+ /openbaar to the bff (relative URLs → no CORS); nginx resolves the BFF at request
time. The compose image bakes config.json with the keycloak:8080 authority so the
browser's token issuer matches the BFF (ADR-0010).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the self-service nginx service (build the app image, depends_on bff healthy +
keycloak started, health-checked, host port 8140). Add it to WAIT_SVCS and the CI
log dump.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tests/e2e Playwright spec drives DigiD login (jan-burger/test123) → submit →
confirmation against the compose-served portal. run-e2e-check.sh runs it inside the
compose network (node container, browser installed at runtime) so the token issuer
(keycloak:8080) matches the BFF authority (ADR-0010). Wired as verify-e2e (Makefile +
verify chain + a verify-stack CI step).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nginx listens on IPv4 only (listen 80), but 'localhost' inside the container resolves
to ::1 first, so the wget healthcheck got connection-refused and self-service never
went healthy — timing out the CI stack bring-up. Probe 127.0.0.1 instead.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The frontend lane ran `nx run-many -t lint test build`, so the ~5min
self-service production build shared nx's task pool with the Vitest test
worker. @angular/build:unit-test's Vitest worker has hard-coded 60s/90s
startup timeouts (not configurable); on a CPU-constrained CI runner the
concurrent build starved the worker and it failed with "Timeout waiting
for worker to respond" — flaky, since it passed on the prior commit.
Split the target into a light lint+test phase and a separate build phase
so tests get CPU and the worker starts well inside its window.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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) <noreply@anthropic.com>
After login the submit silently did nothing: the confirmation ("...is
ontvangen...") never rendered because the POST to the BFF went out with no
Authorization header, so the BFF rejected it and the no-error-handler
subscribe left the page unchanged.
Root cause: angular-auth-oidc-client's interceptor attaches the token when
`req.url.startsWith(secureRoute)`. The api-client calls the BFF with RELATIVE
URLs (same-origin via the nginx proxy), so `req.url` is `/self-service/...` —
but secureRoutes was configured as the app ORIGIN (`http://self-service`),
which a relative URL never starts with. No match → no token.
Configure secureRoutes with the relative `/self-service/` prefix instead. The
unit test mocked the api-client, so only the walking-skeleton e2e exercises the
real token attachment — now green.
Verified against a focused stack (keycloak + self-service + real BFF + stub
domain): the submit now carries the bearer token, the BFF forwards to the
domain, and the portal shows the confirmation with the returned reference.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Failing test: when postSelfServiceRegistrations errors, the page should show an
alert, not the confirmation, and keep the submit button available for retry.
Currently submit() has no error handler, so the rejection is swallowed and the
page silently stays put — exactly the failure mode that hid the missing-token
bug behind a 90s e2e timeout.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an error branch to submit(): on a failed BFF call, set a `failed` signal,
re-enable the button, and render a role="alert" message so the user knows the
submit did not go through and can retry — instead of the click silently doing
nothing.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The token-attachment bug (secureRoutes set to the app origin, which a relative
api-client URL never matches) was only caught by the full-stack e2e. Add a fast
unit guard: drive the REAL angular-auth-oidc-client interceptor and the REAL
api-client against the production route value, faking only the config source and
the token storage. Asserts the bearer token rides the relative /self-service/
call and is withheld from the anonymous /openbaar/ call.
Extract the value to a shared SECURE_API_ROUTES constant so the test binds to
exactly what the app configures. Verified the guard fails (Authorization null)
if the value regresses to an origin.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
not
merged commit 2a746736dc into main2026-07-13 13:20:58 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
What & why
S-08d — serves the self-service portal in the stack and adds the walking-skeleton Playwright
e2e: mock DigiD login → submit → confirmation, in a real browser. This closes the walking skeleton
end-to-end (portal → BFF → domain → Flowable → ACL → OpenZaak; openbaar reads the projection) and
finishes Iteration 1.
Closes #68
Definition of Done
refs #68).verify-e2estep in theverify-stackjob;frontendlane still green (5 projects).docker compose upserves the portal (self-service, health-checked,:8140).docs/frontend-decisions.md+ walking-skeleton demo note.Notes for reviewers — and a big honesty caveat
I could not validate this slice locally — no browser here, and it needs the live stack + a
container-run browser. The nginx image builds and starts cleanly, the app builds with the runtime
config, and the frontend unit lane is green; but the e2e + serving are CI-first and may well need a
round or two. Key design choices that de-risk it:
/self-service+/openbaartothe BFF. The api-client uses relative URLs → no CORS, no BFF change. nginx resolves the BFF at
request time so it starts before the BFF.
browser reaches Keycloak as
keycloak:8080— the same authority the BFF validates against. This iswhat finally makes the real submit return 202 instead of 401.
config.json: one build; dev →localhost:8180, compose image →keycloak:8080.node:24and installs Chromium at runtime, copiedin via
docker cp(no host pollution), matching the repo'sverify-*pattern.Most likely CI fiddles: Keycloak login selectors, the redirect/CORS dance, or browser-install time.
I'll iterate on the logs.
🤖 Generated with Claude Code
After login the submit silently did nothing: the confirmation ("...is ontvangen...") never rendered because the POST to the BFF went out with no Authorization header, so the BFF rejected it and the no-error-handler subscribe left the page unchanged. Root cause: angular-auth-oidc-client's interceptor attaches the token when `req.url.startsWith(secureRoute)`. The api-client calls the BFF with RELATIVE URLs (same-origin via the nginx proxy), so `req.url` is `/self-service/...` — but secureRoutes was configured as the app ORIGIN (`http://self-service`), which a relative URL never starts with. No match → no token. Configure secureRoutes with the relative `/self-service/` prefix instead. The unit test mocked the api-client, so only the walking-skeleton e2e exercises the real token attachment — now green. Verified against a focused stack (keycloak + self-service + real BFF + stub domain): the submit now carries the bearer token, the BFF forwards to the domain, and the portal shows the confirmation with the returned reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>