feat(demo-rp): a test website for the wallet's RP login paths - #140
Merged
Conversation
Both RP-login flows — REST SIOPv2 (`login`) and DIDComm (`loginDidcomm`) — could only be exercised against a real deployment, so in practice they were not exercised at all. One of them was broken for an unknown length of time as a direct result: the wallet sent a retired `authenticate` Type URI the control plane no longer routes, a site-initiated login failed after the user had already approved the consent prompt, and nothing surfaced it (#139). This is the missing half of that loop. A page that calls `window.vtaWallet` the way a relying party does, against a control plane of your choosing — the live one, or a `did-hosting-control` on localhost — and shows what came back. It asserts nothing. The point is to make the round-trip observable next to the wallet's own console, which is where the diagnosis actually happens; a harness that judged the result would just be a worse unit test with a network dependency. Kept out of `server.mjs` deliberately: that is a password-login target for the VTA's `vault/proxy-login` driver and shares nothing with this but a workspace. It also reports when no provider is present rather than leaving a dead button — content scripts are registered per granted origin and never reach a tab that is already open, so a first visit after granting needs a reload, and that is a confusing five minutes if the page says nothing. Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
9 tasks
stormer78
added a commit
that referenced
this pull request
Aug 30, 2026
…hand (#144) `proxyLogin` required the *page* to name a vault entry, so a per-site persona had to be bound in advance through the vault panel and a site without one dead-ended. The page's only route to an entry id was `vaultList()` — a second consent prompt that enumerates the user's vault to the site asking — and on a site with nothing bound it returned an empty array with nowhere to go from there. In practice that made the per-site persona a setup step people did not do, which left `login()` and its holder DID as the path of least resistance. The wallet now resolves the entry itself, from the origin the browser attested, and asks the human once when there is nothing bound yet. `entryId` becomes optional; a page that still supplies one keeps the old behaviour exactly. **One prompt, not two.** Choosing the identity a site sees IS the sign-in approval — it names the site and the identity, which is what the prompt already claims to do. A picker in front of a second approve screen would only train the operator to click through both (R7.2). **The picker bypasses the origin-trust short-circuit, deliberately.** The first-use branch calls `requestConsent`, not `gatedConsent`. A "remember this site" tick made against an earlier sign-in meant "log me in as the identity I already chose for you"; it cannot mean "choose a new identity for me and bind it silently", because that question has never been put to the operator. Same reasoning `requestTaskConsent` already carries: origin trust is not capability trust. Delete an entry and the picker returns, trusted origin or not. **The origin match is local and exact.** `vault/list` narrows by `targetOriginPrefix`, and a prefix is not an origin — `https://shop.example` is a prefix of `https://shop.example.evil.test`. Narrowing the set the VTA sends is a bandwidth decision; deciding which entry is this site's is a security decision, so it happens locally with `===`. Tested in both directions, plus scheme and port. **The prompt returns a DID string and nothing else.** Context and signing key are re-derived in the background from the agent's own `list-dids` and `derive-signing-key-id`, so a DID the agent does not host cannot be bound whatever the consent window sends back. A persona with more than one candidate signing key is refused rather than guessed — which key mints the id_token is a real choice with no default, and a wrong guess fails later and opaquely at the VTA — with the operator sent to the vault panel, which has the key picker. Entries are bound to `{kind:"webOrigin"}` *and* the RP's DID when the page named one. The vault panel's did-self-issued form binds a DID target only, so entries created there stay invisible to an origin lookup; that is left alone rather than migrated, since nothing is deployed and the panel is still the place to bind by hand. The ACL caveat is stated in the prompt, beside the DID, rather than after the failure: the relying party decides which identities it admits and nothing this wallet does can add one, so the operator wants the DID on screen while it is still copyable. A failed first sign-in repeats it and names the DID. The entry is kept on failure — it is correct, and deleting it would make the retry-after-enrolment path ask for an identity all over again. `login()` (REST SIOP) is untouched and still signs as the holder DID for every site. That path reads no vault entry at all, so aligning it is a behaviour change to a working flow and wants its own decision. Decision logic sits in `first-use-profile.ts`, free of `chrome` so it is testable; the demo harness gains a `proxyLogin({})` button, since the whole point of #140 was that a flow nothing exercises stays broken. Signed-off-by: Glenn Gore <glenn.g@affinidi.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Yes, we can test this ourselves — this is the harness
Both RP-login flows — REST SIOPv2 (
login) and DIDComm (loginDidcomm) — could only be exercised against a real deployment, so in practice they were not exercised at all.One of them was broken for an unknown length of time as a direct result: the wallet sent a retired
authenticateType URI the control plane no longer routes, so a site-initiated login failed after the user had already approved the consent prompt, and nothing surfaced it (#139).This serves a page that calls
window.vtaWalletthe way a relying party does, against a control plane of your choosing — the live one, or adid-hosting-controlon localhost — and shows what came back.CONTROL_DID=did:webvh:… MEDIATOR_DID=did:webvh:… \ npm run login-harness --workspace @openvtc/pnm-demo-rp # http://127.0.0.1:4041What it deliberately does not do
It asserts nothing. The point is to make the round-trip observable next to the wallet's own console, which is where the diagnosis actually happens. A harness that judged the result would be a worse unit test with a network dependency attached — the real assertions belong in
rp-login.didcomm.mjs, where they now are.It is kept out of
server.mjs: that is a password-login target for the VTA'svault/proxy-logindriver and shares nothing with this but a workspace.One usability detail worth having
It reports when no provider is present rather than leaving a dead button. Content scripts are registered per granted origin and never reach a tab that is already open, so a first visit after granting needs a reload — which is a confusing five minutes if the page just silently does nothing.
Why this matters beyond the two buttons
Almost everything merged in this run landed unvalidated, and the two defects that reached you — a vault list that would not load, and this login path — were both "no way to drive it locally" problems. This is the cheap end of fixing that: the RP runs locally, the provider API is already public, and the page is a hundred lines.
Next natural extension once the auth family is dispatchable as a Trust Task on the RP: the same page gets a third button that drives the challenge → authenticate flow over TSP.
Zero-dependency, no build step, not wired into CI. 617 workspace tests unaffected; lint clean.