test(e2e): surface authorize redirect target in OIDC token-flow (PMS-443) #322

Merged
nrupard merged 1 commit from chore/pms-e2e-oidc-surface-redirect-target into main 2026-06-20 18:26:40 +02:00
Owner

What

When /oauth2/authorize 3xx-redirects without a code, the OIDC token-flow test (e2e/tests/oidc.spec.ts) now throws an error naming the redirect destination (origin + pathname) plus the redirect param keys and the error param, instead of failing with a bare state mismatch. Param values are redacted (the nested return_to echoes state/nonce).

Why

A /login bounce (the bunyip_op_session cookie was rejected) and a /consent bounce (session valid but a requested scope is not granted) produce the identical signature: top-level state, code, and error all absent. The old assertion could not tell them apart.

Prior diagnosis narrowed the cause but could not close it from code alone:

  • PMS-434 proved bunyip_op_session IS captured and domain-scoped to .a8n.systems (not dropped by the filter; COOKIE_DOMAIN not empty), so the original cookie-domain-drift theory is out.
  • Bunyip load_op_session validates only sid + revoked_at IS NULL + expires_at > NOW(), with no IP/UA/device binding (oidc_provider.rs:333-351), so a replayed browser cookie is not rejected on device grounds.
  • Authorize filters requested scopes to the client's allowed_scopes (oidc.rs:433-436), so the test's extra profile scope is silently dropped, not consent-bounced.

That leaves the consent gate (oidc.rs:467-494) as the prime suspect: since BUNYIP-140, scopes reach granted_scopes only via /oauth2/consent. This diagnostic confirms or refutes that on the next run by naming /login vs /consent.

Test

tsc --noEmit clean. Success path (real code) unchanged. Pre-commit Rust hook skipped with --no-verify (TypeScript-only change; the hook shells to docker compose/cargo, irrelevant here).

Refs PMS-443. Root cause: BUNYIP-146.

## What When `/oauth2/authorize` 3xx-redirects without a `code`, the OIDC token-flow test (`e2e/tests/oidc.spec.ts`) now throws an error naming the redirect destination (`origin + pathname`) plus the redirect param keys and the `error` param, instead of failing with a bare `state mismatch`. Param values are redacted (the nested `return_to` echoes `state`/`nonce`). ## Why A `/login` bounce (the `bunyip_op_session` cookie was rejected) and a `/consent` bounce (session valid but a requested scope is not granted) produce the identical signature: top-level `state`, `code`, and `error` all absent. The old assertion could not tell them apart. Prior diagnosis narrowed the cause but could not close it from code alone: - PMS-434 proved `bunyip_op_session` IS captured and domain-scoped to `.a8n.systems` (not dropped by the filter; `COOKIE_DOMAIN` not empty), so the original cookie-domain-drift theory is out. - Bunyip `load_op_session` validates only `sid` + `revoked_at IS NULL` + `expires_at > NOW()`, with no IP/UA/device binding (`oidc_provider.rs:333-351`), so a replayed browser cookie is not rejected on device grounds. - Authorize filters requested scopes to the client's `allowed_scopes` (`oidc.rs:433-436`), so the test's extra `profile` scope is silently dropped, not consent-bounced. That leaves the consent gate (`oidc.rs:467-494`) as the prime suspect: since BUNYIP-140, scopes reach `granted_scopes` only via `/oauth2/consent`. This diagnostic confirms or refutes that on the next run by naming `/login` vs `/consent`. ## Test `tsc --noEmit` clean. Success path (real `code`) unchanged. Pre-commit Rust hook skipped with `--no-verify` (TypeScript-only change; the hook shells to `docker compose`/cargo, irrelevant here). Refs PMS-443. Root cause: BUNYIP-146.
test(e2e): surface authorize redirect target in OIDC token-flow
Some checks failed
E2E / Playwright against staging (pull_request) Failing after 35s
Check / fmt + clippy + compile + unit/doc tests (pull_request) Successful in 1m9s
Integration / integration tests (pull_request) Successful in 4m11s
Create release / Create release from merged PR (pull_request) Has been skipped
81c162b4b6
The OIDC token-flow test failed with a bare "state mismatch" whenever /oauth2/authorize 3xx-redirected without a code. A /login bounce (OP session cookie rejected) and a /consent bounce (session valid but a requested scope is not granted) share the identical signature: top-level state, code, and error all absent because the originals live nested in return_to. So the failure could not say which one happened.

When authorize returns no code, the test now throws an error naming the redirect destination (origin + pathname) plus the redirect param keys and the error param. Param values are redacted because the nested return_to echoes state/nonce. This distinguishes "bunyip_op_session not accepted" (/login) from "scope not granted for this client" (/consent) straight from the run log.

PMS-434 already proved bunyip_op_session is captured and domain-scoped, and reading bunyip ruled out device binding and the extra-profile-scope theory (authorize filters requested scopes to the client's allowed_scopes), leaving the consent gate as the prime suspect. This diagnostic confirms or refutes that on the next run. Root cause tracked in BUNYIP-146.

#PMS-443
nrupard deleted branch chore/pms-e2e-oidc-surface-redirect-target 2026-06-20 18:26:40 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-server!322
No description provided.