Skip to content

feat: prompt step-up consent on the VERIFIED reason, mid-flow - #104

Merged
stormer78 merged 1 commit into
mainfrom
consent/stepup-verified-reason
Jul 29, 2026
Merged

feat: prompt step-up consent on the VERIFIED reason, mid-flow#104
stormer78 merged 1 commit into
mainfrom
consent/stepup-verified-reason

Conversation

@stormer78

Copy link
Copy Markdown
Contributor

What

The flagged follow-up from #103 ("Merge notes"): the step-up consent prompt fired before the RP start fetch, so the human decided on origin/rpDid alone — the signed reason, the very thing #103 made verifiable, was never shown. The spec's rule is "consumers MUST verify the proof BEFORE surfacing the reason"; we verified, but never surfaced.

This PR reorders the flow so the consent prompt sits between verification and signing, and renders the verified reason.

Flow, before → after

step before after
1 background: consent prompt (origin/rpDid only) background: forward straight to offscreen (attested origin threaded through)
2 offscreen: start fetch → verify → sign → finish offscreen: start fetch → verify (proof + enrolled-executor signer + issuer == page rpDid)
3 offscreen → background RUNTIME_STEP_UP_CONSENT: consent prompt showing the verified reason, attributed to the verified RP
4 on approval only: sign approve-response → finish
  • Core owns the order. New performStepUpVta (packages/core rp-login/step-up.ts) encodes start → verify → consent → sign → finish with an injected consent callback, so the ordering invariant is unit-testable. The offscreen contributes only the holder identity, the enrolled-executor set, and the bridge to a human.
  • Consent bridge. New RUNTIME_STEP_UP_CONSENT (offscreen → background), same shape as the existing RUNTIME_TASK_CONSENT leg. Background raises the same gatedConsent gate as before — the "remember this site" origin-trust short-circuit keeps its pre-existing semantics; the reorder changes when the prompt fires and what it shows, not who sees one.
  • Prompt window stays inside the challenge TTL — the prompt is mid-flow, between start and finish. A decline (or closing the window) sends nothing: no denied approve-response, the RP's challenge lapses on its own TTL, matching prior declined behaviour.

Rendering the reason safely

  • Plain text only: React text nodes, no markup path; background additionally strips control/bidi characters (\u0000–\u001F, \u200E/F, \u202A–E, \u2066–69) and caps at 500 chars with ellipsis (render-side cap as belt-and-braces).
  • Visually attributed: a bordered "Reason given by the relying party" card directly above the verified RP DID card, with an explicit "it is their claim — approve only if it matches what you were doing" note.
  • Step-up framing: title "Step-up approval request", holder card labelled "Approving as".
  • No reason in the signed payload → the card is absent and the prompt is the previous origin/rpDid-only text.

#103 invariants preserved

  • No prompt on missing/invalid document — refusal happens before the consent callback can run (tested).
  • Issuer == proven signer ∈ enrolled executors == page rpDid binding unchanged (moved verbatim into performStepUpVta; mismatch refuses without prompting — tested).
  • Response signing happens only after explicit user approval; every echoed field still comes from the verified payload only.

Tests

Extended packages/core/tests/rp-login.step-up.mjs with a mock-RP harness for performStepUpVta (5 new tests): prompt content comes from inside the signature even when the unsigned copy is tampered; a reason-less document prompts without a reason member; declined sends nothing (only /start ever hit); missing document refuses without prompting; enrolled-but-wrong issuer refuses without prompting.

npm run lint (tsc -b, all workspaces), npm test (181 pass), npm run build — all green.

Refs #103. Part of the step-up programme: affinidi/affinidi-webvh-service#147.

The flagged follow-up from #103: the step-up consent prompt fired before
the RP start fetch, so the human decided on origin/rpDid alone and the
signed reason — the thing #103 made verifiable — was never shown. The
spec's rule is 'consumers MUST verify the proof BEFORE surfacing the
reason'; we verified but never surfaced.

Reorder the flow so consent sits between verification and signing:

- core: new performStepUpVta owns the enforced order — start -> verify
  (signed approve-request; proof + enrolled-executor signer + issuer ==
  page rpDid) -> consent callback -> sign approve-response -> finish. A
  refused approve-request returns before the callback, so no prompt is
  ever raised for unverifiable content; a decline sends nothing (the
  RP's challenge lapses on its TTL). Unit-tested against a mock RP:
  prompt content comes from inside the signature (a tampered unsigned
  copy is never shown), declined sends nothing, missing document and
  issuer/rpDid mismatch both refuse without prompting.

- extension background: handleStepUpVta no longer pre-prompts; it
  forwards to the offscreen, threading the browser-attested origin. The
  new mid-flow RUNTIME_STEP_UP_CONSENT (offscreen -> background) raises
  the prompt through the same gatedConsent gate as before — the
  'remember this site' origin-trust short-circuit keeps its pre-#103
  semantics — with the verified reason length-capped (500 chars) and
  control/bidi-character-stripped before it reaches the popup.

- confirm popup: step-up framing ('Step-up approval request') plus a
  reason card that renders the RP's verified reason as plain text
  (React text nodes, no markup), visually attributed to the verified
  RP DID card with an explicit 'their claim' note. A document with no
  reason falls back to the previous origin/rpDid-only prompt.

Security invariants from #103 unchanged: no prompt on missing/invalid
document, issuer/rpDid binding intact, response signing only after
explicit user approval.

Part of the step-up programme (affinidi/affinidi-webvh-service#147).

Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
@stormer78
stormer78 merged commit 4ef360f into main Jul 29, 2026
3 checks passed
@stormer78
stormer78 deleted the consent/stepup-verified-reason branch July 29, 2026 08:40
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