fix(client): match challenges on intent, not just method - #133
Open
pucedoteth wants to merge 1 commit into
Open
Conversation
The transport selected the first challenge whose method token was
configured, ignoring the challenge intent:
if m, ok := t.methods[ch.Method]; ok {
The core spec's intent negotiation section has a server offer the same
method under two intents, and requires that clients "that do not
recognize an intent SHOULD treat the challenge as unsupported". Matching
on the method alone commits to whichever challenge is listed first.
For the built-in Tempo method the result is a failed request rather than
a wrong payment: CreateCredential rejects a non-charge intent, and
RoundTrip turns that into an error, so a server offering
[tempo/authorize, tempo/charge] gets no payment at all even though the
charge challenge is settleable. A third-party Method that does not
re-check the intent itself would instead build a credential for the
wrong intent.
Add an optional client.IntentMethod interface so a Method can declare
the intents it supports, and consult it during selection. Methods that
do not implement it are treated as accepting any intent, so existing
implementations are unaffected; the built-in Tempo client method
implements it and now skips non-charge challenges instead of failing on
them.
mpp-rs already selects on (method, intent) via PaymentProvider::supports,
and pympp does the same after tempoxyz/pympp#216.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.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.
The client transport selects the first challenge whose method token is configured, ignoring the challenge intent (
pkg/client/transport.go:72):The core spec's Intent Negotiation section has a server offer the same method under two intents:
and requires:
Matching on the method alone commits to whichever challenge is listed first.
Impact
Built-in Tempo method — the request fails outright.
CreateCredentialrejects a non-charge intent (pkg/tempo/client/method.go:94) andRoundTripturns that into an error, so a server offering[tempo/authorize, tempo/charge]gets no payment at all, even though thechargechallenge is settleable:Third-party methods — the wrong intent gets paid. The
client.Methodinterface does not require an intent check, so a Method that does not re-check it itself will build a credential for an intent it never meant to settle.Note the client never sends
Accept-Payment, so the server cannot filter on the client's behalf either — this check is the only thing standing between the client and an unrecognized intent.Fix
Add an optional
client.IntentMethodinterface so a Method can declare the intents it supports, and consult it during selection.Methods that do not implement it are treated as accepting any intent, so existing implementations are unaffected — I chose this over defaulting to
charge-only because Go interfaces cannot be extended without breaking implementors, and silently narrowing third-party methods would be a regression. The built-in Tempo client method implements it and now skips non-charge challenges instead of failing on them.Cross-SDK consistency
This brings mpp-go in line with its siblings: mpp-rs already selects on
(method, intent)viaPaymentProvider::supports(method, intent), and pympp does the same after tempoxyz/pympp#216 ("Challenge selection ignores the intent").Test plan
Three tests in
pkg/client/transport_test.go, all reproducing the spec's two-intent example:SkipsUnsupportedIntent— without the fix:built credentials for [authorize], want only [charge]UnsupportedIntentFirstStillPays— without the fix:request failed even though a payable challenge was offered: mpp: creating credential for method "example": unsupported challenge intent "authorize"MethodWithoutIntentsAcceptsAny— compatibility guard; passes with and without the fix by designVerified by reverting only the three production files and re-running: the first two fail, the third still passes.
make checkis clean and the full suite passes (13 packages).🤖 Written with Claude Code. Every claim above was verified against a local build and test run; the red/green proof is reproducible by reverting
pkg/client/client.go,pkg/client/transport.goandpkg/tempo/client/method.go.