mppx 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/Challenge.d.ts +6 -0
  3. package/dist/Challenge.js +12 -0
  4. package/dist/Method.d.ts +2 -0
  5. package/dist/cli/cli.js +143 -131
  6. package/dist/cli/plugins/index.js +11 -11
  7. package/dist/cli/validate/payment.d.ts +12 -0
  8. package/dist/cli/validate/payment.js +137 -14
  9. package/dist/client/Mppx.d.ts +13 -3
  10. package/dist/client/Mppx.js +47 -22
  11. package/dist/client/internal/Fetch.d.ts +11 -0
  12. package/dist/client/internal/Fetch.js +46 -8
  13. package/dist/client/internal/ProofRealm.d.ts +21 -0
  14. package/dist/client/internal/ProofRealm.js +55 -0
  15. package/dist/client/internal/protocols/Mpp.js +4 -1
  16. package/dist/{attestation → extensions/identity}/Constants.d.ts +2 -2
  17. package/dist/{attestation → extensions/identity}/Constants.js +2 -2
  18. package/dist/extensions/identity/Types.d.ts +21 -0
  19. package/dist/extensions/identity/index.d.ts +7 -0
  20. package/dist/extensions/identity/index.js +6 -0
  21. package/dist/{attestation → extensions/identity}/internal/HttpMessageSignature.d.ts +3 -3
  22. package/dist/{attestation → extensions/identity}/internal/HttpMessageSignature.js +10 -10
  23. package/dist/extensions/identity/internal/SigningContext.d.ts +6 -0
  24. package/dist/{attestation → extensions/identity}/internal/SigningContext.js +3 -3
  25. package/dist/extensions/identity/internal/Verification.d.ts +9 -0
  26. package/dist/extensions/identity/internal/Verification.js +10 -0
  27. package/dist/{tap → extensions/identity/tap}/Constants.js +1 -1
  28. package/dist/extensions/identity/tap/client/index.d.ts +32 -0
  29. package/dist/{tap → extensions/identity/tap}/client/index.js +13 -9
  30. package/dist/extensions/identity/tap/server/index.d.ts +27 -0
  31. package/dist/{tap → extensions/identity/tap}/server/index.js +12 -4
  32. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Constants.js +1 -1
  33. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/client/index.d.ts +6 -5
  34. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/client/index.js +5 -5
  35. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/server/index.d.ts +6 -5
  36. package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/server/index.js +6 -4
  37. package/dist/identity/Client.d.ts +34 -0
  38. package/dist/identity/Client.js +133 -0
  39. package/dist/identity/Server.d.ts +59 -0
  40. package/dist/identity/Server.js +194 -0
  41. package/dist/identity/Types.d.ts +112 -0
  42. package/dist/identity/Types.js +2 -0
  43. package/dist/{attestation → identity}/index.d.ts +2 -2
  44. package/dist/{attestation → identity}/index.js +1 -2
  45. package/dist/identity/internal/SigningContext.d.ts +6 -0
  46. package/dist/identity/internal/SigningContext.js +11 -0
  47. package/dist/integrations/x402/Evm.d.ts +1 -0
  48. package/dist/internal/version.d.ts +2 -2
  49. package/dist/internal/version.js +1 -1
  50. package/dist/mcp/client/McpClient.d.ts +5 -0
  51. package/dist/mcp/client/McpClient.js +2 -0
  52. package/dist/proxy/Proxy.js +10 -5
  53. package/dist/proxy/Service.d.ts +42 -4
  54. package/dist/proxy/Service.js +21 -5
  55. package/dist/server/Mppx.d.ts +27 -8
  56. package/dist/server/Mppx.js +164 -54
  57. package/dist/server/internal/challengeMeta.d.ts +6 -0
  58. package/dist/server/internal/challengeMeta.js +17 -1
  59. package/dist/stripe/server/internal/html.gen.js +1 -1
  60. package/dist/tempo/client/Charge.d.ts +11 -0
  61. package/dist/tempo/client/Charge.js +30 -1
  62. package/dist/tempo/internal/charge.js +2 -1
  63. package/dist/tempo/internal/defaults.d.ts +9 -0
  64. package/dist/tempo/internal/defaults.js +9 -0
  65. package/dist/tempo/internal/fee-payer.d.ts +0 -2
  66. package/dist/tempo/internal/fee-payer.js +2 -6
  67. package/dist/tempo/server/Charge.js +12 -1
  68. package/dist/tempo/server/Subscription.js +0 -1
  69. package/dist/tempo/server/internal/html.gen.js +1 -1
  70. package/dist/tempo/session/client/Runtime.d.ts +2 -0
  71. package/dist/tempo/session/client/Session.js +1 -1
  72. package/dist/tempo/session/client/SessionManager.d.ts +6 -0
  73. package/dist/tempo/session/client/SessionManager.js +14 -7
  74. package/dist/tempo/session/client/Transports.d.ts +12 -3
  75. package/dist/tempo/session/client/Transports.js +37 -11
  76. package/dist/tempo/session/precompile/Chain.js +0 -3
  77. package/dist/tempo/subscription/Store.js +10 -9
  78. package/package.json +15 -11
  79. package/dist/attestation/Client.d.ts +0 -17
  80. package/dist/attestation/Client.js +0 -28
  81. package/dist/attestation/Server.d.ts +0 -4
  82. package/dist/attestation/Server.js +0 -8
  83. package/dist/attestation/Types.d.ts +0 -76
  84. package/dist/attestation/internal/SigningContext.d.ts +0 -4
  85. package/dist/tap/client/index.d.ts +0 -26
  86. package/dist/tap/server/index.d.ts +0 -20
  87. /package/dist/{attestation → extensions/identity}/Types.js +0 -0
  88. /package/dist/{tap → extensions/identity/tap}/Constants.d.ts +0 -0
  89. /package/dist/{tap → extensions/identity/tap}/Types.d.ts +0 -0
  90. /package/dist/{tap → extensions/identity/tap}/Types.js +0 -0
  91. /package/dist/{tap → extensions/identity/tap}/index.d.ts +0 -0
  92. /package/dist/{tap → extensions/identity/tap}/index.js +0 -0
  93. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Constants.d.ts +0 -0
  94. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Types.d.ts +0 -0
  95. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Types.js +0 -0
  96. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/index.d.ts +0 -0
  97. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/index.js +0 -0
  98. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/JwkThumbprint.d.ts +0 -0
  99. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/JwkThumbprint.js +0 -0
  100. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/SignatureAgent.d.ts +0 -0
  101. /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/SignatureAgent.js +0 -0
@@ -2,14 +2,18 @@ import * as Challenge from '../../Challenge.js';
2
2
  import * as Constants from '../../Constants.js';
3
3
  import { PaymentOutcomeUnknownError } from '../../Errors.js';
4
4
  import * as Expires from '../../Expires.js';
5
+ import * as IdentityClient from '../../identity/Client.js';
5
6
  import * as AcceptPayment from '../../internal/AcceptPayment.js';
6
7
  import * as Transport from '../Transport.js';
7
8
  import * as MethodChallenge from './MethodChallenge.js';
8
9
  import * as MethodResponse from './MethodResponse.js';
10
+ import * as ProofRealm from './ProofRealm.js';
9
11
  // We tag wrappers with a global symbol so we can recognize wrappers created by mppx,
10
12
  // even across multiple module instances/bundles. This lets restore() avoid clobbering
11
13
  // an unrelated fetch installed by user code or another library.
12
14
  const MPPX_FETCH_WRAPPER = Symbol.for('mppx.fetch.wrapper');
15
+ /** Identity runner of an mppx fetch wrapper, inherited when that wrapper is wrapped again. */
16
+ const MPPX_FETCH_IDENTITY = Symbol.for('mppx.fetch.identity');
13
17
  const defaultMaxPaymentRetries = 3;
14
18
  const maxErrorBodyBytes = 64 * 1024;
15
19
  const errorBodyTimeoutMs = 2_000;
@@ -101,19 +105,25 @@ async function settleAttempt(prepared, outcome) {
101
105
  *
102
106
  */
103
107
  export function from(config) {
104
- const { acceptPayment, acceptPaymentPolicy = 'always', fetch = globalThis.fetch, maxPaymentRetries = defaultMaxPaymentRetries, methods, onChallenge, orderChallenges, transport = Transport.http(), } = config;
108
+ const { acceptPayment, acceptPaymentPolicy = 'always', fetch = globalThis.fetch, maxPaymentRetries = defaultMaxPaymentRetries, methods, onChallenge, orderChallenges, transport = Transport.http(), trustedTempoProofRealms, } = config;
109
+ // Rewrapping an mppx fetch keeps its identity provers unless new ones are configured.
110
+ // Only an explicitly passed mppx fetch hands down its provers; the global fetch never does.
111
+ const identity = config.identity ?? (config.fetch && identityOf(config.fetch));
105
112
  const events = config.eventDispatcher ?? createEventDispatcher();
106
113
  const resolvedAcceptPayment = acceptPayment ?? AcceptPayment.resolve(methods);
107
114
  // Always operate on the true underlying fetch to avoid wrapper-on-wrapper stacking,
108
115
  // which can duplicate retries and make restore semantics fragile.
109
116
  const baseFetch = unwrapFetch(fetch);
117
+ const send = async (input, init, context) => identity
118
+ ? (await identity.send(baseFetch, new Request(input, init), context)).response
119
+ : baseFetch(input, init);
110
120
  const request = async (input, init, canRefetch) => {
111
121
  const callerHeaders = getCallerHeaders(input, init?.headers);
112
122
  const hasExplicitAcceptPayment = callerHeaders.has(Constants.Headers.acceptPayment);
113
123
  const paymentPreferences = resolvePaymentPreferences(callerHeaders, resolvedAcceptPayment);
114
124
  const capturedBody = captureRequestBody(input, init, callerHeaders);
115
125
  const initialRequest = prepareInitialRequest(input, init, callerHeaders, paymentPreferences.header, hasExplicitAcceptPayment, acceptPaymentPolicy);
116
- let response = await baseFetch(cloneRequestInput(initialRequest.input), initialRequest.init);
126
+ let response = await send(cloneRequestInput(initialRequest.input), initialRequest.init);
117
127
  const transportRequest = withCapturedBody(initialRequest.init, await capturedBody);
118
128
  if (!(await transport.isPaymentRequired(response, transportRequest)))
119
129
  return response;
@@ -150,7 +160,9 @@ export function from(config) {
150
160
  mi = selected.method;
151
161
  if (challenge.expires)
152
162
  Expires.assert(challenge.expires, challenge.id);
163
+ await identity?.challenge(response, { challenge: selectedChallenge }, resolveRequestUrl(initialRequest.input).href);
153
164
  const paymentInput = resolvePaymentRetryInput(response, initialRequest.input, initialRequest.input);
165
+ ProofRealm.assert(selectedChallenge, () => resolveRequestUrl(paymentInput), trustedTempoProofRealms);
154
166
  const previousCredential = preparedCredential;
155
167
  const key = Challenge.serialize(selectedChallenge);
156
168
  const sameAttempt = previousCredential?.method === selected.method && previousCredential?.key === key;
@@ -179,7 +191,12 @@ export function from(config) {
179
191
  prepare: () => MethodChallenge.handle(selected.method, {
180
192
  challenge: selectedChallenge,
181
193
  context: credentialContext,
182
- fetch: baseFetch,
194
+ // Management requests (e.g. session top-ups) carry the same identity proofs.
195
+ fetch: identity
196
+ ? IdentityClient.wrapFetch(baseFetch, identity, {
197
+ challenge: selectedChallenge,
198
+ })
199
+ : baseFetch,
183
200
  input: paymentInput,
184
201
  signal: resolveRequestSignal(input, init),
185
202
  }),
@@ -232,14 +249,14 @@ export function from(config) {
232
249
  transactionHash: MethodResponse.getTransactionHash(selected.method, credential),
233
250
  });
234
251
  try {
235
- response = await baseFetch(paymentInput, {
252
+ response = await send(paymentInput, {
236
253
  ...transport.setCredential({
237
254
  ...fetchInit,
238
255
  headers: initialRequest.headers,
239
256
  }, credential, { challenge: selectedChallenge }),
240
257
  // Runtimes do not strip custom payment headers on cross-origin redirects.
241
258
  redirect: 'manual',
242
- });
259
+ }, { challenge: selectedChallenge });
243
260
  }
244
261
  catch (cause) {
245
262
  outcomeUnknown = unknownOutcome(cause);
@@ -347,6 +364,8 @@ export function from(config) {
347
364
  };
348
365
  const wrappedFetch = (input, init) => request(input, init, true);
349
366
  wrappedFetch[MPPX_FETCH_WRAPPER] = baseFetch;
367
+ if (identity)
368
+ wrappedFetch[MPPX_FETCH_IDENTITY] = identity;
350
369
  return wrappedFetch;
351
370
  }
352
371
  /**
@@ -380,7 +399,8 @@ export function polyfill(config) {
380
399
  globalThis.fetch = from({
381
400
  ...config,
382
401
  acceptPaymentPolicy: config.acceptPaymentPolicy ?? (isBrowser() ? 'same-origin' : 'always'),
383
- fetch: globalThis.fetch,
402
+ // Unwrapped so a previous polyfill's identity provers are not inherited.
403
+ fetch: unwrapFetch(globalThis.fetch),
384
404
  });
385
405
  }
386
406
  /**
@@ -586,8 +606,22 @@ function snapshotMethod(method) {
586
606
  return freezeSnapshot(Object.assign({}, method));
587
607
  }
588
608
  function snapshotResponse(response) {
589
- if (response instanceof Response)
590
- return response.clone();
609
+ if (response instanceof Response) {
610
+ try {
611
+ return response.clone();
612
+ }
613
+ catch {
614
+ // Consumed or locked bodies cannot be cloned; retain isolated status, headers and URL.
615
+ const snapshot = new Response(null, {
616
+ headers: response.headers,
617
+ status: response.status,
618
+ statusText: response.statusText,
619
+ });
620
+ for (const key of ['redirected', 'type', 'url'])
621
+ Object.defineProperty(snapshot, key, { value: response[key] });
622
+ return snapshot;
623
+ }
624
+ }
591
625
  return snapshotValue(response);
592
626
  }
593
627
  function snapshotValue(value) {
@@ -667,6 +701,10 @@ function getCallerHeaders(input, headers) {
667
701
  return new Headers(input instanceof Request ? input.headers : undefined);
668
702
  }
669
703
  /** @internal */
704
+ /** Returns the identity runner of an mppx fetch wrapper, if it has one. @internal */
705
+ export function identityOf(fetch) {
706
+ return fetch[MPPX_FETCH_IDENTITY];
707
+ }
670
708
  /** @internal */
671
709
  export function unwrapFetch(fetch) {
672
710
  let current = fetch;
@@ -0,0 +1,21 @@
1
+ import type * as Challenge from '../../Challenge.js';
2
+ /**
3
+ * Pins that let a server origin request zero-amount Tempo proofs for a realm other than its
4
+ * hostname, keyed by origin (`scheme://host[:port]`), such as
5
+ * `{ 'https://api.example.com': 'MPP Payment' }`.
6
+ */
7
+ export type TrustedRealms = Readonly<Record<string, string>>;
8
+ /**
9
+ * Refuses a zero-amount Tempo proof whose realm the HTTP server that sent the Challenge does not
10
+ * own. A proof authenticates the payer at the Challenge realm, so a foreign origin could otherwise
11
+ * relay another server's Challenge and replay the signed proof there. The server owns the realm
12
+ * when it is the request hostname, or when `trustedRealms` pins it to the request origin. Pass
13
+ * `url` as a function to resolve it only for proof Challenges.
14
+ */
15
+ export declare function assert(challenge: Challenge.Challenge, url: URL | (() => URL), trustedRealms: TrustedRealms | undefined): void;
16
+ /**
17
+ * Refuses a zero-amount Tempo proof on a transport without an origin, such as MCP, unless
18
+ * `trustedRealms` lists its realm.
19
+ */
20
+ export declare function assertWithoutOrigin(challenge: Challenge.Challenge, trustedRealms: readonly string[] | undefined): void;
21
+ //# sourceMappingURL=ProofRealm.d.ts.map
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Refuses a zero-amount Tempo proof whose realm the HTTP server that sent the Challenge does not
3
+ * own. A proof authenticates the payer at the Challenge realm, so a foreign origin could otherwise
4
+ * relay another server's Challenge and replay the signed proof there. The server owns the realm
5
+ * when it is the request hostname, or when `trustedRealms` pins it to the request origin. Pass
6
+ * `url` as a function to resolve it only for proof Challenges.
7
+ */
8
+ export function assert(challenge, url, trustedRealms) {
9
+ if (!isProof(challenge))
10
+ return;
11
+ url = typeof url === 'function' ? url() : url;
12
+ const { realm } = challenge;
13
+ if (realm.toLowerCase() === url.hostname.toLowerCase())
14
+ return;
15
+ const origin = url.origin.toLowerCase();
16
+ for (const [trustedOrigin, trustedRealm] of Object.entries(trustedRealms ?? {}))
17
+ if (trustedRealm === realm && normalizeOrigin(trustedOrigin) === origin)
18
+ return;
19
+ throw new Error(`Refusing to sign Tempo proof for realm ${JSON.stringify(realm)} from ${url.origin}. Pin the realm to this origin with \`trustedTempoProofRealms\` if this server is expected to issue it.`);
20
+ }
21
+ /**
22
+ * Refuses a zero-amount Tempo proof on a transport without an origin, such as MCP, unless
23
+ * `trustedRealms` lists its realm.
24
+ */
25
+ export function assertWithoutOrigin(challenge, trustedRealms) {
26
+ if (!isProof(challenge))
27
+ return;
28
+ if (trustedRealms?.includes(challenge.realm))
29
+ return;
30
+ throw new Error(`Refusing to sign Tempo proof for untrusted realm ${JSON.stringify(challenge.realm)}. Add it to \`trustedTempoProofRealms\` if this server is expected to issue it.`);
31
+ }
32
+ /**
33
+ * Whether `challenge` is a zero-amount `tempo/charge` Challenge, paid with a signed proof. Uses the
34
+ * same `BigInt` conversion as the Tempo charge client, so every encoding it signs a proof for
35
+ * (such as `"0x0"` or `" 0 "`) is checked.
36
+ */
37
+ function isProof(challenge) {
38
+ if (challenge.method !== 'tempo' || challenge.intent !== 'charge')
39
+ return false;
40
+ try {
41
+ return BigInt(challenge.request.amount) === 0n;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ function normalizeOrigin(origin) {
48
+ try {
49
+ return new URL(origin).origin.toLowerCase();
50
+ }
51
+ catch {
52
+ return origin.toLowerCase();
53
+ }
54
+ }
55
+ //# sourceMappingURL=ProofRealm.js.map
@@ -5,8 +5,11 @@ import { paymentRequiredStatus, setCredentialHeader } from './Shared.js';
5
5
  export function mpp() {
6
6
  return {
7
7
  getChallenges(response) {
8
+ const header = response.headers.get(Constants.Headers.wwwAuthenticate);
9
+ // A 402 without a Payment challenge (such as `WWW-Authenticate: X402`) is left to other protocols.
8
10
  if (response.status !== paymentRequiredStatus ||
9
- !response.headers.has(Constants.Headers.wwwAuthenticate))
11
+ !header ||
12
+ !Challenge.hasPaymentScheme(header))
10
13
  return [];
11
14
  return Challenge.fromResponseList(response);
12
15
  },
@@ -1,11 +1,11 @@
1
- /** RFC 9421 signature algorithms supported by request attestation. */
1
+ /** RFC 9421 signature algorithms supported by HTTP message signatures. */
2
2
  export declare const Algorithms: {
3
3
  /** EdDSA using the Ed25519 curve. */
4
4
  readonly ed25519: 'ed25519';
5
5
  /** RSASSA-PSS using SHA-512 and a 64-byte salt. */
6
6
  readonly rsaPssSha512: 'rsa-pss-sha512';
7
7
  };
8
- /** HTTP headers defined by RFC 9421 for request-attestation signatures. */
8
+ /** HTTP headers defined by RFC 9421 for identity signatures. */
9
9
  export declare const Headers: {
10
10
  /** Carries the RFC 9421 signature bytes. */
11
11
  readonly signature: 'Signature';
@@ -1,11 +1,11 @@
1
- /** RFC 9421 signature algorithms supported by request attestation. */
1
+ /** RFC 9421 signature algorithms supported by HTTP message signatures. */
2
2
  export const Algorithms = {
3
3
  /** EdDSA using the Ed25519 curve. */
4
4
  ed25519: 'ed25519',
5
5
  /** RSASSA-PSS using SHA-512 and a 64-byte salt. */
6
6
  rsaPssSha512: 'rsa-pss-sha512',
7
7
  };
8
- /** HTTP headers defined by RFC 9421 for request-attestation signatures. */
8
+ /** HTTP headers defined by RFC 9421 for identity signatures. */
9
9
  export const Headers = {
10
10
  /** Carries the RFC 9421 signature bytes. */
11
11
  signature: 'Signature',
@@ -0,0 +1,21 @@
1
+ import type { MaybePromise } from '../../internal/types.js';
2
+ import type { Algorithms } from './Constants.js';
3
+ /** RFC 9421 algorithms implemented by the HTTP message signature signer and verifier. */
4
+ export type SignatureAlgorithm = (typeof Algorithms)[keyof typeof Algorithms];
5
+ /** Resolves a trusted public key for a parsed request signature. */
6
+ export type KeyResolver = (parameters: {
7
+ /** RFC 9421 algorithm declared by the signature. */
8
+ algorithm: SignatureAlgorithm;
9
+ /** Key identifier declared by the signature. */
10
+ keyId: string;
11
+ /** Request carrying the signature. */
12
+ request: Request;
13
+ }) => MaybePromise<CryptoKey | undefined>;
14
+ /** Values shared by every signature on one HTTP attempt. */
15
+ export type SigningContext = {
16
+ /** Unix timestamp, in seconds, at which the attempt was created. */
17
+ readonly created: number;
18
+ /** Base64url-encoded random value identifying the attempt. */
19
+ readonly nonce: string;
20
+ };
21
+ //# sourceMappingURL=Types.d.ts.map
@@ -0,0 +1,7 @@
1
+ /** Client and server adapters for the TAP agent-recognition signature profile. */
2
+ export * as Tap from './tap/index.js';
3
+ /** Client and server adapters for the Web Bot Auth HTTPS-directory profile. */
4
+ export * as WebBotAuth from './web-bot-auth/index.js';
5
+ export * from './Constants.js';
6
+ export type * from './Types.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,6 @@
1
+ /** Client and server adapters for the TAP agent-recognition signature profile. */
2
+ export * as Tap from './tap/index.js';
3
+ /** Client and server adapters for the Web Bot Auth HTTPS-directory profile. */
4
+ export * as WebBotAuth from './web-bot-auth/index.js';
5
+ export * from './Constants.js';
6
+ //# sourceMappingURL=index.js.map
@@ -1,8 +1,8 @@
1
1
  import type { BareItem } from 'structured-headers';
2
- import type { MaybePromise } from '../../internal/types.js';
3
- import * as Store from '../../Store.js';
2
+ import type { MaybePromise } from '../../../internal/types.js';
3
+ import * as Store from '../../../Store.js';
4
4
  import type { KeyResolver, SignatureAlgorithm, SigningContext } from '../Types.js';
5
- /** RFC 9421 constants used by request-attestation signature profiles. */
5
+ /** RFC 9421 constants used by identity signature profiles. */
6
6
  export declare const Constants: {
7
7
  /** Derived request components supported by this narrow request profile. */
8
8
  readonly components: {
@@ -1,8 +1,8 @@
1
1
  import { isInnerList, parseDictionary, serializeItem, serializeDictionary, serializeInnerList, serializeParameters, } from 'structured-headers';
2
- import * as Store from '../../Store.js';
3
- import { Algorithms, Headers as AttestationHeaders } from '../Constants.js';
2
+ import * as Store from '../../../Store.js';
3
+ import { Algorithms, Headers as SignatureHeaders } from '../Constants.js';
4
4
  import * as SigningContextInternal from './SigningContext.js';
5
- /** RFC 9421 constants used by request-attestation signature profiles. */
5
+ /** RFC 9421 constants used by identity signature profiles. */
6
6
  export const Constants = {
7
7
  /** Derived request components supported by this narrow request profile. */
8
8
  components: {
@@ -55,16 +55,16 @@ export async function sign(request, options) {
55
55
  };
56
56
  const signature = await crypto.subtle.sign(webCryptoAlgorithm(algorithm), options.key, toArrayBuffer(encode(signatureBase(request, input))));
57
57
  const headers = new Headers(request.headers);
58
- const inputs = parseHeaderDictionary(headers, AttestationHeaders.signatureInput);
59
- const signatures = parseHeaderDictionary(headers, AttestationHeaders.signature);
58
+ const inputs = parseHeaderDictionary(headers, SignatureHeaders.signatureInput);
59
+ const signatures = parseHeaderDictionary(headers, SignatureHeaders.signature);
60
60
  if (!haveSameKeys(inputs, signatures))
61
61
  throw new Error('Signature-Input and Signature must contain the same labels.');
62
62
  if (inputs.has(input.label) || signatures.has(input.label))
63
63
  throw new Error(`HTTP message signature label "${input.label}" already exists.`);
64
64
  inputs.set(input.label, toInnerList(input));
65
65
  signatures.set(input.label, [signature, new Map()]);
66
- headers.set(AttestationHeaders.signatureInput, serializeDictionary(inputs));
67
- headers.set(AttestationHeaders.signature, serializeDictionary(signatures));
66
+ headers.set(SignatureHeaders.signatureInput, serializeDictionary(inputs));
67
+ headers.set(SignatureHeaders.signature, serializeDictionary(signatures));
68
68
  return new Request(request, { headers });
69
69
  }
70
70
  /** Verifies a request signature, its lifetime, and optionally its nonce. */
@@ -137,8 +137,8 @@ export async function verify(request, options) {
137
137
  return { status: 'verified', input };
138
138
  }
139
139
  function parse(request, expectedTag) {
140
- const inputHeader = request.headers.get(AttestationHeaders.signatureInput);
141
- const signatureHeader = request.headers.get(AttestationHeaders.signature);
140
+ const inputHeader = request.headers.get(SignatureHeaders.signatureInput);
141
+ const signatureHeader = request.headers.get(SignatureHeaders.signature);
142
142
  if (!inputHeader && !signatureHeader)
143
143
  return undefined;
144
144
  if (!inputHeader || !signatureHeader)
@@ -219,7 +219,7 @@ function algorithmFromKey(key) {
219
219
  for (const algorithm of Object.values(Algorithms))
220
220
  if (keySupportsAlgorithm(key, algorithm))
221
221
  return algorithm;
222
- throw new TypeError('Request attestation requires an Ed25519 or RSA-PSS SHA-512 signing key.');
222
+ throw new TypeError('HTTP message signatures require an Ed25519 or RSA-PSS SHA-512 signing key.');
223
223
  }
224
224
  function isInteger(value) {
225
225
  return typeof value === 'number' && Number.isInteger(value);
@@ -0,0 +1,6 @@
1
+ /** Creates the attempt values shared by every prover on one HTTP attempt. */
2
+ export declare function create(): {
3
+ readonly created: number;
4
+ readonly nonce: string;
5
+ };
6
+ //# sourceMappingURL=SigningContext.d.ts.map
@@ -1,11 +1,11 @@
1
1
  import { Base64 } from 'ox';
2
- /** Creates the signing context for one outbound HTTP request attempt. */
2
+ /** Creates the attempt values shared by every prover on one HTTP attempt. */
3
3
  export function create() {
4
4
  const bytes = new Uint8Array(64);
5
5
  crypto.getRandomValues(bytes);
6
- return Object.freeze({
6
+ return {
7
7
  created: Math.floor(Date.now() / 1_000),
8
8
  nonce: Base64.fromBytes(bytes, { pad: false, url: true }),
9
- });
9
+ };
10
10
  }
11
11
  //# sourceMappingURL=SigningContext.js.map
@@ -0,0 +1,9 @@
1
+ import type * as Identity from '../../../identity/Types.js';
2
+ import type { VerificationResult } from './HttpMessageSignature.js';
3
+ /** Maps a non-verified HTTP message signature result to an identity verification. */
4
+ export declare function fromSignature(result: Exclude<VerificationResult, {
5
+ status: 'verified';
6
+ }>): Exclude<Identity.Verification, {
7
+ status: 'valid';
8
+ }>;
9
+ //# sourceMappingURL=Verification.d.ts.map
@@ -0,0 +1,10 @@
1
+ /** Maps a non-verified HTTP message signature result to an identity verification. */
2
+ export function fromSignature(result) {
3
+ if (result.status === 'absent')
4
+ return { status: 'missing' };
5
+ if (result.status === 'invalid')
6
+ return { status: 'invalid', reason: result.reason };
7
+ // The signature was presented but its key is not trusted or available.
8
+ return { status: 'invalid', code: 403, reason: result.reason };
9
+ }
10
+ //# sourceMappingURL=Verification.js.map
@@ -1,4 +1,4 @@
1
- import { Constants as HttpMessageSignature } from '../attestation/internal/HttpMessageSignature.js';
1
+ import { Constants as HttpMessageSignature } from '../internal/HttpMessageSignature.js';
2
2
  /** TAP values required for its RFC 9421 agent-recognition signature profile. */
3
3
  export const Constants = {
4
4
  /** Stable identifier for Trusted Agent Protocol signers and replay storage. */
@@ -0,0 +1,32 @@
1
+ import type * as Identity from '../../../../identity/Types.js';
2
+ import { Constants } from '../Constants.js';
3
+ /**
4
+ * Creates a TAP identity prover.
5
+ *
6
+ * The prover signs each in-scope attempt, including MPP payment retries, with
7
+ * an RFC 9421 HTTP message signature carrying TAP's `agent-browser-auth` or
8
+ * `agent-payer-auth` tag.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * Mppx.create({ identity: [Tap.Client.prover({ intent: 'payment', key, keyId, scope: ['https://shop.example'] })], methods })
13
+ * ```
14
+ */
15
+ export declare function prover(config: prover.Config): Identity.Prover;
16
+ export declare namespace prover {
17
+ type Config = {
18
+ /** TAP signature type for the request. */
19
+ intent: (typeof Constants.intents)[keyof typeof Constants.intents];
20
+ /** Ed25519 or RSA-PSS SHA-512 private key provisioned to the agent provider. */
21
+ key: CryptoKey;
22
+ /** Identifier the merchant uses to resolve the signing public key. */
23
+ keyId: string;
24
+ /** Origins that receive TAP signatures, or `'*'` for any origin. */
25
+ scope: Identity.Prover['scope'];
26
+ /** Signature lifetime in seconds. TAP permits at most eight minutes. @default 480 */
27
+ expiresIn?: number | undefined;
28
+ /** RFC 9421 dictionary label. @default 'tap' */
29
+ label?: string | undefined;
30
+ };
31
+ }
32
+ //# sourceMappingURL=index.d.ts.map
@@ -1,14 +1,18 @@
1
- import * as HttpMessageSignature from '../../attestation/internal/HttpMessageSignature.js';
1
+ import * as HttpMessageSignature from '../../internal/HttpMessageSignature.js';
2
2
  import { Constants } from '../Constants.js';
3
3
  /**
4
- * Creates a TAP request signer.
4
+ * Creates a TAP identity prover.
5
5
  *
6
- * The signer emits RFC 9421 HTTP message signatures with TAP's
7
- * `agent-browser-auth` or `agent-payer-auth` tag. Pass it to
8
- * `Attestation.Client.wrapFetch` so MPPX signs both the initial request and
9
- * its automatic paid retry.
6
+ * The prover signs each in-scope attempt, including MPP payment retries, with
7
+ * an RFC 9421 HTTP message signature carrying TAP's `agent-browser-auth` or
8
+ * `agent-payer-auth` tag.
9
+ *
10
+ * @example
11
+ * ```ts
12
+ * Mppx.create({ identity: [Tap.Client.prover({ intent: 'payment', key, keyId, scope: ['https://shop.example'] })], methods })
13
+ * ```
10
14
  */
11
- export function signer(config) {
15
+ export function prover(config) {
12
16
  const tag = config.intent === Constants.intents.payment ? Constants.tags.payment : Constants.tags.browse;
13
17
  const expiresIn = config.expiresIn ?? Constants.defaultSignatureLifetime;
14
18
  if (!Number.isInteger(expiresIn) ||
@@ -16,8 +20,8 @@ export function signer(config) {
16
20
  expiresIn > Constants.maximumSignatureLifetime)
17
21
  throw new RangeError(`TAP expiresIn must be an integer from 1 to ${Constants.maximumSignatureLifetime}.`);
18
22
  return {
19
- protocol: Constants.protocol,
20
- sign(request, context) {
23
+ scope: config.scope,
24
+ prove(request, context) {
21
25
  return HttpMessageSignature.sign(request, {
22
26
  components: Constants.requiredComponents,
23
27
  context,
@@ -0,0 +1,27 @@
1
+ import type * as Identity from '../../../../identity/Types.js';
2
+ import type * as Store from '../../../../Store.js';
3
+ import type * as SignatureTypes from '../../Types.js';
4
+ import type * as Types from '../Types.js';
5
+ /**
6
+ * Creates a request-stage identity verifier for TAP agent-recognition signatures.
7
+ *
8
+ * TAP verifies trusted agent participation and request integrity. Consumer
9
+ * recognition and payment-container verification stay protocol-specific and
10
+ * are intentionally not inferred from this header-only verifier.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const tap = Tap.Server.verifier({ keyResolver, nonceStore })
15
+ * mppx.charge({ amount: '1', identity: [tap] })
16
+ * ```
17
+ */
18
+ export declare function verifier(config: verifier.Config): Identity.Verifier<Types.VerifiedRequest>;
19
+ export declare namespace verifier {
20
+ type Config = {
21
+ /** Resolves a TAP-approved public key by its `keyid` and signature algorithm. */
22
+ keyResolver: SignatureTypes.KeyResolver;
23
+ /** Atomically consumes each nonce in shared storage for multi-instance deployments. */
24
+ nonceStore: Store.AtomicStore;
25
+ };
26
+ }
27
+ //# sourceMappingURL=index.d.ts.map
@@ -1,14 +1,22 @@
1
- import * as HttpMessageSignature from '../../attestation/internal/HttpMessageSignature.js';
1
+ import * as HttpMessageSignature from '../../internal/HttpMessageSignature.js';
2
+ import * as Verification from '../../internal/Verification.js';
2
3
  import { Constants } from '../Constants.js';
3
4
  /**
4
- * Creates a verifier for TAP agent-recognition signatures.
5
+ * Creates a request-stage identity verifier for TAP agent-recognition signatures.
5
6
  *
6
7
  * TAP verifies trusted agent participation and request integrity. Consumer
7
8
  * recognition and payment-container verification stay protocol-specific and
8
9
  * are intentionally not inferred from this header-only verifier.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const tap = Tap.Server.verifier({ keyResolver, nonceStore })
14
+ * mppx.charge({ amount: '1', identity: [tap] })
15
+ * ```
9
16
  */
10
17
  export function verifier(config) {
11
18
  return {
19
+ stage: 'request',
12
20
  async verify(request) {
13
21
  const result = await HttpMessageSignature.verify(request, {
14
22
  keyResolver: config.keyResolver,
@@ -19,9 +27,9 @@ export function verifier(config) {
19
27
  tag: [Constants.tags.browse, Constants.tags.payment],
20
28
  });
21
29
  if (result.status !== 'verified')
22
- return result;
30
+ return Verification.fromSignature(result);
23
31
  return {
24
- status: 'verified',
32
+ status: 'valid',
25
33
  value: {
26
34
  keyId: result.input.keyId,
27
35
  nonce: result.input.nonce,
@@ -1,4 +1,4 @@
1
- import { Constants as HttpMessageSignature } from '../attestation/internal/HttpMessageSignature.js';
1
+ import { Constants as HttpMessageSignature } from '../internal/HttpMessageSignature.js';
2
2
  /** Web Bot Auth values for the supported HTTPS-directory signature profile. */
3
3
  export const Constants = {
4
4
  /** Stable identifier for Web Bot Auth signers and replay storage. */
@@ -1,7 +1,6 @@
1
- import type * as AttestationTypes from '../../attestation/Types.js';
2
- import { Constants } from '../Constants.js';
1
+ import type * as Identity from '../../../../identity/Types.js';
3
2
  /**
4
- * Creates a Web Bot Auth HTTPS-directory profile request signer.
3
+ * Creates a Web Bot Auth HTTPS-directory profile identity prover.
5
4
  *
6
5
  * The signed `Signature-Agent` header links the request to the HTTPS origin
7
6
  * of an HTTP Message Signatures Directory. This adapter does not support
@@ -9,13 +8,15 @@ import { Constants } from '../Constants.js';
9
8
  * establishes bot identity only; it does not assert consumer identity or
10
9
  * authority to make a payment.
11
10
  */
12
- export declare function signer(config: signer.Config): AttestationTypes.Signer<typeof Constants.protocol>;
13
- export declare namespace signer {
11
+ export declare function prover(config: prover.Config): Identity.Prover;
12
+ export declare namespace prover {
14
13
  type Config = {
15
14
  /** Ed25519 or RSA-PSS SHA-512 private key registered for the bot. */
16
15
  key: CryptoKey;
17
16
  /** RFC 7638 JWK thumbprint for the registered public key. */
18
17
  keyId: string;
18
+ /** Origins that receive Web Bot Auth signatures, or `'*'` for any origin. */
19
+ scope: Identity.Prover['scope'];
19
20
  /** Required HTTPS directory origin, sent with the default `directory` discovery type. */
20
21
  signatureAgent: string;
21
22
  /** Dictionary member name used for `Signature-Agent`. @default signature label */
@@ -1,10 +1,10 @@
1
1
  import { parseDictionary, serializeDictionary } from 'structured-headers';
2
- import * as HttpMessageSignature from '../../attestation/internal/HttpMessageSignature.js';
2
+ import * as HttpMessageSignature from '../../internal/HttpMessageSignature.js';
3
3
  import { Constants } from '../Constants.js';
4
4
  import * as JwkThumbprint from '../internal/JwkThumbprint.js';
5
5
  import * as SignatureAgent from '../internal/SignatureAgent.js';
6
6
  /**
7
- * Creates a Web Bot Auth HTTPS-directory profile request signer.
7
+ * Creates a Web Bot Auth HTTPS-directory profile identity prover.
8
8
  *
9
9
  * The signed `Signature-Agent` header links the request to the HTTPS origin
10
10
  * of an HTTP Message Signatures Directory. This adapter does not support
@@ -12,7 +12,7 @@ import * as SignatureAgent from '../internal/SignatureAgent.js';
12
12
  * establishes bot identity only; it does not assert consumer identity or
13
13
  * authority to make a payment.
14
14
  */
15
- export function signer(config) {
15
+ export function prover(config) {
16
16
  const label = config.label ?? Constants.label;
17
17
  const signatureAgentKey = config.signatureAgentKey ?? label;
18
18
  const expiresIn = config.expiresIn ?? Constants.defaultSignatureLifetime;
@@ -24,8 +24,8 @@ export function signer(config) {
24
24
  if (!Number.isInteger(expiresIn) || expiresIn <= 0)
25
25
  throw new RangeError('Web Bot Auth expiresIn must be a positive integer.');
26
26
  return {
27
- protocol: Constants.protocol,
28
- sign(request, context) {
27
+ scope: config.scope,
28
+ prove(request, context) {
29
29
  const headers = new Headers(request.headers);
30
30
  const signatureAgents = parseSignatureAgents(headers);
31
31
  if (signatureAgents.has(signatureAgentKey))