mppx 0.6.3 → 0.6.6

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 (63) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/Credential.d.ts +4 -0
  3. package/dist/Credential.d.ts.map +1 -1
  4. package/dist/Credential.js +18 -2
  5. package/dist/Credential.js.map +1 -1
  6. package/dist/bin.js +1 -1
  7. package/dist/bin.js.map +1 -1
  8. package/dist/cli/cli.d.ts.map +1 -1
  9. package/dist/cli/cli.js +166 -54
  10. package/dist/cli/cli.js.map +1 -1
  11. package/dist/discovery/Discovery.d.ts +234 -18
  12. package/dist/discovery/Discovery.d.ts.map +1 -1
  13. package/dist/discovery/Discovery.js +24 -2
  14. package/dist/discovery/Discovery.js.map +1 -1
  15. package/dist/discovery/OpenApi.d.ts +1 -1
  16. package/dist/discovery/OpenApi.d.ts.map +1 -1
  17. package/dist/discovery/OpenApi.js +2 -1
  18. package/dist/discovery/OpenApi.js.map +1 -1
  19. package/dist/discovery/Validate.d.ts.map +1 -1
  20. package/dist/discovery/Validate.js +2 -1
  21. package/dist/discovery/Validate.js.map +1 -1
  22. package/dist/stripe/server/internal/html.gen.d.ts +1 -1
  23. package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
  24. package/dist/stripe/server/internal/html.gen.js +1 -1
  25. package/dist/stripe/server/internal/html.gen.js.map +1 -1
  26. package/dist/tempo/client/Charge.js +1 -1
  27. package/dist/tempo/client/Charge.js.map +1 -1
  28. package/dist/tempo/internal/proof.d.ts +6 -2
  29. package/dist/tempo/internal/proof.d.ts.map +1 -1
  30. package/dist/tempo/internal/proof.js +7 -4
  31. package/dist/tempo/internal/proof.js.map +1 -1
  32. package/dist/tempo/server/Charge.d.ts.map +1 -1
  33. package/dist/tempo/server/Charge.js +4 -3
  34. package/dist/tempo/server/Charge.js.map +1 -1
  35. package/dist/tempo/server/internal/html.gen.d.ts +1 -1
  36. package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
  37. package/dist/tempo/server/internal/html.gen.js +1 -1
  38. package/dist/tempo/server/internal/html.gen.js.map +1 -1
  39. package/package.json +2 -2
  40. package/src/Challenge.test.ts +45 -0
  41. package/src/Credential.test.ts +66 -0
  42. package/src/Credential.ts +23 -3
  43. package/src/bin.ts +1 -1
  44. package/src/cli/cli.ts +194 -58
  45. package/src/cli/mcp.test.ts +233 -0
  46. package/src/discovery/Discovery.test.ts +66 -4
  47. package/src/discovery/Discovery.ts +40 -7
  48. package/src/discovery/OpenApi.test.ts +61 -33
  49. package/src/discovery/OpenApi.ts +2 -2
  50. package/src/discovery/Validate.test.ts +117 -0
  51. package/src/discovery/Validate.ts +2 -1
  52. package/src/middlewares/elysia.test.ts +1 -1
  53. package/src/middlewares/express.test.ts +1 -1
  54. package/src/middlewares/hono.test.ts +1 -1
  55. package/src/middlewares/nextjs.test.ts +1 -1
  56. package/src/proxy/Proxy.test.ts +3 -3
  57. package/src/stripe/server/internal/html.gen.ts +1 -1
  58. package/src/tempo/client/Charge.ts +1 -1
  59. package/src/tempo/internal/proof.test.ts +11 -5
  60. package/src/tempo/internal/proof.ts +7 -4
  61. package/src/tempo/server/Charge.test.ts +51 -15
  62. package/src/tempo/server/Charge.ts +5 -3
  63. package/src/tempo/server/internal/html.gen.ts +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"html.gen.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/html.gen.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,IAAI,GAAG,qykcAAqykc,CAAA"}
1
+ {"version":3,"file":"html.gen.js","sourceRoot":"","sources":["../../../../src/tempo/server/internal/html.gen.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,MAAM,CAAC,MAAM,IAAI,GAAG,6wtcAA6wtc,CAAA"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mppx",
3
3
  "type": "module",
4
- "version": "0.6.3",
4
+ "version": "0.6.6",
5
5
  "main": "./dist/index.js",
6
6
  "license": "MIT",
7
7
  "files": [
@@ -124,7 +124,7 @@
124
124
  },
125
125
  "dependencies": {
126
126
  "incur": "^0.3.25",
127
- "ox": "0.14.15",
127
+ "ox": "0.14.18",
128
128
  "zod": "^4.3.6"
129
129
  },
130
130
  "repository": {
@@ -109,6 +109,29 @@ describe('from', () => {
109
109
  },
110
110
  expectedId: 'm39jbWWCIfmfJZSwCfvKFFtBl0Qwf9X4nOmDb21peLA',
111
111
  },
112
+ {
113
+ label: 'with opaque',
114
+ params: {
115
+ realm: 'api.example.com',
116
+ method: 'tempo',
117
+ intent: 'charge',
118
+ request: { amount: '1000000' },
119
+ meta: { pi: 'pi_3abc123XYZ' },
120
+ },
121
+ expectedId: 'rxzKZ2qjXvinqCH96RORTZEPs1KXsA-0AUjrCAPFOWc',
122
+ },
123
+ {
124
+ label: 'with opaque and expires',
125
+ params: {
126
+ realm: 'api.example.com',
127
+ method: 'tempo',
128
+ intent: 'charge',
129
+ request: { amount: '1000000' },
130
+ expires: '2025-01-06T12:00:00Z',
131
+ meta: { pi: 'pi_3abc123XYZ' },
132
+ },
133
+ expectedId: 'KAfoMrA4fnzS1DPWN_cUv_b3_yHxCizdp6OhH7gluMY',
134
+ },
112
135
  {
113
136
  label: 'with description (not in HMAC input)',
114
137
  params: {
@@ -150,6 +173,17 @@ describe('from', () => {
150
173
  },
151
174
  expectedId: 'yLN7yChAejW9WNmb54HpJIWpdb1WWXeA3_aCx4dxmkU',
152
175
  },
176
+ {
177
+ label: 'with empty opaque',
178
+ params: {
179
+ realm: 'api.example.com',
180
+ method: 'tempo',
181
+ intent: 'charge',
182
+ request: { amount: '1000000' },
183
+ meta: {},
184
+ },
185
+ expectedId: 'vb4IyH-0LdJ3s7L0QAw8jIzcZkyxksPhIvEfmHmzA9k',
186
+ },
153
187
  {
154
188
  label: 'different realm',
155
189
  params: {
@@ -180,6 +214,17 @@ describe('from', () => {
180
214
  },
181
215
  expectedId: 'aAY7_IEDzsznNYplhOSE8cERQxvjFcT4Lcn-7FHjLVE',
182
216
  },
217
+ {
218
+ label: 'with multi-key opaque',
219
+ params: {
220
+ realm: 'api.example.com',
221
+ method: 'tempo',
222
+ intent: 'charge',
223
+ request: { amount: '1000000' },
224
+ meta: { deposit: 'dep_456', pi: 'pi_3abc123XYZ' },
225
+ },
226
+ expectedId: 'aKskU8sadR5ZuFbUCsIwhO-ENxuVpTw17FdwHEXsJDk',
227
+ },
183
228
  ] as const
184
229
 
185
230
  test.each(hmacVectors)('hmac: $label', ({ params, expectedId }) => {
@@ -1,4 +1,5 @@
1
1
  import { Challenge, Credential } from 'mppx'
2
+ import { Base64 } from 'ox'
2
3
  import { describe, expect, test } from 'vp/test'
3
4
 
4
5
  const challenge = Challenge.from({
@@ -88,6 +89,28 @@ describe('serialize', () => {
88
89
  const deserialized = Credential.deserialize(header)
89
90
  expect(deserialized.challenge.request).toEqual({ amount: '1000' })
90
91
  })
92
+
93
+ test('behavior: serializes opaque as a base64url string', () => {
94
+ const credential = Credential.from({
95
+ challenge: Challenge.from({
96
+ id: 'opaque123',
97
+ intent: 'charge',
98
+ meta: { pi: 'pi_3abc123XYZ' },
99
+ method: 'tempo',
100
+ realm: 'api.example.com',
101
+ request: { amount: '1000' },
102
+ }),
103
+ payload: { signature: '0x1234' },
104
+ })
105
+
106
+ const header = Credential.serialize(credential)
107
+ const encoded = header.replace(/^Payment\s+/i, '')
108
+ const parsed = JSON.parse(Base64.toString(encoded)) as {
109
+ challenge: { opaque?: unknown }
110
+ }
111
+
112
+ expect(parsed.challenge.opaque).toBe('eyJwaSI6InBpXzNhYmMxMjNYWVoifQ')
113
+ })
91
114
  })
92
115
 
93
116
  describe('deserialize', () => {
@@ -134,6 +157,49 @@ describe('deserialize', () => {
134
157
  expect(deserialized.source).toBe(original.source)
135
158
  })
136
159
 
160
+ test('behavior: deserializes spec-compliant opaque string credentials', () => {
161
+ const encoded = Base64.fromString(
162
+ JSON.stringify({
163
+ challenge: {
164
+ id: 'opaque123',
165
+ intent: 'charge',
166
+ method: 'tempo',
167
+ opaque: 'eyJwaSI6InBpXzNhYmMxMjNYWVoifQ',
168
+ realm: 'api.example.com',
169
+ request: 'eyJhbW91bnQiOiIxMDAwIn0',
170
+ },
171
+ payload: { signature: '0x1234' },
172
+ }),
173
+ { pad: false, url: true },
174
+ )
175
+
176
+ const credential = Credential.deserialize(`Payment ${encoded}`)
177
+
178
+ expect(credential.challenge.opaque).toEqual({ pi: 'pi_3abc123XYZ' })
179
+ expect(credential.challenge.request).toEqual({ amount: '1000' })
180
+ })
181
+
182
+ test('behavior: preserves legacy object-shaped opaque credentials', () => {
183
+ const encoded = Base64.fromString(
184
+ JSON.stringify({
185
+ challenge: {
186
+ id: 'opaque123',
187
+ intent: 'charge',
188
+ method: 'tempo',
189
+ opaque: { pi: 'pi_3abc123XYZ' },
190
+ realm: 'api.example.com',
191
+ request: 'eyJhbW91bnQiOiIxMDAwIn0',
192
+ },
193
+ payload: { signature: '0x1234' },
194
+ }),
195
+ { pad: false, url: true },
196
+ )
197
+
198
+ const credential = Credential.deserialize(`Payment ${encoded}`)
199
+
200
+ expect(credential.challenge.opaque).toEqual({ pi: 'pi_3abc123XYZ' })
201
+ })
202
+
137
203
  test('error: throws for missing Payment scheme', () => {
138
204
  expect(() => Credential.deserialize('Bearer abc123')).toThrow('Missing Payment scheme.')
139
205
  })
package/src/Credential.ts CHANGED
@@ -44,6 +44,8 @@ export class InvalidCredentialEncodingError extends Error {
44
44
 
45
45
  /**
46
46
  * Deserializes an Authorization header value to a credential.
47
+ * Accepts the spec-compliant base64url `opaque` string shape and the legacy
48
+ * object-shaped `opaque` form emitted by older mppx versions.
47
49
  *
48
50
  * @param header - The Authorization header value.
49
51
  * @returns The deserialized credential.
@@ -61,12 +63,26 @@ export function deserialize<payload = unknown>(value: string): Credential<payloa
61
63
  try {
62
64
  const json = Base64.toString(prefixMatch[1])
63
65
  const parsed = JSON.parse(json) as {
64
- challenge: Omit<Challenge.Challenge, 'request'> & { request: string }
66
+ challenge: Omit<Challenge.Challenge, 'opaque' | 'request'> & {
67
+ opaque?: Record<string, string> | string
68
+ request: string
69
+ }
65
70
  payload: payload
66
71
  source?: string
67
72
  }
68
73
  const challenge = Challenge.Schema.parse({
69
74
  ...parsed.challenge,
75
+ ...(parsed.challenge.opaque !== undefined && {
76
+ // TODO: Drop the legacy object-shaped `opaque` fallback after old mppx
77
+ // clients are no longer in circulation. Older mppx versions echoed
78
+ // `opaque` as an expanded JSON object in credentials, but the Payment
79
+ // auth spec requires clients to return the original base64url string
80
+ // unchanged in the credential challenge object.
81
+ opaque:
82
+ typeof parsed.challenge.opaque === 'string'
83
+ ? (PaymentRequest.deserialize(parsed.challenge.opaque) as Record<string, string>)
84
+ : parsed.challenge.opaque,
85
+ }),
70
86
  request: PaymentRequest.deserialize(parsed.challenge.request),
71
87
  })
72
88
  return {
@@ -140,6 +156,8 @@ export function fromRequest<payload = unknown>(request: Request): Credential<pay
140
156
 
141
157
  /**
142
158
  * Serializes a credential to the Authorization header format.
159
+ * When present, `challenge.opaque` is encoded as the base64url string required
160
+ * by the Payment auth credential format.
143
161
  *
144
162
  * @param credential - The credential to serialize.
145
163
  * @returns A string suitable for the Authorization header value.
@@ -153,10 +171,12 @@ export function fromRequest<payload = unknown>(request: Request): Credential<pay
153
171
  * ```
154
172
  */
155
173
  export function serialize(credential: Credential): string {
174
+ const { opaque, request, ...challenge } = credential.challenge
156
175
  const wire = {
157
176
  challenge: {
158
- ...credential.challenge,
159
- request: PaymentRequest.serialize(credential.challenge.request),
177
+ ...challenge,
178
+ ...(opaque !== undefined && { opaque: PaymentRequest.serialize(opaque) }),
179
+ request: PaymentRequest.serialize(request),
160
180
  },
161
181
  payload: credential.payload,
162
182
  ...(credential.source && { source: credential.source }),
package/src/bin.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
2
  import cli from './cli/cli.js'
3
3
 
4
- cli.serve().then(() => process.exit(0))
4
+ await cli.serve()