@sphereon/ssi-sdk.siopv2-oid4vp-rp-auth 0.34.1-next.3 → 0.34.1-next.322
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/dist/index.cjs +481 -432
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +739 -69
- package/dist/index.d.ts +739 -69
- package/dist/index.js +472 -424
- package/dist/index.js.map +1 -1
- package/package.json +19 -19
- package/src/RPInstance.ts +14 -29
- package/src/agent/SIOPv2RP.ts +138 -67
- package/src/functions.ts +49 -52
- package/src/index.ts +1 -1
- package/src/types/ISIOPv2RP.ts +32 -62
package/src/agent/SIOPv2RP.ts
CHANGED
|
@@ -3,10 +3,15 @@ import {
|
|
|
3
3
|
AuthorizationResponsePayload,
|
|
4
4
|
AuthorizationResponseState,
|
|
5
5
|
AuthorizationResponseStateStatus,
|
|
6
|
+
AuthorizationResponseStateWithVerifiedData,
|
|
6
7
|
decodeUriAsJson,
|
|
8
|
+
EncodedDcqlPresentationVpToken,
|
|
7
9
|
VerifiedAuthorizationResponse,
|
|
8
10
|
} from '@sphereon/did-auth-siop'
|
|
9
11
|
import { getAgentResolver } from '@sphereon/ssi-sdk-ext.did-utils'
|
|
12
|
+
import { shaHasher as defaultHasher } from '@sphereon/ssi-sdk.core'
|
|
13
|
+
import { validate as isValidUUID } from 'uuid'
|
|
14
|
+
import type { ImportDcqlQueryItem } from '@sphereon/ssi-sdk.pd-manager'
|
|
10
15
|
import {
|
|
11
16
|
AdditionalClaims,
|
|
12
17
|
CredentialMapper,
|
|
@@ -22,8 +27,8 @@ import {
|
|
|
22
27
|
SdJwtDecodedVerifiableCredential,
|
|
23
28
|
} from '@sphereon/ssi-types'
|
|
24
29
|
import { IAgentPlugin } from '@veramo/core'
|
|
30
|
+
import { DcqlQuery } from 'dcql'
|
|
25
31
|
import {
|
|
26
|
-
AuthorizationResponseStateWithVerifiedData,
|
|
27
32
|
IAuthorizationRequestPayloads,
|
|
28
33
|
ICreateAuthRequestArgs,
|
|
29
34
|
IGetAuthRequestStateArgs,
|
|
@@ -39,13 +44,9 @@ import {
|
|
|
39
44
|
IUpdateRequestStateArgs,
|
|
40
45
|
IVerifyAuthResponseStateArgs,
|
|
41
46
|
schema,
|
|
42
|
-
VerifiedDataMode,
|
|
43
47
|
} from '../index'
|
|
44
48
|
import { RPInstance } from '../RPInstance'
|
|
45
|
-
|
|
46
49
|
import { ISIOPv2RP } from '../types/ISIOPv2RP'
|
|
47
|
-
import { shaHasher as defaultHasher } from '@sphereon/ssi-sdk.core'
|
|
48
|
-
import { DcqlQuery } from 'dcql'
|
|
49
50
|
|
|
50
51
|
export class SIOPv2RP implements IAgentPlugin {
|
|
51
52
|
private readonly opts: ISiopv2RPOpts
|
|
@@ -85,7 +86,14 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
private async createAuthorizationRequestURI(createArgs: ICreateAuthRequestArgs, context: IRequiredContext): Promise<string> {
|
|
88
|
-
return await this.getRPInstance(
|
|
89
|
+
return await this.getRPInstance(
|
|
90
|
+
{
|
|
91
|
+
createWhenNotPresent: true,
|
|
92
|
+
responseRedirectURI: createArgs.responseRedirectURI,
|
|
93
|
+
...(createArgs.useQueryIdInstance === true && { queryId: createArgs.queryId }),
|
|
94
|
+
},
|
|
95
|
+
context,
|
|
96
|
+
)
|
|
89
97
|
.then((rp) => rp.createAuthorizationRequestURI(createArgs, context))
|
|
90
98
|
.then((URI) => URI.encodedUri)
|
|
91
99
|
}
|
|
@@ -94,20 +102,20 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
94
102
|
createArgs: ICreateAuthRequestArgs,
|
|
95
103
|
context: IRequiredContext,
|
|
96
104
|
): Promise<IAuthorizationRequestPayloads> {
|
|
97
|
-
return await this.getRPInstance({
|
|
105
|
+
return await this.getRPInstance({ createWhenNotPresent: true, queryId: createArgs.queryId }, context)
|
|
98
106
|
.then((rp) => rp.createAuthorizationRequest(createArgs, context))
|
|
99
107
|
.then(async (request) => {
|
|
100
108
|
const authRequest: IAuthorizationRequestPayloads = {
|
|
101
109
|
authorizationRequest: request.payload,
|
|
102
110
|
requestObject: await request.requestObjectJwt(),
|
|
103
|
-
requestObjectDecoded:
|
|
111
|
+
requestObjectDecoded: request.requestObject?.getPayload(),
|
|
104
112
|
}
|
|
105
113
|
return authRequest
|
|
106
114
|
})
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
private async siopGetRequestState(args: IGetAuthRequestStateArgs, context: IRequiredContext): Promise<AuthorizationRequestState | undefined> {
|
|
110
|
-
return await this.getRPInstance({
|
|
118
|
+
return await this.getRPInstance({ createWhenNotPresent: false, queryId: args.queryId }, context).then((rp) =>
|
|
111
119
|
rp.get(context).then((rp) => rp.sessionManager.getRequestStateByCorrelationId(args.correlationId, args.errorOnNotFound)),
|
|
112
120
|
)
|
|
113
121
|
}
|
|
@@ -116,7 +124,7 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
116
124
|
args: IGetAuthResponseStateArgs,
|
|
117
125
|
context: IRequiredContext,
|
|
118
126
|
): Promise<AuthorizationResponseStateWithVerifiedData | undefined> {
|
|
119
|
-
const rpInstance: RPInstance = await this.getRPInstance({
|
|
127
|
+
const rpInstance: RPInstance = await this.getRPInstance({ createWhenNotPresent: false, queryId: args.queryId }, context)
|
|
120
128
|
const authorizationResponseState: AuthorizationResponseState | undefined = await rpInstance
|
|
121
129
|
.get(context)
|
|
122
130
|
.then((rp) => rp.sessionManager.getResponseStateByCorrelationId(args.correlationId, args.errorOnNotFound))
|
|
@@ -125,11 +133,7 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
125
133
|
}
|
|
126
134
|
|
|
127
135
|
const responseState = authorizationResponseState as AuthorizationResponseStateWithVerifiedData
|
|
128
|
-
if (
|
|
129
|
-
responseState.status === AuthorizationResponseStateStatus.VERIFIED &&
|
|
130
|
-
args.includeVerifiedData &&
|
|
131
|
-
args.includeVerifiedData !== VerifiedDataMode.NONE
|
|
132
|
-
) {
|
|
136
|
+
if (responseState.status === AuthorizationResponseStateStatus.VERIFIED) {
|
|
133
137
|
let hasher: HasherSync | undefined
|
|
134
138
|
if (
|
|
135
139
|
CredentialMapper.isSdJwtEncoded(responseState.response.payload.vp_token as OriginalVerifiablePresentation) &&
|
|
@@ -137,19 +141,36 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
137
141
|
) {
|
|
138
142
|
hasher = defaultHasher
|
|
139
143
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
144
|
+
|
|
145
|
+
// FIXME SSISDK-64 currently assuming that all vp tokens are or type EncodedDcqlPresentationVpToken as we only work with DCQL now. But the types still indicate it can be another type of vp token
|
|
146
|
+
const vpToken = responseState.response.payload.vp_token && JSON.parse(responseState.response.payload.vp_token as EncodedDcqlPresentationVpToken)
|
|
147
|
+
const claims = []
|
|
148
|
+
for (const [credentialQueryId, presentationValue] of Object.entries(vpToken)) {
|
|
149
|
+
let singleVP: OriginalVerifiablePresentation
|
|
150
|
+
if (Array.isArray(presentationValue)) {
|
|
151
|
+
if (presentationValue.length === 0) {
|
|
152
|
+
throw Error(`DCQL query '${credentialQueryId}' has empty array of presentations`)
|
|
153
|
+
}
|
|
154
|
+
if (presentationValue.length > 1) {
|
|
155
|
+
throw Error(`DCQL query '${credentialQueryId}' has multiple presentations (${presentationValue.length}), but only one is supported atm`)
|
|
156
|
+
}
|
|
157
|
+
singleVP = presentationValue[0] as OriginalVerifiablePresentation
|
|
158
|
+
} else {
|
|
159
|
+
singleVP = presentationValue as OriginalVerifiablePresentation
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// todo this should also include mdl-mdoc
|
|
163
|
+
const presentationDecoded = CredentialMapper.decodeVerifiablePresentation(
|
|
164
|
+
singleVP as OriginalVerifiablePresentation,
|
|
165
|
+
//todo: later we want to conditionally pass in options for mdl-mdoc here
|
|
166
|
+
hasher,
|
|
167
|
+
)
|
|
168
|
+
console.log(`presentationDecoded: ${JSON.stringify(presentationDecoded)}`)
|
|
169
|
+
|
|
170
|
+
const allClaims: AdditionalClaims = {}
|
|
171
|
+
const presentationOrClaims = this.presentationOrClaimsFrom(presentationDecoded)
|
|
172
|
+
if ('verifiableCredential' in presentationOrClaims) {
|
|
173
|
+
for (const credential of presentationOrClaims.verifiableCredential) {
|
|
153
174
|
const vc = credential as IVerifiableCredential
|
|
154
175
|
const schemaValidationResult = await context.agent.cvVerifySchema({
|
|
155
176
|
credential,
|
|
@@ -172,11 +193,35 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
172
193
|
allClaims[key] = value
|
|
173
194
|
}
|
|
174
195
|
})
|
|
196
|
+
|
|
197
|
+
claims.push({
|
|
198
|
+
id: credentialQueryId,
|
|
199
|
+
type: vc.type[0],
|
|
200
|
+
claims: allClaims,
|
|
201
|
+
})
|
|
175
202
|
}
|
|
176
|
-
|
|
177
|
-
|
|
203
|
+
} else {
|
|
204
|
+
claims.push({
|
|
205
|
+
id: credentialQueryId,
|
|
206
|
+
type: (presentationDecoded as SdJwtDecodedVerifiableCredential).decodedPayload.vct,
|
|
207
|
+
claims: presentationOrClaims,
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
responseState.verifiedData = {
|
|
213
|
+
...(responseState.response.payload.vp_token && {
|
|
214
|
+
authorization_response: {
|
|
215
|
+
vp_token:
|
|
216
|
+
typeof responseState.response.payload.vp_token === 'string'
|
|
217
|
+
? JSON.parse(responseState.response.payload.vp_token)
|
|
218
|
+
: responseState.response.payload.vp_token,
|
|
219
|
+
},
|
|
220
|
+
}),
|
|
221
|
+
...(claims.length > 0 && { credential_claims: claims }),
|
|
178
222
|
}
|
|
179
223
|
}
|
|
224
|
+
|
|
180
225
|
return responseState
|
|
181
226
|
}
|
|
182
227
|
|
|
@@ -187,16 +232,17 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
187
232
|
| SdJwtDecodedVerifiableCredential
|
|
188
233
|
| MdocOid4vpMdocVpToken
|
|
189
234
|
| MdocDeviceResponse,
|
|
190
|
-
): AdditionalClaims | IPresentation =>
|
|
191
|
-
CredentialMapper.isSdJwtDecodedCredential(presentationDecoded)
|
|
235
|
+
): AdditionalClaims | IPresentation => {
|
|
236
|
+
return CredentialMapper.isSdJwtDecodedCredential(presentationDecoded)
|
|
192
237
|
? presentationDecoded.decodedPayload
|
|
193
238
|
: CredentialMapper.toUniformPresentation(presentationDecoded as OriginalVerifiablePresentation)
|
|
239
|
+
}
|
|
194
240
|
|
|
195
241
|
private async siopUpdateRequestState(args: IUpdateRequestStateArgs, context: IRequiredContext): Promise<AuthorizationRequestState> {
|
|
196
|
-
if (args.state !== '
|
|
197
|
-
throw Error(`Only '
|
|
242
|
+
if (args.state !== 'authorization_request_created') {
|
|
243
|
+
throw Error(`Only 'authorization_request_created' status is supported for this method at this point`)
|
|
198
244
|
}
|
|
199
|
-
return await this.getRPInstance({
|
|
245
|
+
return await this.getRPInstance({ createWhenNotPresent: false, queryId: args.queryId }, context)
|
|
200
246
|
// todo: In the SIOP library we need to update the signal method to be more like this method
|
|
201
247
|
.then((rp) =>
|
|
202
248
|
rp.get(context).then(async (rp) => {
|
|
@@ -210,7 +256,7 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
210
256
|
}
|
|
211
257
|
|
|
212
258
|
private async siopDeleteState(args: IGetAuthResponseStateArgs, context: IRequiredContext): Promise<boolean> {
|
|
213
|
-
return await this.getRPInstance({
|
|
259
|
+
return await this.getRPInstance({ createWhenNotPresent: false, queryId: args.queryId }, context)
|
|
214
260
|
.then((rp) => rp.get(context).then((rp) => rp.sessionManager.deleteStateForCorrelationId(args.correlationId)))
|
|
215
261
|
.then(() => true)
|
|
216
262
|
}
|
|
@@ -223,12 +269,11 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
223
269
|
typeof args.authorizationResponse === 'string'
|
|
224
270
|
? (decodeUriAsJson(args.authorizationResponse) as AuthorizationResponsePayload)
|
|
225
271
|
: args.authorizationResponse
|
|
226
|
-
return await this.getRPInstance({
|
|
272
|
+
return await this.getRPInstance({ createWhenNotPresent: false, queryId: args.queryId }, context).then((rp) =>
|
|
227
273
|
rp.get(context).then((rp) =>
|
|
228
274
|
rp.verifyAuthorizationResponse(authResponse, {
|
|
229
275
|
correlationId: args.correlationId,
|
|
230
|
-
...(args.
|
|
231
|
-
...(args.dcqlQuery ? { dcqlQuery: args.dcqlQuery as DcqlQuery } : {}), // TODO BEFORE PR, check compatibility and whether we can remove local type
|
|
276
|
+
...(args.dcqlQuery && { dcqlQuery: args.dcqlQuery }),
|
|
232
277
|
audience: args.audience,
|
|
233
278
|
}),
|
|
234
279
|
),
|
|
@@ -236,19 +281,18 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
236
281
|
}
|
|
237
282
|
|
|
238
283
|
private async siopImportDefinitions(args: ImportDefinitionsArgs, context: IRequiredContext): Promise<void> {
|
|
239
|
-
const {
|
|
284
|
+
const { importItems, tenantId, version, versionControlMode } = args
|
|
240
285
|
await Promise.all(
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
286
|
+
importItems.map(async (importItem: ImportDcqlQueryItem) => {
|
|
287
|
+
DcqlQuery.validate(importItem.query)
|
|
288
|
+
console.log(`persisting DCQL definition ${importItem.queryId} with versionControlMode ${versionControlMode}`)
|
|
244
289
|
|
|
245
|
-
console.log(`persisting definition ${definitionPayload.id} / ${definitionPayload.name} with versionControlMode ${versionControlMode}`)
|
|
246
290
|
return context.agent.pdmPersistDefinition({
|
|
247
291
|
definitionItem: {
|
|
292
|
+
queryId: importItem.queryId!,
|
|
248
293
|
tenantId: tenantId,
|
|
249
294
|
version: version,
|
|
250
|
-
|
|
251
|
-
dcqlPayload: definitionPair.dcqlPayload,
|
|
295
|
+
query: importItem.query,
|
|
252
296
|
},
|
|
253
297
|
opts: { versionControlMode: versionControlMode },
|
|
254
298
|
})
|
|
@@ -257,12 +301,12 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
257
301
|
}
|
|
258
302
|
|
|
259
303
|
private async siopGetRedirectURI(args: IGetRedirectUriArgs, context: IRequiredContext): Promise<string | undefined> {
|
|
260
|
-
const instanceId = args.
|
|
304
|
+
const instanceId = args.queryId ?? SIOPv2RP._DEFAULT_OPTS_KEY
|
|
261
305
|
if (this.instances.has(instanceId)) {
|
|
262
306
|
const rpInstance = this.instances.get(instanceId)
|
|
263
307
|
if (rpInstance !== undefined) {
|
|
264
308
|
const rp = await rpInstance.get(context)
|
|
265
|
-
return rp.getResponseRedirectUri({
|
|
309
|
+
return await rp.getResponseRedirectUri({
|
|
266
310
|
correlation_id: args.correlationId,
|
|
267
311
|
correlationId: args.correlationId,
|
|
268
312
|
...(args.state && { state: args.state }),
|
|
@@ -272,37 +316,64 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
272
316
|
return undefined
|
|
273
317
|
}
|
|
274
318
|
|
|
275
|
-
async getRPInstance({
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
319
|
+
async getRPInstance({ createWhenNotPresent, queryId, responseRedirectURI }: ISiopRPInstanceArgs, context: IRequiredContext): Promise<RPInstance> {
|
|
320
|
+
let rpInstanceId: string = SIOPv2RP._DEFAULT_OPTS_KEY
|
|
321
|
+
let rpInstance: RPInstance | undefined
|
|
322
|
+
if (queryId) {
|
|
323
|
+
if (this.instances.has(queryId)) {
|
|
324
|
+
rpInstanceId = queryId
|
|
325
|
+
rpInstance = this.instances.get(rpInstanceId)!
|
|
326
|
+
} else if (isValidUUID(queryId)) {
|
|
327
|
+
try {
|
|
328
|
+
// Check whether queryId is actually the PD item id
|
|
329
|
+
const pd = await context.agent.pdmGetDefinition({ itemId: queryId })
|
|
330
|
+
if (this.instances.has(pd.queryId)) {
|
|
331
|
+
rpInstanceId = pd.queryId
|
|
332
|
+
rpInstance = this.instances.get(rpInstanceId)!
|
|
333
|
+
}
|
|
334
|
+
} catch (ignore) {}
|
|
335
|
+
}
|
|
336
|
+
if (createWhenNotPresent) {
|
|
337
|
+
rpInstanceId = queryId
|
|
338
|
+
} else {
|
|
339
|
+
rpInstance = this.instances.get(rpInstanceId)
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
rpInstance = this.instances.get(rpInstanceId)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (!rpInstance) {
|
|
346
|
+
if (!createWhenNotPresent) {
|
|
347
|
+
return Promise.reject(`No RP instance found for key ${rpInstanceId}`)
|
|
348
|
+
}
|
|
349
|
+
const instanceOpts = this.getInstanceOpts(queryId)
|
|
350
|
+
const rpOpts = await this.getRPOptions(context, { queryId, responseRedirectURI: responseRedirectURI })
|
|
280
351
|
if (!rpOpts.identifierOpts.resolveOpts?.resolver || typeof rpOpts.identifierOpts.resolveOpts.resolver.resolve !== 'function') {
|
|
281
352
|
if (!rpOpts.identifierOpts?.resolveOpts) {
|
|
282
353
|
rpOpts.identifierOpts = { ...rpOpts.identifierOpts }
|
|
283
354
|
rpOpts.identifierOpts.resolveOpts = { ...rpOpts.identifierOpts.resolveOpts }
|
|
284
355
|
}
|
|
285
|
-
console.log('Using agent DID resolver for RP instance with definition id ' +
|
|
356
|
+
console.log('Using agent DID resolver for RP instance with definition id ' + queryId)
|
|
286
357
|
rpOpts.identifierOpts.resolveOpts.resolver = getAgentResolver(context, {
|
|
287
358
|
uniresolverResolution: true,
|
|
288
359
|
localResolution: true,
|
|
289
360
|
resolverResolution: true,
|
|
290
361
|
})
|
|
291
362
|
}
|
|
292
|
-
|
|
363
|
+
rpInstance = new RPInstance({ rpOpts, pexOpts: instanceOpts })
|
|
364
|
+
this.instances.set(rpInstanceId, rpInstance)
|
|
293
365
|
}
|
|
294
|
-
const rpInstance = this.instances.get(instanceId)!
|
|
295
366
|
if (responseRedirectURI) {
|
|
296
367
|
rpInstance.rpOptions.responseRedirectUri = responseRedirectURI
|
|
297
368
|
}
|
|
298
369
|
return rpInstance
|
|
299
370
|
}
|
|
300
371
|
|
|
301
|
-
async getRPOptions(context: IRequiredContext, opts: {
|
|
302
|
-
const {
|
|
303
|
-
const options = this.getInstanceOpts(
|
|
372
|
+
async getRPOptions(context: IRequiredContext, opts: { queryId?: string; responseRedirectURI?: string }): Promise<IRPOptions> {
|
|
373
|
+
const { queryId, responseRedirectURI: responseRedirectURI } = opts
|
|
374
|
+
const options = this.getInstanceOpts(queryId)?.rpOpts ?? this.opts.defaultOpts
|
|
304
375
|
if (!options) {
|
|
305
|
-
throw Error(`Could not get specific nor default options for definition ${
|
|
376
|
+
throw Error(`Could not get specific nor default options for definition ${queryId}`)
|
|
306
377
|
}
|
|
307
378
|
if (this.opts.defaultOpts) {
|
|
308
379
|
if (!options.identifierOpts) {
|
|
@@ -333,22 +404,22 @@ export class SIOPv2RP implements IAgentPlugin {
|
|
|
333
404
|
return options
|
|
334
405
|
}
|
|
335
406
|
|
|
336
|
-
getInstanceOpts(
|
|
407
|
+
getInstanceOpts(queryId?: string): IPEXInstanceOptions | undefined {
|
|
337
408
|
if (!this.opts.instanceOpts) return undefined
|
|
338
409
|
|
|
339
|
-
const instanceOpt =
|
|
410
|
+
const instanceOpt = queryId ? this.opts.instanceOpts.find((i) => i.queryId === queryId) : undefined
|
|
340
411
|
|
|
341
|
-
return instanceOpt ?? this.getDefaultOptions(
|
|
412
|
+
return instanceOpt ?? this.getDefaultOptions(queryId)
|
|
342
413
|
}
|
|
343
414
|
|
|
344
|
-
private getDefaultOptions(
|
|
415
|
+
private getDefaultOptions(queryId: string | undefined) {
|
|
345
416
|
if (!this.opts.instanceOpts) return undefined
|
|
346
417
|
|
|
347
|
-
const defaultOptions = this.opts.instanceOpts.find((i) => i.
|
|
418
|
+
const defaultOptions = this.opts.instanceOpts.find((i) => i.queryId === 'default')
|
|
348
419
|
if (defaultOptions) {
|
|
349
420
|
const clonedOptions = { ...defaultOptions }
|
|
350
|
-
if (
|
|
351
|
-
clonedOptions.
|
|
421
|
+
if (queryId !== undefined) {
|
|
422
|
+
clonedOptions.queryId = queryId
|
|
352
423
|
}
|
|
353
424
|
return clonedOptions
|
|
354
425
|
}
|
package/src/functions.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
2
|
+
ClientIdentifierPrefix,
|
|
3
3
|
ClientMetadataOpts,
|
|
4
|
+
DcqlQueryLookupCallback,
|
|
4
5
|
InMemoryRPSessionManager,
|
|
5
6
|
PassBy,
|
|
6
7
|
PresentationVerificationCallback,
|
|
@@ -28,14 +29,14 @@ import {
|
|
|
28
29
|
} from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
29
30
|
import { JwtCompactResult } from '@sphereon/ssi-sdk-ext.jwt-service'
|
|
30
31
|
import { IVerifySdJwtPresentationResult } from '@sphereon/ssi-sdk.sd-jwt'
|
|
31
|
-
import { CredentialMapper,
|
|
32
|
+
import { CredentialMapper, HasherSync, OriginalVerifiableCredential, PresentationSubmission } from '@sphereon/ssi-types'
|
|
32
33
|
import { IVerifyCallbackArgs, IVerifyCredentialResult, VerifyCallback } from '@sphereon/wellknown-dids-client'
|
|
33
|
-
// import { KeyAlgo, SuppliedSigner } from '@sphereon/ssi-sdk.core'
|
|
34
34
|
import { TKeyType } from '@veramo/core'
|
|
35
35
|
import { JWTVerifyOptions } from 'did-jwt'
|
|
36
36
|
import { Resolvable } from 'did-resolver'
|
|
37
37
|
import { EventEmitter } from 'events'
|
|
38
|
-
import {
|
|
38
|
+
import { validate as isValidUUID } from 'uuid'
|
|
39
|
+
import { IRequiredContext, IRPOptions, ISIOPIdentifierOptions } from './types/ISIOPv2RP'
|
|
39
40
|
import { DcqlQuery } from 'dcql'
|
|
40
41
|
import { defaultHasher } from '@sphereon/ssi-sdk.core'
|
|
41
42
|
|
|
@@ -43,7 +44,7 @@ export function getRequestVersion(rpOptions: IRPOptions): SupportedVersion {
|
|
|
43
44
|
if (Array.isArray(rpOptions.supportedVersions) && rpOptions.supportedVersions.length > 0) {
|
|
44
45
|
return rpOptions.supportedVersions[0]
|
|
45
46
|
}
|
|
46
|
-
return SupportedVersion.
|
|
47
|
+
return SupportedVersion.OID4VP_v1
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
function getWellKnownDIDVerifyCallback(siopIdentifierOpts: ISIOPIdentifierOptions, context: IRequiredContext) {
|
|
@@ -58,6 +59,29 @@ function getWellKnownDIDVerifyCallback(siopIdentifierOpts: ISIOPIdentifierOption
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
export function getDcqlQueryLookupCallback(context: IRequiredContext): DcqlQueryLookupCallback {
|
|
63
|
+
async function dcqlQueryLookup(queryId: string, version?: string, tenantId?: string): Promise<DcqlQuery> {
|
|
64
|
+
// TODO Add caching?
|
|
65
|
+
const result = await context.agent.pdmGetDefinitions({
|
|
66
|
+
filter: [
|
|
67
|
+
{
|
|
68
|
+
queryId,
|
|
69
|
+
...(tenantId && { tenantId }),
|
|
70
|
+
...(version && { version }),
|
|
71
|
+
},
|
|
72
|
+
...(isValidUUID(queryId) ? [{ id: queryId }] : []),
|
|
73
|
+
],
|
|
74
|
+
})
|
|
75
|
+
if (result && result.length > 0) {
|
|
76
|
+
return result[0].query
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return Promise.reject(Error(`No dcql query found for queryId ${queryId}`))
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return dcqlQueryLookup
|
|
83
|
+
}
|
|
84
|
+
|
|
61
85
|
export function getPresentationVerificationCallback(
|
|
62
86
|
idOpts: ManagedIdentifierOptsOrResult,
|
|
63
87
|
context: IRequiredContext,
|
|
@@ -69,7 +93,6 @@ export function getPresentationVerificationCallback(
|
|
|
69
93
|
if (CredentialMapper.isSdJwtEncoded(args)) {
|
|
70
94
|
const result: IVerifySdJwtPresentationResult = await context.agent.verifySdJwtPresentation({
|
|
71
95
|
presentation: args,
|
|
72
|
-
kb: true,
|
|
73
96
|
})
|
|
74
97
|
// fixme: investigate the correct way to handle this
|
|
75
98
|
return { verified: !!result.payload }
|
|
@@ -103,35 +126,11 @@ export function getPresentationVerificationCallback(
|
|
|
103
126
|
|
|
104
127
|
export async function createRPBuilder(args: {
|
|
105
128
|
rpOpts: IRPOptions
|
|
106
|
-
pexOpts?: IPEXOptions | undefined
|
|
107
129
|
definition?: IPresentationDefinition
|
|
108
|
-
dcql?: DcqlQuery
|
|
109
130
|
context: IRequiredContext
|
|
110
131
|
}): Promise<RPBuilder> {
|
|
111
|
-
const { rpOpts,
|
|
132
|
+
const { rpOpts, context } = args
|
|
112
133
|
const { identifierOpts } = rpOpts
|
|
113
|
-
let definition: IPresentationDefinition | undefined = args.definition
|
|
114
|
-
let dcqlQuery: DcqlQuery | undefined = args.dcql
|
|
115
|
-
|
|
116
|
-
if (!definition && pexOpts && pexOpts.definitionId) {
|
|
117
|
-
const presentationDefinitionItems = await context.agent.pdmGetDefinitions({
|
|
118
|
-
filter: [
|
|
119
|
-
{
|
|
120
|
-
definitionId: pexOpts.definitionId,
|
|
121
|
-
version: pexOpts.version,
|
|
122
|
-
tenantId: pexOpts.tenantId,
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
if (presentationDefinitionItems.length > 0) {
|
|
128
|
-
const presentationDefinitionItem = presentationDefinitionItems[0]
|
|
129
|
-
definition = presentationDefinitionItem.definitionPayload
|
|
130
|
-
if (!dcqlQuery && presentationDefinitionItem.dcqlPayload) {
|
|
131
|
-
dcqlQuery = presentationDefinitionItem.dcqlPayload as DcqlQuery // cast from DcqlQueryREST back to valibot DcqlQuery
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
134
|
|
|
136
135
|
const didMethods = identifierOpts.supportedDIDMethods ?? (await getAgentDIDMethods(context))
|
|
137
136
|
const eventEmitter = rpOpts.eventEmitter ?? new EventEmitter()
|
|
@@ -161,7 +160,7 @@ export async function createRPBuilder(args: {
|
|
|
161
160
|
uniresolverResolution: rpOpts.identifierOpts.resolveOpts?.noUniversalResolverFallback !== true,
|
|
162
161
|
})
|
|
163
162
|
//todo: probably wise to first look and see if we actually need the hasher to begin with
|
|
164
|
-
let hasher:
|
|
163
|
+
let hasher: HasherSync | undefined = rpOpts.credentialOpts?.hasher
|
|
165
164
|
if (!rpOpts.credentialOpts?.hasher || typeof rpOpts.credentialOpts?.hasher !== 'function') {
|
|
166
165
|
hasher = defaultHasher
|
|
167
166
|
}
|
|
@@ -171,9 +170,7 @@ export async function createRPBuilder(args: {
|
|
|
171
170
|
.withResponseMode(rpOpts.responseMode ?? ResponseMode.POST)
|
|
172
171
|
.withResponseType(ResponseType.VP_TOKEN, PropertyTarget.REQUEST_OBJECT)
|
|
173
172
|
// todo: move to options fill/correct method
|
|
174
|
-
.withSupportedVersions(
|
|
175
|
-
rpOpts.supportedVersions ?? [SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1, SupportedVersion.SIOPv2_ID1, SupportedVersion.SIOPv2_D11],
|
|
176
|
-
)
|
|
173
|
+
.withSupportedVersions(rpOpts.supportedVersions ?? [SupportedVersion.OID4VP_v1, SupportedVersion.SIOPv2_OID4VP_D28])
|
|
177
174
|
|
|
178
175
|
.withEventEmitter(eventEmitter)
|
|
179
176
|
.withSessionManager(rpOpts.sessionManager ?? new InMemoryRPSessionManager(eventEmitter))
|
|
@@ -192,23 +189,21 @@ export async function createRPBuilder(args: {
|
|
|
192
189
|
context,
|
|
193
190
|
),
|
|
194
191
|
)
|
|
192
|
+
.withDcqlQueryLookup(getDcqlQueryLookupCallback(context))
|
|
195
193
|
.withRevocationVerification(RevocationVerification.NEVER)
|
|
196
194
|
.withPresentationVerification(getPresentationVerificationCallback(identifierOpts.idOpts, context))
|
|
197
195
|
|
|
198
196
|
const oidfOpts = identifierOpts.oidfOpts
|
|
199
197
|
if (oidfOpts && isExternalIdentifierOIDFEntityIdOpts(oidfOpts)) {
|
|
200
|
-
builder.withEntityId(oidfOpts.identifier, PropertyTarget.REQUEST_OBJECT)
|
|
198
|
+
builder.withEntityId(oidfOpts.identifier, PropertyTarget.REQUEST_OBJECT)
|
|
201
199
|
} else {
|
|
202
200
|
const resolution = await context.agent.identifierManagedGet(identifierOpts.idOpts)
|
|
203
|
-
|
|
204
|
-
.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
(resolution.clientIdScheme as ClientIdScheme) ?? (identifierOpts.idOpts.clientIdScheme as ClientIdScheme),
|
|
210
|
-
PropertyTarget.REQUEST_OBJECT,
|
|
211
|
-
)
|
|
201
|
+
const clientId: string =
|
|
202
|
+
rpOpts.clientMetadataOpts?.client_id ??
|
|
203
|
+
resolution.issuer ??
|
|
204
|
+
(isManagedIdentifierDidResult(resolution) ? resolution.did : resolution.jwkThumbprint)
|
|
205
|
+
const clientIdPrefixed = prefixClientId(clientId)
|
|
206
|
+
builder.withClientId(clientIdPrefixed, PropertyTarget.REQUEST_OBJECT)
|
|
212
207
|
}
|
|
213
208
|
|
|
214
209
|
if (hasher) {
|
|
@@ -222,13 +217,6 @@ export async function createRPBuilder(args: {
|
|
|
222
217
|
//fixme: this has been removed in the new version of did-auth-siop
|
|
223
218
|
// builder.withWellknownDIDVerifyCallback(getWellKnownDIDVerifyCallback(didOpts, context))
|
|
224
219
|
|
|
225
|
-
if (definition) {
|
|
226
|
-
builder.withPresentationDefinition({ definition }, PropertyTarget.REQUEST_OBJECT)
|
|
227
|
-
}
|
|
228
|
-
if (dcqlQuery) {
|
|
229
|
-
builder.withDcqlQuery(dcqlQuery)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
220
|
if (rpOpts.responseRedirectUri) {
|
|
233
221
|
builder.withResponseRedirectUri(rpOpts.responseRedirectUri)
|
|
234
222
|
}
|
|
@@ -309,3 +297,12 @@ export function getSigningAlgo(type: TKeyType): SigningAlgo {
|
|
|
309
297
|
throw Error('Key type not yet supported')
|
|
310
298
|
}
|
|
311
299
|
}
|
|
300
|
+
|
|
301
|
+
export function prefixClientId(clientId: string): string {
|
|
302
|
+
// FIXME SSISDK-60
|
|
303
|
+
if (clientId.startsWith('did:')) {
|
|
304
|
+
return `${ClientIdentifierPrefix.DECENTRALIZED_IDENTIFIER}:${clientId}`
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return clientId
|
|
308
|
+
}
|