Skip to content

fix(portal): drive eSignet in the portal e2e sign-in - #35

Merged
jeremi merged 1 commit into
mainfrom
fix/portal-e2e-esignet-signin
Aug 20, 2026
Merged

fix(portal): drive eSignet in the portal e2e sign-in#35
jeremi merged 1 commit into
mainfrom
fix/portal-e2e-esignet-signin

Conversation

@jeremi

@jeremi jeremi commented Aug 20, 2026

Copy link
Copy Markdown
Member

Why

The portal e2e signIn helper clicked the sign-in button and asserted /services. That only holds under PORTAL_AUTH_PROVIDER=mock, where the login binds a persona session and continues straight to the catalog. With eSignet in front, the portal redirects off-origin to the eSignet login and the assertion fails on the redirect. Running the suite against a stack with eSignet enabled gave 2 passed / 6 failed, all six on the same first line of the helper.

Nothing was wrong with the portal. Only the harness assumed one provider.

What

portal/e2e/support/auth.ts is a new shared helper that discovers the provider from where the sign-in lands (/services = mock, /login = eSignet) and drives whichever answered. The seam is server-side configuration the browser cannot read, so landing path is the only signal available, and using it lets one suite run against a mock build and against a hosted deployment with no new flag.

Completing an eSignet login needs three things that are not obvious:

  • eSignet refuses an authorization request while it is still starting, and the refusal is terminal for that request. The helper reissues the whole request rather than waiting it out.
  • eSignet reports a refusal as HTTP 200 with an errors array. send-otp returns errors: [] even for an identity eSignet does not hold; the denial only arrives at authenticate, where the Relay lookup answers. Both responses are read, so a bad demo subject now fails in about two seconds naming the step instead of timing out blind after thirty.
  • Claim consent is asked once per subject per client and replayed afterwards, so the flow either stops on the consent screen or carries on by itself. The helper handles both.

The persona tests changed shape: the ?persona= handoff is mock-only by design, since under eSignet the subject comes from UserInfo. Those two tests skip under eSignet. I added a third that asserts the inverse security property, that a query parameter cannot sign anyone in as someone else under eSignet. Say the word if you would rather not have that one.

scripts/smoke-esignet-login.mjs drove the same login from its own copy, so it now imports the helper and is 21 lines. Node strips the helper's types on import; the Playwright import in the helper is type-only, so nothing is resolved at runtime beyond what the smoke already loaded.

The smoke's output policy is unchanged and deliberately so. It still prints exactly one fixed line, and the helper names only the step that failed, never the codes eSignet returned. test_browser_smoke_has_only_fixed_sanitized_output now asserts that against the helper, where the code moved, so the guard did not quietly become vacuous. That is why the helper does not surface the error code even though it would be useful in a test report.

Two supporting changes: the hosted per-test timeout goes to 120s, since a full OIDC round trip with a boot retry does not fit the 30s default, and just portal-live-e2e honours SOLMARA_PORTAL_PUBLIC_BASE_URL so it can point at a hosted portal instead of only localhost.

Verification

  • pnpm check: 423 files, 0 errors, 0 warnings
  • pnpm e2e (mock, built-in webServer): 8 passed, 1 skipped
  • pnpm e2e against a hosted stack with eSignet: 7 passed, 2 skipped
  • node scripts/smoke-esignet-login.mjs against the same stack: PASS
  • Same smoke with a subject eSignet does not hold: fails in ~2s, no eSignet detail in the output
  • uv run python -m unittest scripts/test_smoke_esignet.py: 5 tests, OK

CI does not run either the portal e2e suite or this smoke, so the two live runs above are the coverage.

Flagged, not fixed

The landing page shows "Continuing as " under eSignet too, promising a persona the login will ignore. Cosmetic, in portal/src/routes/+page.server.ts, and out of scope here.

The e2e helper clicked sign-in and asserted /services, which only holds
under the mock provider. Against a hosted stack eSignet answers instead,
so six of the eight tests failed on the redirect off-origin.

The helper now reports which provider answered and completes an eSignet
login when that is the one in front: it reissues the authorization while
eSignet is still starting, reads the refusals eSignet returns as HTTP 200
rather than trusting the status, and grants claim consent when asked. The
persona tests skip the handoff under eSignet, where the subject comes from
UserInfo, and assert the inverse there instead.

The hosted login smoke drove the same flow from its own copy, so it now
imports the helper. Its output stays a fixed line: the helper names the
step that failed and never the codes eSignet returned, and the
sanitization test follows the code onto the helper.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@jeremi
jeremi merged commit baa801a into main Aug 20, 2026
1 check passed
@jeremi
jeremi deleted the fix/portal-e2e-esignet-signin branch August 20, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant