@reclaimprotocol/client 0.1.29 → 0.1.32

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 (110) hide show
  1. package/README.md +255 -234
  2. package/lib/api.d.ts +2 -0
  3. package/lib/api.js +2 -0
  4. package/lib/client.js +2 -0
  5. package/lib/enckeys.d.ts +1 -1
  6. package/lib/enckeys.js +2 -2
  7. package/lib/encoding.d.ts +4 -0
  8. package/lib/encoding.js +10 -0
  9. package/lib/feature-flags.d.ts +9 -0
  10. package/lib/feature-flags.js +19 -0
  11. package/lib/index.d.ts +4 -4
  12. package/lib/index.js +4 -4
  13. package/lib/jwks.d.ts +8 -8
  14. package/lib/jwks.js +38 -17
  15. package/lib/launch/destination.d.ts +14 -0
  16. package/lib/launch/destination.js +89 -0
  17. package/lib/launch/device.d.ts +30 -0
  18. package/lib/launch/device.js +168 -0
  19. package/lib/launch/environment.d.ts +224 -0
  20. package/lib/launch/environment.js +98 -0
  21. package/lib/launch/errors.d.ts +20 -0
  22. package/lib/launch/errors.js +25 -0
  23. package/lib/launch/extension.d.ts +42 -0
  24. package/lib/launch/extension.js +72 -0
  25. package/lib/launch/flow.d.ts +82 -0
  26. package/lib/launch/flow.js +222 -0
  27. package/lib/launch/index.d.ts +16 -0
  28. package/lib/launch/index.js +12 -0
  29. package/lib/launch/mobile.d.ts +77 -0
  30. package/lib/launch/mobile.js +124 -0
  31. package/lib/launch/modal.d.ts +50 -0
  32. package/lib/launch/modal.js +258 -0
  33. package/lib/launch/options.d.ts +74 -0
  34. package/lib/launch/options.js +20 -0
  35. package/lib/launch/portal.d.ts +28 -0
  36. package/lib/launch/portal.js +68 -0
  37. package/lib/launch/qr.d.ts +5 -0
  38. package/lib/launch/qr.js +28 -0
  39. package/lib/launch/target.d.ts +46 -0
  40. package/lib/launch/target.js +128 -0
  41. package/lib/launch/vendor/qrcodegen.d.ts +134 -0
  42. package/lib/launch/vendor/qrcodegen.js +822 -0
  43. package/lib/legacy-proof.d.ts +2 -0
  44. package/lib/legacy-proof.js +37 -7
  45. package/lib/proof-hash.d.ts +5 -5
  46. package/lib/proof-hash.js +1 -1
  47. package/lib/proof-recipe.d.ts +3 -3
  48. package/lib/proof-recipe.js +1 -1
  49. package/lib/proof-types.d.ts +35 -8
  50. package/lib/reclaim.d.ts +81 -70
  51. package/lib/reclaim.js +79 -130
  52. package/lib/request-proof.d.ts +60 -0
  53. package/lib/request-proof.js +241 -0
  54. package/lib/result.d.ts +46 -38
  55. package/lib/result.js +160 -100
  56. package/lib/routes.gen.d.ts +76 -28
  57. package/lib/routes.gen.js +24 -11
  58. package/lib/tee-binding.d.ts +1 -1
  59. package/lib/types/openapi.d.ts +1 -0
  60. package/lib/types/openapi.gen.d.ts +1598 -485
  61. package/lib/types/openapi.gen.js +5 -0
  62. package/lib/verification-clients.d.ts +28 -14
  63. package/lib/verification-clients.js +31 -11
  64. package/lib/verify-attestor-tee.d.ts +19 -2
  65. package/lib/verify-attestor-tee.js +40 -4
  66. package/lib/verify-tee.d.ts +10 -1
  67. package/lib/verify-tee.js +34 -10
  68. package/package.json +5 -1
  69. package/src/api.ts +9 -0
  70. package/src/client.ts +2 -0
  71. package/src/enckeys.ts +2 -2
  72. package/src/encoding.ts +12 -0
  73. package/src/feature-flags.ts +39 -0
  74. package/src/index.ts +13 -14
  75. package/src/jwks.ts +52 -30
  76. package/src/launch/destination.ts +111 -0
  77. package/src/launch/device.ts +242 -0
  78. package/src/launch/environment.ts +337 -0
  79. package/src/launch/errors.ts +49 -0
  80. package/src/launch/extension.ts +124 -0
  81. package/src/launch/flow.ts +369 -0
  82. package/src/launch/index.ts +42 -0
  83. package/src/launch/mobile.ts +184 -0
  84. package/src/launch/modal.ts +378 -0
  85. package/src/launch/options.ts +99 -0
  86. package/src/launch/portal.ts +104 -0
  87. package/src/launch/qr.ts +37 -0
  88. package/src/launch/target.ts +150 -0
  89. package/src/launch/vendor/qrcodegen.ts +1034 -0
  90. package/src/proof-hash.ts +6 -6
  91. package/src/proof-recipe.ts +3 -3
  92. package/src/proof-types.ts +36 -8
  93. package/src/reclaim.ts +185 -218
  94. package/src/{legacy-proof.ts → request-proof.ts} +81 -36
  95. package/src/result.ts +238 -172
  96. package/src/routes.gen.ts +24 -11
  97. package/src/tee-binding.ts +1 -1
  98. package/src/types/openapi.gen.ts +1603 -485
  99. package/src/types/openapi.ts +3 -1
  100. package/src/verification-clients.ts +33 -19
  101. package/src/verify-attestor-tee.ts +66 -8
  102. package/src/verify-tee.ts +44 -13
  103. package/lib/api-client.d.ts +0 -31
  104. package/lib/api-client.js +0 -112
  105. package/lib/api-consts.d.ts +0 -4
  106. package/lib/api-consts.js +0 -4
  107. package/lib/api-errors.d.ts +0 -13
  108. package/lib/api-errors.js +0 -30
  109. package/lib/eip712.d.ts +0 -20
  110. package/lib/eip712.js +0 -108
package/src/reclaim.ts CHANGED
@@ -1,20 +1,19 @@
1
1
  /**
2
2
  * Declarative entry point for the consumer SDK. The ONLY config is the org
3
3
  * secret (`rorg_…`) — it authenticates every call and identifies the org
4
- * server-side. Everything else is passed where it's actually used: the `orgId`
5
- * to the org-scoped `keypair.set`/`get`, and the eth decryption key to
6
- * `results.decrypt`/`receive` (only when results arrive encrypted):
4
+ * server-side. Pass the optional organization ETH private key only to the
5
+ * operation that needs it:
7
6
  *
8
- * const reclaim = createReclaim({ orgSecret })
9
- * // one-time setup:
10
- * await reclaim.callbacks.register({ callbackUrl })
11
- * // enable encryption (eth public key, hex):
12
- * await reclaim.keypair.set(orgId, { publicKey, canEncryptResult: true })
13
- * const session = await reclaim.sessions.create({ providers, context })
7
+ * const reclaim = ReclaimVerification.create({ orgSecret })
8
+ * const session = await reclaim.sessions.create({
9
+ * providers,
10
+ * context,
11
+ * orgEthPrivateKey, // optional TEE session binding
12
+ * })
14
13
  * // …end-user completes session.verificationUrl…
15
14
  * const outcome = await reclaim.results.receive(callbackBody, {
16
- * credential: ethPrivateKeyHex, // omit for plaintext
17
- * expectedReclaimSessionId: session.id,
15
+ * orgEthPrivateKey, // optional decryption and TEE proof verification
16
+ * reclaimSessionId: session.id,
18
17
  * })
19
18
  *
20
19
  * Built by composition over the package's pure helpers (`decryptCallback`,
@@ -28,10 +27,9 @@ import {
28
27
  type DecryptionKeyInput,
29
28
  loadDecryptionKey,
30
29
  } from './enckeys.ts'
31
- import { isProblemError } from './errors.ts'
32
30
  import type { BuilderProofRecipe } from './proof-recipe.ts'
33
31
  import { proofHashesFromBuilderRecipe } from './proof-recipe.ts'
34
- import type { LegacyProof } from './proof-types.ts'
32
+ import type { ReclaimRequestProof } from './proof-types.ts'
35
33
  import {
36
34
  type CallbackBody,
37
35
  decryptCallback,
@@ -45,21 +43,9 @@ import {
45
43
  type LegacyTeeNonceMode,
46
44
  } from './tee-binding.ts'
47
45
 
48
- type CallbackSubscription = components['schemas']['CallbackSubscription']
49
46
  type VerificationSession = components['schemas']['VerificationSession']
50
47
  type VerificationEvent = components['schemas']['VerificationEvent']
51
48
  type VerificationEventRecord = components['schemas']['VerificationEventRecord']
52
- type OrgKeypair = components['schemas']['OrgKeypair']
53
- type OrgKeypairRequest = components['schemas']['OrgKeypairRequest']
54
-
55
- /** Terminal lifecycle events selected by default for callback subscriptions. */
56
- export const TERMINAL_EVENTS = [
57
- 'verification_success',
58
- 'verification_rejected',
59
- 'verification_error',
60
- 'verification_cancelled',
61
- 'session_expired',
62
- ] as const
63
49
 
64
50
  const SIGNED_RESULT_EVENTS = [
65
51
  'verification_success',
@@ -67,47 +53,41 @@ const SIGNED_RESULT_EVENTS = [
67
53
  'verification_error',
68
54
  ] as const
69
55
 
70
- export interface RegisterCallbackInput {
71
- callbackUrl: string
72
- events: VerificationEvent[]
73
- }
74
-
75
56
  export interface CreateSessionInput {
76
57
  /** Providers to verify, in run order. The end-user proves each (one browser
77
58
  * session) and one combined result is delivered; all are required. */
78
59
  providers: { providerId: string, version?: string }[]
79
60
  context: Record<string, unknown>
80
- /** Exact registered Verification Client base URL. It may contain a
61
+ /** Exact registered Verification Client base URL. Defaults to the Portal.
62
+ * It may contain a
81
63
  * `{sessionId}` placeholder; Builder otherwise adds the session id as a
82
64
  * query parameter. Open the returned `verificationUrl`, not this base
83
65
  * URL. */
84
- verificationClientUrl: string
85
- /** Optional legacy-compatible TEE session binding. The app secret is the
86
- * organization's verification private key and never leaves this process.
66
+ verificationClientUrl?: string
67
+ /** Optional legacy-compatible TEE session binding. The organization's ETH
68
+ * private key never leaves this process.
87
69
  * Session creation returns only after Builder stores the signed binding. */
88
- teeAttestation?: CreateSessionTeeAttestation
70
+ orgEthPrivateKey?: string
71
+ /** Override the legacy TEE nonce mode when binding a session. */
72
+ teeNonceMode?: LegacyTeeNonceMode
73
+ /** Opt this session into diagnostic mode: more event detail, including
74
+ * personal data such as the claimant's IP addresses. Builder builds
75
+ * `verificationUrl` with `diag=1` for a diagnostic session — do not
76
+ * append `diag` to the URL directly, since Builder strips it and derives
77
+ * the parameter from this flag instead. Treat a diagnostic
78
+ * `verificationUrl` as sensitive. */
79
+ diagnostics?: boolean
89
80
  }
90
81
 
91
- /** Options intentionally mirror legacy `verifyProof`'s `teeAttestation`
92
- * vocabulary. `timestamp` is decimal Unix milliseconds; omit it to use now. */
93
- export interface CreateSessionTeeAttestation {
94
- appSecret: string
82
+ /** Input for explicitly binding a session's legacy-compatible TEE context. */
83
+ export interface BindSessionTeeNonceInput {
84
+ orgEthPrivateKey: string
95
85
  timestamp?: string
96
86
  /** `hash` is the default. Use `signature` only for an older Verification
97
87
  * Client that expects the 65-byte EIP-191 nonce. */
98
- nonceMode?: LegacyTeeNonceMode
88
+ teeNonceMode?: LegacyTeeNonceMode
99
89
  }
100
90
 
101
- /** Input for `keypair.set`: the org's eth public key as uncompressed hex
102
- * (`publicKey`, the form wallets/ethers emit) OR as a secp256k1 JWK
103
- * (`publicKeyJwk`); keep the matching eth private key to decrypt results.
104
- * `canEncryptResult` toggles ECIES encryption (default off). The two request
105
- * shapes are the spec's `OrgKeypairRequest` union members, relaxed to optional
106
- * so the SDK caller supplies just one key form. */
107
- export type SetKeypairInput =
108
- & Partial<components['schemas']['OrgKeypairHexRequest']>
109
- & Partial<components['schemas']['OrgKeypairJwkRequest']>
110
-
111
91
  export interface ReclaimConfig {
112
92
  /** The org secret (`rorg_…`). Authenticates ALL API calls and identifies
113
93
  * the org server-side. The only required config. */
@@ -120,70 +100,82 @@ export interface ReclaimConfig {
120
100
  headers?: Record<string, string>
121
101
  }
122
102
 
103
+ /** Serializable SDK configuration.
104
+ * Credentials and runtime hooks are omitted. */
105
+ export interface ReclaimVerificationState {
106
+ version: 1
107
+ baseUrl: string
108
+ }
109
+
110
+ /** Runtime-only values required to restore serialized SDK configuration. */
111
+ export type ImportReclaimVerificationConfig = Omit<ReclaimConfig, 'baseUrl'>
112
+
123
113
  /** Options for `results.receive` — the verification options plus an optional
124
114
  * eth decryption key (the org's eth private key as `0x`-hex). The key is
125
115
  * needed only when the delivery is an ECIES ciphertext. */
126
- export type ReceiveOptions = VerifyResultFullOptions & {
127
- credential?: DecryptionKeyInput
116
+ export type ReceiveOptions = VerifyResultFullOptions
117
+
118
+ /** A thin backend-side handle on a session's `verificationUrl` — see
119
+ * `reclaim.flow`. Not the browser launcher: this holds no device
120
+ * detection or launch mechanism, only the URL the browser needs. The
121
+ * browser-only `ReclaimFlow.from(session)` in
122
+ * `@reclaimprotocol/client/launch` is the counterpart that actually
123
+ * resolves how to open it. */
124
+ export interface SessionFlow {
125
+ /** `session.verificationUrl`. A `Promise` so backend code calling this
126
+ * doesn't need to special-case it against the browser `ReclaimFlow`'s
127
+ * `getUrl()`, which does real async work. */
128
+ getUrl(): Promise<string>
129
+ readonly session: VerificationSession
128
130
  }
129
131
 
130
- export interface Reclaim {
132
+ /** `reclaim.flow(session)` wraps a session already created via
133
+ * `sessions.create`/`sessions.get`; `reclaim.flow.create(input)` creates one
134
+ * and wraps it in the same step. */
135
+ export interface ReclaimFlowFactory {
136
+ (session: VerificationSession): SessionFlow
137
+ create(input: CreateSessionInput): Promise<SessionFlow>
138
+ }
139
+
140
+ export interface ReclaimVerification {
141
+ /** Serialize non-secret configuration. Credentials, headers, and custom
142
+ * fetch implementations are never included. */
143
+ export(): string
131
144
  /** Typed escape hatch: `reclaim.api('OperationId', args)` for any spec
132
145
  * operation. */
133
146
  api: ReclaimClient['call']
134
147
  sessions: {
135
148
  /** Create a verification session. `verificationClientUrl` defaults to
136
- * the Builder's own `/v/{sessionId}` page. */
137
- create(
138
- input: Omit<CreateSessionInput, 'verificationClientUrl'> &
139
- Partial<Pick<CreateSessionInput, 'verificationClientUrl'>>,
140
- ): Promise<VerificationSession>
149
+ * the Portal, `VerificationClient.portals`. */
150
+ create(input: CreateSessionInput): Promise<VerificationSession>
141
151
  /** Add the immutable legacy-compatible TEE context after session
142
152
  * creation. Normally use
143
- * `sessions.create({ teeAttestation: { appSecret } })`; this method
153
+ * `sessions.create({ orgEthPrivateKey })`; this method
144
154
  * supports retrying or explicitly orchestrating the two-step flow. */
145
155
  bindTeeNonce(
146
- sessionId: string,
147
- input: CreateSessionTeeAttestation,
156
+ reclaimSessionId: string,
157
+ input: BindSessionTeeNonceInput,
148
158
  ): Promise<VerificationSession>
149
- get(sessionId: string): Promise<VerificationSession>
150
- listEvents(sessionId: string): Promise<VerificationEventRecord[]>
151
- }
152
- callbacks: {
153
- /** Register a callback subscription (one-time setup). Delivery is scoped
154
- * to the token's org; results are ECIES-encrypted to the org's eth key
155
- * when `canEncryptResult` is enabled (see `keypair.set`), else plaintext.
156
- * `events` defaults to all terminal lifecycle events. */
157
- register(
158
- input: Partial<RegisterCallbackInput> &
159
- Pick<RegisterCallbackInput, 'callbackUrl'>,
160
- ): Promise<CallbackSubscription>
161
- list(): Promise<CallbackSubscription[]>
162
- remove(subscriptionId: string): Promise<void>
163
- }
164
- keypair: {
165
- /** Set/rotate org `orgId`'s single eth encryption keypair. When
166
- * `canEncryptResult` is enabled, terminal results are delivered as an
167
- * ECIES ciphertext encrypted to it. */
168
- set(orgId: string, input: SetKeypairInput): Promise<OrgKeypair>
169
- /** Get org `orgId`'s encryption keypair, or `undefined` when none is set
170
- * (the org is keyless ⇒ plaintext delivery). */
171
- get(orgId: string): Promise<OrgKeypair | undefined>
159
+ get(reclaimSessionId: string): Promise<VerificationSession>
160
+ listEvents(reclaimSessionId: string): Promise<VerificationEventRecord[]>
172
161
  }
173
162
  results: {
174
163
  /** Decrypt a callback body (ECIES ciphertext or plaintext JWS) to the
175
- * signed JWS. Pass the eth `credential` (`0x`-hex private key) only when
176
- * the delivery is encrypted. */
177
- decrypt(body: CallbackBody, credential?: DecryptionKeyInput): Promise<Jws>
164
+ * signed JWS. Pass `orgEthPrivateKey` only when the delivery is
165
+ * encrypted. */
166
+ decrypt(
167
+ body: CallbackBody,
168
+ orgEthPrivateKey?: DecryptionKeyInput,
169
+ ): Promise<Jws>
178
170
  /** Verify a decrypted JWS: JWKS discovery, ES256K signature,
179
- * `reclaimSessionId`/`aud`, any legacy `exp`, and exact legacy proofs. */
171
+ * `reclaimSessionId`/`aud`, any legacy `exp`, and exact proofs. */
180
172
  verify(
181
173
  jws: Jws,
182
174
  opts?: VerifyResultFullOptions,
183
175
  ): Promise<VerifyResultFullOutcome>
184
176
  /** Handle any callback delivery in one call — the 90% path. Branches on
185
177
  * the envelope's `event`: a terminal event is decrypted (with
186
- * `opts.credential` when encrypted) + verified into a `result`; a
178
+ * `opts.orgEthPrivateKey` when encrypted) and verified into a `result`; a
187
179
  * non-terminal event is returned as a plaintext (unsigned) `event`
188
180
  * notification without decryption. (A raw JWE/JWS body with no envelope
189
181
  * `event` is treated as a terminal result.) */
@@ -192,6 +184,8 @@ export interface Reclaim {
192
184
  opts?: ReceiveOptions,
193
185
  ): Promise<ReceivedDelivery>
194
186
  }
187
+ /** `reclaim.flow(session)` — see `ReclaimFlowFactory`. */
188
+ flow: ReclaimFlowFactory
195
189
  }
196
190
 
197
191
  /** Outcome of `results.receive` — a verified terminal result or a plaintext
@@ -204,11 +198,26 @@ export type ReceivedDelivery =
204
198
  eventData: Record<string, unknown>
205
199
  }
206
200
 
207
- export function createReclaim(config: ReclaimConfig): Reclaim {
201
+ export const ReclaimVerification = Object.freeze({
202
+ create: createReclaimVerification,
203
+ import: importReclaimVerification,
204
+ })
205
+
206
+ function importReclaimVerification(
207
+ serialized: string,
208
+ config: ImportReclaimVerificationConfig,
209
+ ) {
210
+ const state = parseReclaimVerificationState(serialized)
211
+ return createReclaimVerification({ ...config, baseUrl: state.baseUrl })
212
+ }
213
+
214
+ function createReclaimVerification(
215
+ config: ReclaimConfig,
216
+ ): ReclaimVerification {
208
217
  if(!config?.orgSecret) {
209
218
  throw new Error(
210
- 'createReclaim requires an org secret — pass '
211
- + 'createReclaim({ orgSecret: <rorg_… org secret> })',
219
+ 'ReclaimVerification.create requires an org secret — pass '
220
+ + 'ReclaimVerification.create({ orgSecret: <rorg_… org secret> })',
212
221
  )
213
222
  }
214
223
 
@@ -228,101 +237,42 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
228
237
  )
229
238
 
230
239
  return {
240
+ export() {
241
+ return JSON.stringify({
242
+ version: 1,
243
+ baseUrl: client.baseUrl,
244
+ } satisfies ReclaimVerificationState)
245
+ },
231
246
  api: client.call.bind(client),
232
247
  sessions: {
233
- async create(input) {
234
- const { teeAttestation, ...request } = input
235
- const body:
236
- components['schemas']['CreateVerificationSessionRequest'] = {
237
- ...request,
238
- verificationClientUrl:
239
- request.verificationClientUrl ?? `${client.baseUrl}/v/{sessionId}`,
240
- }
241
- const { data } = await client.call('CreateVerificationSession', {
242
- body,
243
- })
244
- return teeAttestation
245
- ? bindTeeNonce(data, teeAttestation)
246
- : data
247
- },
248
- async bindTeeNonce(sessionId, input) {
248
+ create: createSession,
249
+ async bindTeeNonce(reclaimSessionId, input) {
249
250
  const { data } = await client.call('GetVerificationSession', {
250
- params: { sessionId },
251
+ params: { sessionId: reclaimSessionId },
251
252
  })
252
253
  return bindTeeNonce(data, input)
253
254
  },
254
- async get(sessionId) {
255
+ async get(reclaimSessionId) {
255
256
  const { data } = await client.call('GetVerificationSession', {
256
- params: { sessionId },
257
+ params: { sessionId: reclaimSessionId },
257
258
  })
258
259
  return data
259
260
  },
260
- async listEvents(sessionId) {
261
+ async listEvents(reclaimSessionId) {
261
262
  const { data } = await client.call('ListVerificationEvents', {
262
- params: { sessionId },
263
- })
264
- return data.items
265
- },
266
- },
267
- callbacks: {
268
- async register(input) {
269
- const events = input.events ?? [...TERMINAL_EVENTS]
270
- const { data } = await client.call('CreateCallbackSubscription', {
271
- body: { callbackUrl: input.callbackUrl, events },
263
+ params: { sessionId: reclaimSessionId },
272
264
  })
273
- return data
274
- },
275
- async list() {
276
- const { data } = await client.call('ListCallbackSubscriptions')
277
265
  return data.items
278
266
  },
279
- async remove(subscriptionId) {
280
- await client.call('DeleteCallbackSubscription', {
281
- params: { subscriptionId },
282
- })
283
- },
284
- },
285
- keypair: {
286
- async set(orgId, input) {
287
- // Forward the eth public key as given (hex or JWK); the API stores
288
- // the secp256k1 JWK + derived address.
289
- const body = {
290
- ...(input.publicKeyJwk
291
- ? { publicKeyJwk: input.publicKeyJwk }
292
- : { publicKey: input.publicKey }),
293
- canEncryptResult: input.canEncryptResult ?? false,
294
- ...(input.label ? { label: input.label } : {}),
295
- } as OrgKeypairRequest
296
- const { data } = await client.call('SetOrgKeypair', {
297
- params: { orgId },
298
- body,
299
- })
300
- return data
301
- },
302
- async get(orgId) {
303
- try {
304
- const { data } = await client.call('GetOrgKeypair', {
305
- params: { orgId },
306
- })
307
- return data
308
- } catch(err) {
309
- // 404 ⇒ keyless org (plaintext delivery).
310
- if(isNotFound(err)) {
311
- return undefined
312
- }
313
-
314
- throw err
315
- }
316
- },
317
267
  },
318
268
  results: {
319
- async decrypt(body, credential) {
320
- return decryptCallback(resolveKey(credential), body)
269
+ async decrypt(body, orgEthPrivateKey) {
270
+ return decryptCallback(resolveKey(orgEthPrivateKey), body)
321
271
  },
322
272
  verify(jws, opts) {
323
273
  return verifyResultFull(jws, {
324
274
  fetchImpl: config.fetch,
325
- ...withRegisteredIssuer(opts),
275
+ ...withBuilderTrust(opts),
326
276
  })
327
277
  },
328
278
  async receive(body, opts) {
@@ -348,28 +298,59 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
348
298
  }
349
299
  }
350
300
 
351
- const { credential, ...verifyOpts } = opts ?? {}
352
- const key = resolveKey(credential)
301
+ const { orgEthPrivateKey, ...verifyOpts } = opts ?? {}
302
+ const key = resolveKey(orgEthPrivateKey)
353
303
  const jws = await decryptCallback(key, body)
354
304
  const result = await verifyResultFull(jws, {
355
305
  fetchImpl: config.fetch,
356
- ...withRegisteredIssuer(verifyOpts),
306
+ ...withBuilderTrust({ ...verifyOpts, orgEthPrivateKey }),
357
307
  })
358
308
  return { kind: 'result', result }
359
309
  },
360
310
  },
311
+ flow: Object.assign(
312
+ (session: VerificationSession): SessionFlow => makeSessionFlow(session),
313
+ {
314
+ async create(input: CreateSessionInput): Promise<SessionFlow> {
315
+ return makeSessionFlow(await createSession(input))
316
+ },
317
+ },
318
+ ),
319
+ }
320
+
321
+ async function createSession(
322
+ input: CreateSessionInput,
323
+ ): Promise<VerificationSession> {
324
+ const {
325
+ orgEthPrivateKey,
326
+ teeNonceMode,
327
+ ...request
328
+ } = input
329
+ const body:
330
+ components['schemas']['CreateVerificationSessionRequest'] = {
331
+ ...request,
332
+ }
333
+ const { data } = await client.call('CreateVerificationSession', {
334
+ body,
335
+ })
336
+ return orgEthPrivateKey
337
+ ? bindTeeNonce(data, {
338
+ orgEthPrivateKey,
339
+ teeNonceMode,
340
+ })
341
+ : data
361
342
  }
362
343
 
363
344
  async function bindTeeNonce(
364
345
  session: VerificationSession,
365
- input: CreateSessionTeeAttestation,
346
+ input: BindSessionTeeNonceInput,
366
347
  ) {
367
348
  const binding = createBuilderTeeBinding({
368
349
  organizationId: session.orgId,
369
- organizationVerificationPrivateKey: input.appSecret,
350
+ organizationVerificationPrivateKey: input.orgEthPrivateKey,
370
351
  sessionId: session.id,
371
352
  timestamp: input.timestamp ?? Date.now().toString(),
372
- nonceMode: input.nonceMode,
353
+ nonceMode: input.teeNonceMode,
373
354
  })
374
355
  const { data } = await client.call('BindVerificationSessionTeeNonce', {
375
356
  params: { sessionId: session.id },
@@ -394,7 +375,7 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
394
375
  providerId: string,
395
376
  providerVersion: string | undefined,
396
377
  _allowedTags: string[] | undefined,
397
- proofs: LegacyProof[],
378
+ proofs: ReclaimRequestProof[],
398
379
  ) {
399
380
  let exactVersion = providerVersion
400
381
  if(!exactVersion) {
@@ -422,10 +403,9 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
422
403
  }]
423
404
  }
424
405
 
425
- function withRegisteredIssuer(opts: VerifyResultFullOptions = {}) {
426
- const sessionId = opts.sessionId ?? opts.expectedReclaimSessionId
406
+ function withBuilderTrust(opts: VerifyResultFullOptions = {}) {
407
+ const reclaimSessionId = opts.reclaimSessionId
427
408
  const requestedProofValidation = opts.proofValidation
428
- ?? opts.proofVerification
429
409
  const resolvedOpts: VerifyResultFullOptions = {
430
410
  ...opts,
431
411
  proofValidation: {
@@ -433,59 +413,32 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
433
413
  resolveProvider: requestedProofValidation?.resolveProvider
434
414
  ?? resolvedProvider,
435
415
  },
436
- ...(sessionId && !opts.resolveSession && !opts.proofRecipes
416
+ ...(reclaimSessionId && !opts.resolveSession && !opts.proofRecipes
437
417
  ? { resolveSession: resolvedSession }
438
418
  : {}),
439
419
  }
440
420
  if(
441
421
  opts.signerPublicKey
442
- || opts.issuer
443
- || opts.trustedIssuer
444
- || opts.issuers?.length
445
422
  || opts.trustedIssuers?.length
446
- || opts.resolveIssuers
447
- || opts.issuerResolver
448
- || opts.issuersByVerificationClientId
449
423
  || opts.jwksUrl
450
- || opts.jwksUrlsByVerificationClientId
451
- || opts.resolveJwksUrl
452
424
  ) {
453
425
  return resolvedOpts
454
426
  }
455
427
 
456
428
  return {
457
429
  ...resolvedOpts,
458
- resolveIssuers: async(verificationClientId: string) => {
459
- const session = await registeredClient(verificationClientId)
460
- if(!session) {
461
- return []
462
- }
463
-
464
- return session.verificationClientIssuer
465
- ? [session.verificationClientIssuer]
466
- : []
467
- },
468
- resolveJwksUrl: async(verificationClientId: string) => {
469
- const session = await registeredClient(verificationClientId)
470
- return session?.verificationClientJwksUrl
471
- },
430
+ trustedIssuers: [client.baseUrl],
431
+ jwksUrl: `${client.baseUrl}/.well-known/jwks.json`,
472
432
  }
433
+ }
434
+ }
473
435
 
474
- async function registeredClient(verificationClientId: string) {
475
- if(!sessionId) {
476
- return undefined
477
- }
478
-
479
- const { data: session } = await client.call(
480
- 'GetVerificationSession',
481
- { params: { sessionId } },
482
- )
483
- if(session.verificationClientId !== verificationClientId) {
484
- return undefined
485
- }
486
-
487
- return session
488
- }
436
+ function makeSessionFlow(session: VerificationSession): SessionFlow {
437
+ return {
438
+ session,
439
+ async getUrl() {
440
+ return session.verificationUrl
441
+ },
489
442
  }
490
443
  }
491
444
 
@@ -516,7 +469,21 @@ function parseEventData(data: string | undefined): Record<string, unknown> {
516
469
  }
517
470
  }
518
471
 
519
- /** True for a `404` thrown by {@link ReclaimClient.call}. */
520
- function isNotFound(err: unknown): boolean {
521
- return isProblemError(err) && err.output.statusCode === 404
472
+ function parseReclaimVerificationState(
473
+ serialized: string,
474
+ ): ReclaimVerificationState {
475
+ const state: unknown = JSON.parse(serialized)
476
+ if(
477
+ !state
478
+ || typeof state !== 'object'
479
+ || Array.isArray(state)
480
+ || !('version' in state)
481
+ || state.version !== 1
482
+ || !('baseUrl' in state)
483
+ || typeof state.baseUrl !== 'string'
484
+ ) {
485
+ throw new Error('Invalid ReclaimVerification state')
486
+ }
487
+
488
+ return { version: 1, baseUrl: state.baseUrl }
522
489
  }