@reclaimprotocol/client 0.1.27 → 0.1.29
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.
- package/README.md +634 -42
- package/lib/api-client.d.ts +31 -0
- package/lib/api-client.js +112 -0
- package/lib/api-consts.d.ts +4 -0
- package/lib/api-consts.js +4 -0
- package/lib/api-errors.d.ts +13 -0
- package/lib/api-errors.js +30 -0
- package/lib/api.d.ts +7 -0
- package/lib/api.js +3 -0
- package/lib/attestor-proof.d.ts +6 -0
- package/lib/attestor-proof.js +45 -18
- package/lib/client.d.ts +1 -6
- package/lib/client.js +12 -33
- package/lib/consts.d.ts +1 -1
- package/lib/consts.js +1 -1
- package/lib/eip191.d.ts +4 -2
- package/lib/eip191.js +17 -1
- package/lib/errors.d.ts +38 -4
- package/lib/errors.js +99 -16
- package/lib/index.d.ts +9 -7
- package/lib/index.js +8 -4
- package/lib/jwks.d.ts +25 -1
- package/lib/jwks.js +69 -4
- package/lib/legacy-proof.d.ts +57 -0
- package/lib/legacy-proof.js +211 -0
- package/lib/proof-hash.d.ts +35 -0
- package/lib/proof-hash.js +216 -0
- package/lib/proof-recipe.d.ts +18 -0
- package/lib/proof-recipe.js +37 -0
- package/lib/proof-trust-constants.d.ts +3 -0
- package/lib/proof-trust-constants.js +43 -0
- package/lib/proof-types.d.ts +62 -0
- package/lib/proof-types.js +1 -0
- package/lib/proof-verification-error.d.ts +13 -0
- package/lib/proof-verification-error.js +11 -0
- package/lib/reclaim.d.ts +27 -5
- package/lib/reclaim.js +140 -14
- package/lib/result.d.ts +58 -6
- package/lib/result.js +252 -61
- package/lib/routes.gen.d.ts +54 -6
- package/lib/routes.gen.js +15 -3
- package/lib/tee-binding.d.ts +82 -0
- package/lib/tee-binding.js +112 -0
- package/lib/types/openapi.gen.d.ts +1343 -206
- package/lib/verification-clients.d.ts +17 -0
- package/lib/verification-clients.js +14 -0
- package/lib/verify-attestor-tee.d.ts +76 -0
- package/lib/verify-attestor-tee.js +243 -0
- package/lib/verify-tee.d.ts +32 -0
- package/lib/verify-tee.js +347 -0
- package/package.json +5 -1
- package/src/api.ts +15 -0
- package/src/attestor-proof.ts +57 -20
- package/src/client.ts +15 -38
- package/src/consts.ts +1 -2
- package/src/eip191.ts +31 -3
- package/src/errors.ts +132 -18
- package/src/index.ts +50 -5
- package/src/jwks.ts +128 -5
- package/src/legacy-proof.ts +367 -0
- package/src/proof-hash.ts +316 -0
- package/src/proof-recipe.ts +75 -0
- package/src/proof-trust-constants.ts +45 -0
- package/src/proof-types.ts +57 -0
- package/src/proof-verification-error.ts +13 -0
- package/src/reclaim.ts +206 -19
- package/src/result.ts +403 -68
- package/src/routes.gen.ts +15 -3
- package/src/tee-binding.ts +192 -0
- package/src/types/openapi.gen.ts +1343 -206
- package/src/verification-clients.ts +22 -0
- package/src/verify-attestor-tee.ts +365 -0
- package/src/verify-tee.ts +507 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { components } from './types/openapi.gen.ts'
|
|
2
|
+
import {
|
|
3
|
+
hashRequirementsFromSpecs,
|
|
4
|
+
type ProofRequestSpec,
|
|
5
|
+
} from './proof-hash.ts'
|
|
6
|
+
import type { LegacyProof } from './proof-types.ts'
|
|
7
|
+
|
|
8
|
+
type RequestSelection = components['schemas']['RequestSelection']
|
|
9
|
+
type RequestSelectionTemplate =
|
|
10
|
+
components['schemas']['RequestSelectionTemplate']
|
|
11
|
+
|
|
12
|
+
/** Resolved Builder provider recipe returned for a verification session. */
|
|
13
|
+
export interface BuilderProofRecipe {
|
|
14
|
+
providerId: string
|
|
15
|
+
resolvedVersion: string
|
|
16
|
+
initialUrl: string
|
|
17
|
+
requests: RequestSelection[]
|
|
18
|
+
allowedJsRequests?: RequestSelectionTemplate[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Derive the exact hash requirements consumed by legacy `verifyProof` from a
|
|
23
|
+
* resolved Builder recipe. Builder's fixed requests are required once each.
|
|
24
|
+
*/
|
|
25
|
+
export function proofHashesFromBuilderRecipe(
|
|
26
|
+
recipe: BuilderProofRecipe,
|
|
27
|
+
proofs: LegacyProof[],
|
|
28
|
+
) {
|
|
29
|
+
return hashRequirementsFromSpecs(
|
|
30
|
+
recipe.requests.map((request) => toRequestSpec(
|
|
31
|
+
request,
|
|
32
|
+
{ required: true, multiple: false },
|
|
33
|
+
)),
|
|
34
|
+
(recipe.allowedJsRequests ?? []).map(
|
|
35
|
+
(request) => toRequestSpec(request, {
|
|
36
|
+
required: request.required,
|
|
37
|
+
multiple: request.multiple,
|
|
38
|
+
templateParams: request.templateParams,
|
|
39
|
+
templateParamsMode: request.templateParamsMode,
|
|
40
|
+
}),
|
|
41
|
+
),
|
|
42
|
+
proofs,
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function toRequestSpec(
|
|
47
|
+
request: RequestSelection | RequestSelectionTemplate,
|
|
48
|
+
flags: Pick<
|
|
49
|
+
ProofRequestSpec,
|
|
50
|
+
'required' | 'multiple' | 'templateParams' | 'templateParamsMode'
|
|
51
|
+
>,
|
|
52
|
+
): ProofRequestSpec {
|
|
53
|
+
return {
|
|
54
|
+
url: request.url,
|
|
55
|
+
urlType: request.url.includes('{{') ? 'TEMPLATE' : 'CONSTANT',
|
|
56
|
+
method: request.method,
|
|
57
|
+
bodySniff: {
|
|
58
|
+
enabled: request.requestBodyTemplate !== undefined,
|
|
59
|
+
template: request.requestBodyTemplate ?? '',
|
|
60
|
+
},
|
|
61
|
+
responseMatches: (request.responseMatches ?? []).map((match) => ({
|
|
62
|
+
type: match.type,
|
|
63
|
+
value: match.value,
|
|
64
|
+
invert: undefined,
|
|
65
|
+
isOptional: match.isOptional,
|
|
66
|
+
})),
|
|
67
|
+
responseRedactions: (request.responseRedactions ?? []).map((redaction) => ({
|
|
68
|
+
...(redaction.hash ? { hash: redaction.hash } : {}),
|
|
69
|
+
jsonPath: redaction.jsonPath ?? '',
|
|
70
|
+
regex: redaction.regex ?? '',
|
|
71
|
+
xPath: redaction.xPath ?? '',
|
|
72
|
+
})),
|
|
73
|
+
...flags,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/* eslint-disable @stylistic/max-len */
|
|
2
|
+
// GCP Confidential Space Root CA — pinned. Rotates infrequently
|
|
3
|
+
// (current cert valid 2024-2034). Used to verify attestor TEE attestation JWTs.
|
|
4
|
+
export const GCP_CONFIDENTIAL_SPACE_ROOT_CA = `-----BEGIN CERTIFICATE-----
|
|
5
|
+
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
|
|
37
|
+
uyT5CnJulxSC5CT1
|
|
38
|
+
-----END CERTIFICATE-----`
|
|
39
|
+
|
|
40
|
+
// Matches an `attestor_public_key:0x<40-hex>` entry in a GCP Confidential
|
|
41
|
+
// Space attestation JWT's `eat_nonce` claim.
|
|
42
|
+
export const ATTESTOR_NONCE_PATTERN = /^attestor_public_key:0x([0-9a-fA-F]{40})$/
|
|
43
|
+
|
|
44
|
+
// Expected `iss` claim on GCP Confidential Space attestation JWTs.
|
|
45
|
+
export const GCP_CONFIDENTIAL_SPACE_ISSUER = 'https://confidentialcomputing.googleapis.com'
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/** Exact legacy proof structures carried inside Builder result envelopes. */
|
|
2
|
+
export interface LegacyProof {
|
|
3
|
+
identifier: string
|
|
4
|
+
claimData: LegacyClaimData
|
|
5
|
+
signatures: string[]
|
|
6
|
+
witnesses: LegacyWitness[]
|
|
7
|
+
extractedParameterValues: unknown
|
|
8
|
+
publicData?: unknown
|
|
9
|
+
taskId?: number
|
|
10
|
+
teeAttestation?: TeeAttestation | string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface LegacyClaimData {
|
|
14
|
+
provider: string
|
|
15
|
+
parameters: string
|
|
16
|
+
owner: string
|
|
17
|
+
timestampS: number
|
|
18
|
+
context: string
|
|
19
|
+
identifier: string
|
|
20
|
+
epoch: number
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface LegacyWitness {
|
|
24
|
+
id: string
|
|
25
|
+
url: string
|
|
26
|
+
claimAttestation?: AttestorClaimAttestation
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface AttestorClaimAttestation {
|
|
30
|
+
attestor_address: string
|
|
31
|
+
claim_signature: string
|
|
32
|
+
attestation_report: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface TeeAttestation {
|
|
36
|
+
proof_version?: 'v2' | 'v3'
|
|
37
|
+
proofVersion?: 'v2' | 'v3'
|
|
38
|
+
tee_provider: string
|
|
39
|
+
tee_technology: string
|
|
40
|
+
nonce: string
|
|
41
|
+
timestamp: string
|
|
42
|
+
workload: { container_name?: string, image_digest: string }
|
|
43
|
+
verifier: { container_name?: string, image_digest: string }
|
|
44
|
+
attestation: { token: string }
|
|
45
|
+
error?: { code: string, message: string }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface TrustedData {
|
|
49
|
+
context: Record<string, unknown>
|
|
50
|
+
extractedParameters: Record<string, string>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface HashRequirement {
|
|
54
|
+
value: string | string[]
|
|
55
|
+
required?: boolean
|
|
56
|
+
multiple?: boolean
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function legacyErrorClass(name: string) {
|
|
2
|
+
return class extends Error {
|
|
3
|
+
readonly innerError?: unknown
|
|
4
|
+
|
|
5
|
+
constructor(message?: string, innerError?: unknown) {
|
|
6
|
+
super(innerError
|
|
7
|
+
? `${message ?? ''} caused by ${String(innerError)}`
|
|
8
|
+
: message)
|
|
9
|
+
this.name = name
|
|
10
|
+
this.innerError = innerError
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/reclaim.ts
CHANGED
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
type DecryptionKeyInput,
|
|
29
29
|
loadDecryptionKey,
|
|
30
30
|
} from './enckeys.ts'
|
|
31
|
+
import { isProblemError } from './errors.ts'
|
|
32
|
+
import type { BuilderProofRecipe } from './proof-recipe.ts'
|
|
33
|
+
import { proofHashesFromBuilderRecipe } from './proof-recipe.ts'
|
|
34
|
+
import type { LegacyProof } from './proof-types.ts'
|
|
31
35
|
import {
|
|
32
36
|
type CallbackBody,
|
|
33
37
|
decryptCallback,
|
|
@@ -36,6 +40,10 @@ import {
|
|
|
36
40
|
type VerifyResultFullOptions,
|
|
37
41
|
type VerifyResultFullOutcome,
|
|
38
42
|
} from './result.ts'
|
|
43
|
+
import {
|
|
44
|
+
createBuilderTeeBinding,
|
|
45
|
+
type LegacyTeeNonceMode,
|
|
46
|
+
} from './tee-binding.ts'
|
|
39
47
|
|
|
40
48
|
type CallbackSubscription = components['schemas']['CallbackSubscription']
|
|
41
49
|
type VerificationSession = components['schemas']['VerificationSession']
|
|
@@ -44,11 +52,19 @@ type VerificationEventRecord = components['schemas']['VerificationEventRecord']
|
|
|
44
52
|
type OrgKeypair = components['schemas']['OrgKeypair']
|
|
45
53
|
type OrgKeypairRequest = components['schemas']['OrgKeypairRequest']
|
|
46
54
|
|
|
47
|
-
/**
|
|
55
|
+
/** Terminal lifecycle events selected by default for callback subscriptions. */
|
|
48
56
|
export const TERMINAL_EVENTS = [
|
|
49
57
|
'verification_success',
|
|
50
58
|
'verification_rejected',
|
|
51
59
|
'verification_error',
|
|
60
|
+
'verification_cancelled',
|
|
61
|
+
'session_expired',
|
|
62
|
+
] as const
|
|
63
|
+
|
|
64
|
+
const SIGNED_RESULT_EVENTS = [
|
|
65
|
+
'verification_success',
|
|
66
|
+
'verification_rejected',
|
|
67
|
+
'verification_error',
|
|
52
68
|
] as const
|
|
53
69
|
|
|
54
70
|
export interface RegisterCallbackInput {
|
|
@@ -61,8 +77,25 @@ export interface CreateSessionInput {
|
|
|
61
77
|
* session) and one combined result is delivered; all are required. */
|
|
62
78
|
providers: { providerId: string, version?: string }[]
|
|
63
79
|
context: Record<string, unknown>
|
|
64
|
-
/**
|
|
80
|
+
/** Exact registered Verification Client base URL. It may contain a
|
|
81
|
+
* `{sessionId}` placeholder; Builder otherwise adds the session id as a
|
|
82
|
+
* query parameter. Open the returned `verificationUrl`, not this base
|
|
83
|
+
* URL. */
|
|
65
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.
|
|
87
|
+
* Session creation returns only after Builder stores the signed binding. */
|
|
88
|
+
teeAttestation?: CreateSessionTeeAttestation
|
|
89
|
+
}
|
|
90
|
+
|
|
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
|
|
95
|
+
timestamp?: string
|
|
96
|
+
/** `hash` is the default. Use `signature` only for an older Verification
|
|
97
|
+
* Client that expects the 65-byte EIP-191 nonce. */
|
|
98
|
+
nonceMode?: LegacyTeeNonceMode
|
|
66
99
|
}
|
|
67
100
|
|
|
68
101
|
/** Input for `keypair.set`: the org's eth public key as uncompressed hex
|
|
@@ -105,6 +138,14 @@ export interface Reclaim {
|
|
|
105
138
|
input: Omit<CreateSessionInput, 'verificationClientUrl'> &
|
|
106
139
|
Partial<Pick<CreateSessionInput, 'verificationClientUrl'>>,
|
|
107
140
|
): Promise<VerificationSession>
|
|
141
|
+
/** Add the immutable legacy-compatible TEE context after session
|
|
142
|
+
* creation. Normally use
|
|
143
|
+
* `sessions.create({ teeAttestation: { appSecret } })`; this method
|
|
144
|
+
* supports retrying or explicitly orchestrating the two-step flow. */
|
|
145
|
+
bindTeeNonce(
|
|
146
|
+
sessionId: string,
|
|
147
|
+
input: CreateSessionTeeAttestation,
|
|
148
|
+
): Promise<VerificationSession>
|
|
108
149
|
get(sessionId: string): Promise<VerificationSession>
|
|
109
150
|
listEvents(sessionId: string): Promise<VerificationEventRecord[]>
|
|
110
151
|
}
|
|
@@ -112,7 +153,7 @@ export interface Reclaim {
|
|
|
112
153
|
/** Register a callback subscription (one-time setup). Delivery is scoped
|
|
113
154
|
* to the token's org; results are ECIES-encrypted to the org's eth key
|
|
114
155
|
* when `canEncryptResult` is enabled (see `keypair.set`), else plaintext.
|
|
115
|
-
* `events` defaults to all
|
|
156
|
+
* `events` defaults to all terminal lifecycle events. */
|
|
116
157
|
register(
|
|
117
158
|
input: Partial<RegisterCallbackInput> &
|
|
118
159
|
Pick<RegisterCallbackInput, 'callbackUrl'>,
|
|
@@ -135,7 +176,7 @@ export interface Reclaim {
|
|
|
135
176
|
* the delivery is encrypted. */
|
|
136
177
|
decrypt(body: CallbackBody, credential?: DecryptionKeyInput): Promise<Jws>
|
|
137
178
|
/** Verify a decrypted JWS: JWKS discovery, ES256K signature,
|
|
138
|
-
* `reclaimSessionId`/`aud
|
|
179
|
+
* `reclaimSessionId`/`aud`, any legacy `exp`, and exact legacy proofs. */
|
|
139
180
|
verify(
|
|
140
181
|
jws: Jws,
|
|
141
182
|
opts?: VerifyResultFullOptions,
|
|
@@ -190,15 +231,25 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
|
|
|
190
231
|
api: client.call.bind(client),
|
|
191
232
|
sessions: {
|
|
192
233
|
async create(input) {
|
|
193
|
-
const
|
|
194
|
-
|
|
234
|
+
const { teeAttestation, ...request } = input
|
|
235
|
+
const body:
|
|
236
|
+
components['schemas']['CreateVerificationSessionRequest'] = {
|
|
237
|
+
...request,
|
|
195
238
|
verificationClientUrl:
|
|
196
|
-
|
|
239
|
+
request.verificationClientUrl ?? `${client.baseUrl}/v/{sessionId}`,
|
|
197
240
|
}
|
|
198
241
|
const { data } = await client.call('CreateVerificationSession', {
|
|
199
242
|
body,
|
|
200
243
|
})
|
|
201
|
-
return
|
|
244
|
+
return teeAttestation
|
|
245
|
+
? bindTeeNonce(data, teeAttestation)
|
|
246
|
+
: data
|
|
247
|
+
},
|
|
248
|
+
async bindTeeNonce(sessionId, input) {
|
|
249
|
+
const { data } = await client.call('GetVerificationSession', {
|
|
250
|
+
params: { sessionId },
|
|
251
|
+
})
|
|
252
|
+
return bindTeeNonce(data, input)
|
|
202
253
|
},
|
|
203
254
|
async get(sessionId) {
|
|
204
255
|
const { data } = await client.call('GetVerificationSession', {
|
|
@@ -269,7 +320,10 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
|
|
|
269
320
|
return decryptCallback(resolveKey(credential), body)
|
|
270
321
|
},
|
|
271
322
|
verify(jws, opts) {
|
|
272
|
-
return verifyResultFull(jws, {
|
|
323
|
+
return verifyResultFull(jws, {
|
|
324
|
+
fetchImpl: config.fetch,
|
|
325
|
+
...withRegisteredIssuer(opts),
|
|
326
|
+
})
|
|
273
327
|
},
|
|
274
328
|
async receive(body, opts) {
|
|
275
329
|
// A delivery envelope tagged with a non-terminal event carries a
|
|
@@ -283,7 +337,7 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
|
|
|
283
337
|
const env = body
|
|
284
338
|
if(
|
|
285
339
|
env.event
|
|
286
|
-
&& !(
|
|
340
|
+
&& !(SIGNED_RESULT_EVENTS as readonly string[]).includes(env.event)
|
|
287
341
|
) {
|
|
288
342
|
const decoded = parseEventData(env.data)
|
|
289
343
|
return {
|
|
@@ -299,12 +353,151 @@ export function createReclaim(config: ReclaimConfig): Reclaim {
|
|
|
299
353
|
const jws = await decryptCallback(key, body)
|
|
300
354
|
const result = await verifyResultFull(jws, {
|
|
301
355
|
fetchImpl: config.fetch,
|
|
302
|
-
...verifyOpts,
|
|
356
|
+
...withRegisteredIssuer(verifyOpts),
|
|
303
357
|
})
|
|
304
358
|
return { kind: 'result', result }
|
|
305
359
|
},
|
|
306
360
|
},
|
|
307
361
|
}
|
|
362
|
+
|
|
363
|
+
async function bindTeeNonce(
|
|
364
|
+
session: VerificationSession,
|
|
365
|
+
input: CreateSessionTeeAttestation,
|
|
366
|
+
) {
|
|
367
|
+
const binding = createBuilderTeeBinding({
|
|
368
|
+
organizationId: session.orgId,
|
|
369
|
+
organizationVerificationPrivateKey: input.appSecret,
|
|
370
|
+
sessionId: session.id,
|
|
371
|
+
timestamp: input.timestamp ?? Date.now().toString(),
|
|
372
|
+
nonceMode: input.nonceMode,
|
|
373
|
+
})
|
|
374
|
+
const { data } = await client.call('BindVerificationSessionTeeNonce', {
|
|
375
|
+
params: { sessionId: session.id },
|
|
376
|
+
body: {
|
|
377
|
+
attestationNonce: binding.attestationNonce,
|
|
378
|
+
attestationNonceData: binding.attestationNonceData,
|
|
379
|
+
signature: binding.signature,
|
|
380
|
+
},
|
|
381
|
+
})
|
|
382
|
+
return data
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function resolvedSession(id: string) {
|
|
386
|
+
const { data } = await client.call(
|
|
387
|
+
'GetVerificationRecipe',
|
|
388
|
+
{ params: { sessionId: id } },
|
|
389
|
+
)
|
|
390
|
+
return { recipes: data.recipes satisfies BuilderProofRecipe[] }
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function resolvedProvider(
|
|
394
|
+
providerId: string,
|
|
395
|
+
providerVersion: string | undefined,
|
|
396
|
+
_allowedTags: string[] | undefined,
|
|
397
|
+
proofs: LegacyProof[],
|
|
398
|
+
) {
|
|
399
|
+
let exactVersion = providerVersion
|
|
400
|
+
if(!exactVersion) {
|
|
401
|
+
const { data: provider } = await client.call('GetProvider', {
|
|
402
|
+
params: { providerId },
|
|
403
|
+
})
|
|
404
|
+
if(!provider.latestVersion) {
|
|
405
|
+
throw new Error(`Provider ${providerId} has no live version`)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
exactVersion = canonicalVersion(provider.latestVersion)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const { data: version } = await client.call('GetProviderVersion', {
|
|
412
|
+
params: { providerId, version: exactVersion },
|
|
413
|
+
})
|
|
414
|
+
return [{
|
|
415
|
+
hashes: proofHashesFromBuilderRecipe({
|
|
416
|
+
providerId,
|
|
417
|
+
resolvedVersion: exactVersion,
|
|
418
|
+
initialUrl: version.initialUrl,
|
|
419
|
+
requests: version.requests,
|
|
420
|
+
allowedJsRequests: version.allowedJsRequests,
|
|
421
|
+
}, proofs),
|
|
422
|
+
}]
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function withRegisteredIssuer(opts: VerifyResultFullOptions = {}) {
|
|
426
|
+
const sessionId = opts.sessionId ?? opts.expectedReclaimSessionId
|
|
427
|
+
const requestedProofValidation = opts.proofValidation
|
|
428
|
+
?? opts.proofVerification
|
|
429
|
+
const resolvedOpts: VerifyResultFullOptions = {
|
|
430
|
+
...opts,
|
|
431
|
+
proofValidation: {
|
|
432
|
+
...requestedProofValidation,
|
|
433
|
+
resolveProvider: requestedProofValidation?.resolveProvider
|
|
434
|
+
?? resolvedProvider,
|
|
435
|
+
},
|
|
436
|
+
...(sessionId && !opts.resolveSession && !opts.proofRecipes
|
|
437
|
+
? { resolveSession: resolvedSession }
|
|
438
|
+
: {}),
|
|
439
|
+
}
|
|
440
|
+
if(
|
|
441
|
+
opts.signerPublicKey
|
|
442
|
+
|| opts.issuer
|
|
443
|
+
|| opts.trustedIssuer
|
|
444
|
+
|| opts.issuers?.length
|
|
445
|
+
|| opts.trustedIssuers?.length
|
|
446
|
+
|| opts.resolveIssuers
|
|
447
|
+
|| opts.issuerResolver
|
|
448
|
+
|| opts.issuersByVerificationClientId
|
|
449
|
+
|| opts.jwksUrl
|
|
450
|
+
|| opts.jwksUrlsByVerificationClientId
|
|
451
|
+
|| opts.resolveJwksUrl
|
|
452
|
+
) {
|
|
453
|
+
return resolvedOpts
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
return {
|
|
457
|
+
...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
|
+
},
|
|
472
|
+
}
|
|
473
|
+
|
|
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
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function canonicalVersion(
|
|
493
|
+
version: components['schemas']['ProviderVersionRef'],
|
|
494
|
+
) {
|
|
495
|
+
const base = `${version.major}.${version.minor}.${version.patch}`
|
|
496
|
+
return version.branch
|
|
497
|
+
? `${base}-${version.branch}${version.buildNumber === undefined
|
|
498
|
+
? ''
|
|
499
|
+
: `.${version.buildNumber}`}`
|
|
500
|
+
: base
|
|
308
501
|
}
|
|
309
502
|
|
|
310
503
|
/** Non-terminal `data` is a base64-encoded JSON `{ event, eventData }`. */
|
|
@@ -323,13 +516,7 @@ function parseEventData(data: string | undefined): Record<string, unknown> {
|
|
|
323
516
|
}
|
|
324
517
|
}
|
|
325
518
|
|
|
326
|
-
/** True for a `404` thrown by {@link ReclaimClient.call}
|
|
327
|
-
* `output.statusCode` is 404 (see ./errors.ts). */
|
|
519
|
+
/** True for a `404` thrown by {@link ReclaimClient.call}. */
|
|
328
520
|
function isNotFound(err: unknown): boolean {
|
|
329
|
-
|
|
330
|
-
return false
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
const boom = err as { output?: { statusCode?: number }, statusCode?: number }
|
|
334
|
-
return boom.output?.statusCode === 404 || boom.statusCode === 404
|
|
521
|
+
return isProblemError(err) && err.output.statusCode === 404
|
|
335
522
|
}
|