@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.34.1-next.3 → 0.34.1-next.323
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 +647 -1070
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +723 -118
- package/dist/index.d.ts +723 -118
- package/dist/index.js +607 -1030
- package/dist/index.js.map +1 -1
- package/package.json +24 -24
- package/src/agent/DidAuthSiopOpAuthenticator.ts +10 -145
- package/src/index.ts +2 -1
- package/src/machine/Siopv2Machine.ts +5 -5
- package/src/services/Siopv2MachineService.ts +140 -267
- package/src/session/OID4VP.ts +151 -292
- package/src/session/OpSession.ts +11 -119
- package/src/session/functions.ts +1 -8
- package/src/types/IDidAuthSiopOpAuthenticator.ts +6 -59
- package/src/types/identifier/index.ts +0 -4
- package/src/types/machine/index.ts +1 -1
- package/src/types/siop-service/index.ts +12 -10
- package/src/utils/CredentialUtils.ts +2 -40
- package/src/utils/dcql.ts +26 -19
|
@@ -1,31 +1,21 @@
|
|
|
1
|
-
import { AuthorizationRequest
|
|
2
|
-
import {
|
|
3
|
-
import { InputDescriptorV1, InputDescriptorV2, PresentationDefinitionV1, PresentationDefinitionV2 } from '@sphereon/pex-models'
|
|
1
|
+
import { AuthorizationRequest } from '@sphereon/did-auth-siop'
|
|
2
|
+
import { getOrCreatePrimaryIdentifier, SupportedDidMethodEnum } from '@sphereon/ssi-sdk-ext.did-utils'
|
|
4
3
|
import { isOID4VCIssuerIdentifier, ManagedIdentifierOptsOrResult } from '@sphereon/ssi-sdk-ext.identifier-resolution'
|
|
4
|
+
import { encodeJoseBlob } from '@sphereon/ssi-sdk.core'
|
|
5
5
|
import { UniqueDigitalCredential, verifiableCredentialForRoleFilter } from '@sphereon/ssi-sdk.credential-store'
|
|
6
|
-
import { ConnectionType
|
|
7
|
-
import { CredentialMapper, HasherSync, Loggers, OriginalVerifiableCredential
|
|
8
|
-
import { OID4VP, OpSession } from '../session'
|
|
9
|
-
import {
|
|
10
|
-
DidAgents,
|
|
11
|
-
LOGGER_NAMESPACE,
|
|
12
|
-
RequiredContext,
|
|
13
|
-
SelectableCredential,
|
|
14
|
-
SelectableCredentialsMap,
|
|
15
|
-
Siopv2HolderEvent,
|
|
16
|
-
SuitableCredentialAgents,
|
|
17
|
-
VerifiableCredentialsWithDefinition,
|
|
18
|
-
VerifiablePresentationWithDefinition,
|
|
19
|
-
} from '../types'
|
|
6
|
+
import { ConnectionType } from '@sphereon/ssi-sdk.data-store-types'
|
|
7
|
+
import { CredentialMapper, CredentialRole, HasherSync, Loggers, OriginalVerifiableCredential } from '@sphereon/ssi-types'
|
|
20
8
|
import { IAgentContext, IDIDManager } from '@veramo/core'
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
9
|
+
import { DcqlPresentation, DcqlQuery } from 'dcql'
|
|
10
|
+
import { createVerifiablePresentationForFormat, OpSession, PresentationBuilderContext } from '../session'
|
|
11
|
+
import { LOGGER_NAMESPACE, RequiredContext, SelectableCredential, SelectableCredentialsMap, Siopv2HolderEvent } from '../types'
|
|
24
12
|
import { convertToDcqlCredentials } from '../utils/dcql'
|
|
25
|
-
|
|
13
|
+
|
|
14
|
+
const CLOCK_SKEW = 120
|
|
26
15
|
|
|
27
16
|
export const logger = Loggers.DEFAULT.get(LOGGER_NAMESPACE)
|
|
28
17
|
|
|
18
|
+
// @ts-ignore
|
|
29
19
|
const createEbsiIdentifier = async (agentContext: IAgentContext<IDIDManager>): Promise<ManagedIdentifierOptsOrResult> => {
|
|
30
20
|
logger.log(`No EBSI key present yet. Creating a new one...`)
|
|
31
21
|
const { result: newIdentifier, created } = await getOrCreatePrimaryIdentifier(agentContext, {
|
|
@@ -39,9 +29,10 @@ const createEbsiIdentifier = async (agentContext: IAgentContext<IDIDManager>): P
|
|
|
39
29
|
return await agentContext.agent.identifierManagedGetByDid({ identifier: newIdentifier.did })
|
|
40
30
|
}
|
|
41
31
|
|
|
32
|
+
// @ts-ignore
|
|
42
33
|
const hasEbsiClient = async (authorizationRequest: AuthorizationRequest) => {
|
|
43
|
-
const clientId =
|
|
44
|
-
const redirectUri =
|
|
34
|
+
const clientId = authorizationRequest.getMergedProperty<string>('client_id')
|
|
35
|
+
const redirectUri = authorizationRequest.getMergedProperty<string>('redirect_uri')
|
|
45
36
|
return clientId?.toLowerCase().includes('.ebsi.eu') || redirectUri?.toLowerCase().includes('.ebsi.eu')
|
|
46
37
|
}
|
|
47
38
|
|
|
@@ -49,293 +40,175 @@ export const siopSendAuthorizationResponse = async (
|
|
|
49
40
|
connectionType: ConnectionType,
|
|
50
41
|
args: {
|
|
51
42
|
sessionId: string
|
|
52
|
-
|
|
43
|
+
credentials: Array<UniqueDigitalCredential | OriginalVerifiableCredential>
|
|
53
44
|
idOpts?: ManagedIdentifierOptsOrResult
|
|
54
45
|
isFirstParty?: boolean
|
|
55
46
|
hasher?: HasherSync
|
|
56
|
-
dcqlQuery?: DcqlQuery
|
|
57
47
|
},
|
|
58
48
|
context: RequiredContext,
|
|
59
49
|
) => {
|
|
60
50
|
const { agent } = context
|
|
61
|
-
const
|
|
62
|
-
let { idOpts, isFirstParty, hasher = defaultHasher } = args
|
|
63
|
-
|
|
51
|
+
const { credentials } = args
|
|
64
52
|
if (connectionType !== ConnectionType.SIOPv2_OpenID4VP) {
|
|
65
53
|
return Promise.reject(Error(`No supported authentication provider for type: ${connectionType}`))
|
|
66
54
|
}
|
|
55
|
+
|
|
67
56
|
const session: OpSession = await agent.siopGetOPSession({ sessionId: args.sessionId })
|
|
68
57
|
const request = await session.getAuthorizationRequest()
|
|
69
|
-
const aud =
|
|
58
|
+
const aud = request.authorizationRequest.getMergedProperty<string>('aud')
|
|
70
59
|
logger.debug(`AUD: ${aud}`)
|
|
71
60
|
logger.debug(JSON.stringify(request.authorizationRequest))
|
|
72
61
|
|
|
73
|
-
|
|
74
|
-
let presentationSubmission: PresentationSubmission | undefined
|
|
75
|
-
if (await session.hasPresentationDefinitions()) {
|
|
76
|
-
const oid4vp: OID4VP = await session.getOID4VP({ hasher })
|
|
77
|
-
|
|
78
|
-
const credentialsAndDefinitions = args.verifiableCredentialsWithDefinition
|
|
79
|
-
? args.verifiableCredentialsWithDefinition
|
|
80
|
-
: await oid4vp.filterCredentialsAgainstAllDefinitions(CredentialRole.HOLDER)
|
|
81
|
-
const domain =
|
|
82
|
-
((await request.authorizationRequest.getMergedProperty('client_id')) as string) ??
|
|
83
|
-
request.issuer ??
|
|
84
|
-
(request.versions.includes(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1)
|
|
85
|
-
? 'https://self-issued.me/v2/openid-vc'
|
|
86
|
-
: 'https://self-issued.me/v2')
|
|
87
|
-
logger.log(`NONCE: ${session.nonce}, domain: ${domain}`)
|
|
88
|
-
|
|
89
|
-
const firstUniqueDC = credentialsAndDefinitions[0].credentials[0]
|
|
90
|
-
if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
|
|
91
|
-
return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
|
|
92
|
-
}
|
|
62
|
+
const domain = ((await request.authorizationRequest.getMergedProperty('client_id')) as string) ?? request.issuer ?? 'https://self-issued.me/v2'
|
|
93
63
|
|
|
94
|
-
|
|
95
|
-
const digitalCredential = firstUniqueDC.digitalCredential
|
|
96
|
-
const firstVC = firstUniqueDC.uniformVerifiableCredential
|
|
97
|
-
const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
|
|
98
|
-
? firstVC.decodedPayload.cnf?.jwk
|
|
99
|
-
? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
|
|
100
|
-
//doesn't apply to did:jwk only, as you can represent any DID key as a JWK. So whenever you encounter a JWK it doesn't mean it had to come from a did:jwk in the system. It just can always be represented as a did:jwk
|
|
101
|
-
`did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
|
|
102
|
-
: firstVC.decodedPayload.sub
|
|
103
|
-
: Array.isArray(firstVC.credentialSubject)
|
|
104
|
-
? firstVC.credentialSubject[0].id
|
|
105
|
-
: firstVC.credentialSubject.id
|
|
106
|
-
if (!digitalCredential.kmsKeyRef) {
|
|
107
|
-
// In case the store does not have the kmsKeyRef lets search for the holder
|
|
108
|
-
|
|
109
|
-
if (!holder) {
|
|
110
|
-
return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
|
|
114
|
-
} catch (e) {
|
|
115
|
-
logger.debug(`Holder DID not found: ${holder}`)
|
|
116
|
-
throw e
|
|
117
|
-
}
|
|
118
|
-
} else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
|
|
119
|
-
identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
|
|
120
|
-
identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
|
|
121
|
-
})
|
|
122
|
-
} else {
|
|
123
|
-
switch (digitalCredential.subjectCorrelationType) {
|
|
124
|
-
case 'DID':
|
|
125
|
-
identifier = await session.context.agent.identifierManagedGetByDid({
|
|
126
|
-
identifier: digitalCredential.subjectCorrelationId ?? holder,
|
|
127
|
-
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
128
|
-
})
|
|
129
|
-
break
|
|
130
|
-
// TODO other implementations?
|
|
131
|
-
default:
|
|
132
|
-
if (digitalCredential.subjectCorrelationId?.startsWith('did:') || holder?.startsWith('did:')) {
|
|
133
|
-
identifier = await session.context.agent.identifierManagedGetByDid({
|
|
134
|
-
identifier: digitalCredential.subjectCorrelationId ?? holder,
|
|
135
|
-
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
136
|
-
})
|
|
137
|
-
} else {
|
|
138
|
-
// Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
|
|
139
|
-
identifier = await session.context.agent.identifierManagedGetByKid({
|
|
140
|
-
identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
|
|
141
|
-
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
142
|
-
})
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
64
|
+
logger.debug(`NONCE: ${session.nonce}, domain: ${domain}`)
|
|
146
65
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
66
|
+
const firstUniqueDC = credentials[0]
|
|
67
|
+
if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
|
|
68
|
+
return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
|
|
69
|
+
}
|
|
151
70
|
|
|
152
|
-
|
|
71
|
+
let identifier: ManagedIdentifierOptsOrResult
|
|
72
|
+
const digitalCredential = firstUniqueDC.digitalCredential
|
|
73
|
+
const firstVC = firstUniqueDC.uniformVerifiableCredential
|
|
74
|
+
|
|
75
|
+
// Determine holder DID for identifier resolution
|
|
76
|
+
let holder: string | undefined
|
|
77
|
+
if (CredentialMapper.isSdJwtDecodedCredential(firstVC)) {
|
|
78
|
+
// TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
|
|
79
|
+
// doesn't apply to did:jwk only, as you can represent any DID key as a
|
|
80
|
+
holder = firstVC.decodedPayload.cnf?.jwk ? `did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0` : firstVC.decodedPayload.sub
|
|
81
|
+
} else {
|
|
82
|
+
holder = Array.isArray(firstVC.credentialSubject) ? firstVC.credentialSubject[0].id : firstVC.credentialSubject.id
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Resolve identifier
|
|
86
|
+
if (!digitalCredential.kmsKeyRef) {
|
|
87
|
+
// In case the store does not have the kmsKeyRef lets search for the holder
|
|
153
88
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
89
|
+
if (!holder) {
|
|
90
|
+
return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
|
|
94
|
+
} catch (e) {
|
|
95
|
+
logger.debug(`Holder DID not found: ${holder}`)
|
|
96
|
+
throw e
|
|
97
|
+
}
|
|
98
|
+
} else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
|
|
99
|
+
identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
|
|
100
|
+
identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
|
|
160
101
|
})
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
102
|
+
} else {
|
|
103
|
+
switch (digitalCredential.subjectCorrelationType) {
|
|
104
|
+
case 'DID':
|
|
105
|
+
identifier = await session.context.agent.identifierManagedGetByDid({
|
|
106
|
+
identifier: digitalCredential.subjectCorrelationId ?? holder,
|
|
107
|
+
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
108
|
+
})
|
|
109
|
+
break
|
|
110
|
+
// TODO other implementations?
|
|
111
|
+
default:
|
|
112
|
+
// Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
|
|
113
|
+
identifier = await session.context.agent.identifierManagedGetByKid({
|
|
114
|
+
identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
|
|
115
|
+
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
116
|
+
})
|
|
165
117
|
}
|
|
118
|
+
}
|
|
166
119
|
|
|
167
|
-
|
|
168
|
-
presentationSubmission = presentationsAndDefs[0].presentationSubmission
|
|
169
|
-
|
|
170
|
-
logger.log(`Definitions and locations:`, JSON.stringify(presentationsAndDefs?.[0]?.verifiablePresentations, null, 2))
|
|
171
|
-
logger.log(`Presentation Submission:`, JSON.stringify(presentationSubmission, null, 2))
|
|
172
|
-
const mergedVerifiablePresentations = presentationsAndDefs?.flatMap((pd) => pd.verifiablePresentations) || []
|
|
173
|
-
return await session.sendAuthorizationResponse({
|
|
174
|
-
...(presentationsAndDefs && { verifiablePresentations: mergedVerifiablePresentations }),
|
|
175
|
-
...(presentationSubmission && { presentationSubmission }),
|
|
176
|
-
// todo: Change issuer value in case we do not use identifier. Use key.meta.jwkThumbprint then
|
|
177
|
-
responseSignerOpts: idOpts!,
|
|
178
|
-
isFirstParty,
|
|
179
|
-
})
|
|
180
|
-
} else if (request.dcqlQuery) {
|
|
181
|
-
if (args.verifiableCredentialsWithDefinition !== undefined && args.verifiableCredentialsWithDefinition !== null) {
|
|
182
|
-
const vcs = args.verifiableCredentialsWithDefinition.flatMap((vcd) => vcd.credentials)
|
|
183
|
-
const domain =
|
|
184
|
-
((await request.authorizationRequest.getMergedProperty('client_id')) as string) ??
|
|
185
|
-
request.issuer ??
|
|
186
|
-
(request.versions.includes(SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1)
|
|
187
|
-
? 'https://self-issued.me/v2/openid-vc'
|
|
188
|
-
: 'https://self-issued.me/v2')
|
|
189
|
-
logger.debug(`NONCE: ${session.nonce}, domain: ${domain}`)
|
|
190
|
-
|
|
191
|
-
const firstUniqueDC = vcs[0]
|
|
192
|
-
if (typeof firstUniqueDC !== 'object' || !('digitalCredential' in firstUniqueDC)) {
|
|
193
|
-
return Promise.reject(Error('SiopMachine only supports UniqueDigitalCredentials for now'))
|
|
194
|
-
}
|
|
120
|
+
const dcqlCredentialsWithCredentials = new Map(credentials.map((vc) => [convertToDcqlCredentials(vc), vc]))
|
|
195
121
|
|
|
196
|
-
|
|
197
|
-
const digitalCredential = firstUniqueDC.digitalCredential
|
|
198
|
-
const firstVC = firstUniqueDC.uniformVerifiableCredential
|
|
199
|
-
const holder = CredentialMapper.isSdJwtDecodedCredential(firstVC)
|
|
200
|
-
? firstVC.decodedPayload.cnf?.jwk
|
|
201
|
-
? //TODO SDK-19: convert the JWK to hex and search for the appropriate key and associated DID
|
|
202
|
-
//doesn't apply to did:jwk only, as you can represent any DID key as a JWK. So whenever you encounter a JWK it doesn't mean it had to come from a did:jwk in the system. It just can always be represented as a did:jwk
|
|
203
|
-
`did:jwk:${encodeJoseBlob(firstVC.decodedPayload.cnf?.jwk)}#0`
|
|
204
|
-
: firstVC.decodedPayload.sub
|
|
205
|
-
: Array.isArray(firstVC.credentialSubject)
|
|
206
|
-
? firstVC.credentialSubject[0].id
|
|
207
|
-
: firstVC.credentialSubject.id
|
|
208
|
-
if (!digitalCredential.kmsKeyRef) {
|
|
209
|
-
// In case the store does not have the kmsKeyRef lets search for the holder
|
|
210
|
-
|
|
211
|
-
if (!holder) {
|
|
212
|
-
return Promise.reject(`No holder found and no kmsKeyRef in DB. Cannot determine identifier to use`)
|
|
213
|
-
}
|
|
214
|
-
try {
|
|
215
|
-
identifier = await session.context.agent.identifierManagedGet({ identifier: holder })
|
|
216
|
-
} catch (e) {
|
|
217
|
-
logger.debug(`Holder DID not found: ${holder}`)
|
|
218
|
-
throw e
|
|
219
|
-
}
|
|
220
|
-
} else if (isOID4VCIssuerIdentifier(digitalCredential.kmsKeyRef)) {
|
|
221
|
-
identifier = await session.context.agent.identifierManagedGetByOID4VCIssuer({
|
|
222
|
-
identifier: firstUniqueDC.digitalCredential.kmsKeyRef,
|
|
223
|
-
})
|
|
224
|
-
} else {
|
|
225
|
-
switch (digitalCredential.subjectCorrelationType) {
|
|
226
|
-
case 'DID':
|
|
227
|
-
identifier = await session.context.agent.identifierManagedGetByDid({
|
|
228
|
-
identifier: digitalCredential.subjectCorrelationId ?? holder,
|
|
229
|
-
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
230
|
-
})
|
|
231
|
-
break
|
|
232
|
-
// TODO other implementations?
|
|
233
|
-
default:
|
|
234
|
-
// Since we are using the kmsKeyRef we will find the KID regardless of the identifier. We set it for later access though
|
|
235
|
-
identifier = await session.context.agent.identifierManagedGetByKid({
|
|
236
|
-
identifier: digitalCredential.subjectCorrelationId ?? holder ?? digitalCredential.kmsKeyRef,
|
|
237
|
-
kmsKeyRef: digitalCredential.kmsKeyRef,
|
|
238
|
-
})
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
console.log(`Identifier`, identifier)
|
|
242
|
-
|
|
243
|
-
const dcqlRepresentations: DcqlCredential[] = []
|
|
244
|
-
vcs.forEach((vc: UniqueDigitalCredential | OriginalVerifiableCredential) => {
|
|
245
|
-
const rep = convertToDcqlCredentials(vc, args.hasher)
|
|
246
|
-
if (rep) {
|
|
247
|
-
dcqlRepresentations.push(rep)
|
|
248
|
-
}
|
|
249
|
-
})
|
|
122
|
+
const queryResult = DcqlQuery.query(request.dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
|
|
250
123
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
for (const [key, value] of Object.entries(queryResult.credential_matches)) {
|
|
255
|
-
const allMatches = Array.isArray(value) ? value : [value]
|
|
256
|
-
allMatches.forEach((match) => {
|
|
257
|
-
if (match.success) {
|
|
258
|
-
const originalCredential = getOriginalVerifiableCredential(vcs[match.input_credential_index])
|
|
259
|
-
if (!originalCredential) {
|
|
260
|
-
throw new Error(`Index ${match.input_credential_index} out of range in credentials array`)
|
|
261
|
-
}
|
|
262
|
-
presentation[key] =
|
|
263
|
-
(originalCredential as any)['compactSdJwtVc'] !== undefined ? (originalCredential as any).compactSdJwtVc : originalCredential
|
|
264
|
-
}
|
|
265
|
-
})
|
|
266
|
-
}
|
|
124
|
+
if (!queryResult.can_be_satisfied) {
|
|
125
|
+
return Promise.reject(Error('Credentials do not match required query request'))
|
|
126
|
+
}
|
|
267
127
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
128
|
+
// Build presentation context for format-aware VP creation
|
|
129
|
+
const presentationContext: PresentationBuilderContext = {
|
|
130
|
+
nonce: request.requestObject?.getPayload()?.nonce ?? session.nonce,
|
|
131
|
+
audience: domain,
|
|
132
|
+
agent: context.agent,
|
|
133
|
+
clockSkew: CLOCK_SKEW,
|
|
134
|
+
hasher: args.hasher,
|
|
135
|
+
}
|
|
272
136
|
|
|
273
|
-
|
|
137
|
+
// Build DCQL presentation with format-aware VPs
|
|
138
|
+
const presentation: DcqlPresentation.Output = {}
|
|
139
|
+
const uniqueCredentials = Array.from(dcqlCredentialsWithCredentials.values())
|
|
140
|
+
for (const [key, value] of Object.entries(queryResult.credential_matches)) {
|
|
141
|
+
if (value.success) {
|
|
142
|
+
const matchedCredentials = value.valid_credentials.map((cred) => uniqueCredentials[cred.input_credential_index])
|
|
143
|
+
const vc = matchedCredentials[0] // taking the first match for now
|
|
274
144
|
|
|
275
|
-
|
|
145
|
+
if (!vc) {
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
// Use format-aware presentation builder
|
|
151
|
+
const vp = await createVerifiablePresentationForFormat(vc, identifier, presentationContext)
|
|
152
|
+
presentation[key] = vp as any
|
|
153
|
+
} catch (error) {
|
|
154
|
+
logger.error(`Failed to create VP for credential ${key}:`, error)
|
|
155
|
+
throw error
|
|
156
|
+
}
|
|
276
157
|
}
|
|
277
158
|
}
|
|
278
|
-
throw Error('Presentation Definition or DCQL is required')
|
|
279
|
-
}
|
|
280
159
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
160
|
+
const dcqlPresentation = DcqlPresentation.parse(presentation)
|
|
161
|
+
|
|
162
|
+
const response = session.sendAuthorizationResponse({
|
|
163
|
+
responseSignerOpts: identifier,
|
|
164
|
+
dcqlResponse: {
|
|
165
|
+
dcqlPresentation,
|
|
166
|
+
},
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
logger.debug(`Response: `, response)
|
|
170
|
+
return response
|
|
289
171
|
}
|
|
290
172
|
|
|
291
|
-
export const getSelectableCredentials = async (
|
|
292
|
-
|
|
293
|
-
context: RequiredContext,
|
|
294
|
-
): Promise<SelectableCredentialsMap> => {
|
|
295
|
-
const agentContext = { ...context, agent: context.agent as SuitableCredentialAgents }
|
|
173
|
+
export const getSelectableCredentials = async (dcqlQuery: DcqlQuery, context: RequiredContext): Promise<SelectableCredentialsMap> => {
|
|
174
|
+
const agentContext = { ...context, agent: context.agent }
|
|
296
175
|
const { agent } = agentContext
|
|
297
|
-
const pex = new PEX()
|
|
298
|
-
|
|
299
176
|
const uniqueVerifiableCredentials = await agent.crsGetUniqueCredentials({
|
|
300
177
|
filter: verifiableCredentialForRoleFilter(CredentialRole.HOLDER),
|
|
301
178
|
})
|
|
302
|
-
const
|
|
303
|
-
|
|
179
|
+
const branding = await agent.ibGetCredentialBranding()
|
|
180
|
+
const dcqlCredentialsWithCredentials = new Map(uniqueVerifiableCredentials.map((vc) => [convertToDcqlCredentials(vc), vc]))
|
|
181
|
+
const queryResult = DcqlQuery.query(dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()))
|
|
182
|
+
const uniqueCredentials = Array.from(dcqlCredentialsWithCredentials.values())
|
|
304
183
|
const selectableCredentialsMap: SelectableCredentialsMap = new Map()
|
|
305
184
|
|
|
306
|
-
for (const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
})
|
|
311
|
-
const selectionResults = pex.selectFrom(partialPD, originalCredentials)
|
|
185
|
+
for (const [key, value] of Object.entries(queryResult.credential_matches)) {
|
|
186
|
+
if (!value.valid_credentials) {
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
312
189
|
|
|
313
|
-
const
|
|
314
|
-
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
190
|
+
const mapSelectableCredentialPromises = value.valid_credentials.map(async (cred) => {
|
|
191
|
+
const matchedCredential = uniqueCredentials[cred.input_credential_index]
|
|
192
|
+
const credentialBranding = branding.filter((cb) => cb.vcHash === matchedCredential.hash)
|
|
193
|
+
const issuerPartyIdentity = await agent.cmGetContacts({
|
|
194
|
+
filter: [{ identities: { identifier: { correlationId: matchedCredential.uniformVerifiableCredential!.issuerDid } } }],
|
|
195
|
+
})
|
|
196
|
+
const subjectPartyIdentity = await agent.cmGetContacts({
|
|
197
|
+
filter: [{ identities: { identifier: { correlationId: matchedCredential.uniformVerifiableCredential!.subjectDid } } }],
|
|
318
198
|
})
|
|
319
199
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const subjectPartyIdentity = await agent.cmGetContacts({
|
|
326
|
-
filter: [{ identities: { identifier: { correlationId: filteredUniqueVC.uniformVerifiableCredential!.subjectDid } } }],
|
|
327
|
-
})
|
|
328
|
-
|
|
329
|
-
selectableCredentials.push({
|
|
330
|
-
credential: filteredUniqueVC,
|
|
331
|
-
credentialBranding: filteredCredentialBrandings[0]?.localeBranding,
|
|
332
|
-
issuerParty: issuerPartyIdentity?.[0],
|
|
333
|
-
subjectParty: subjectPartyIdentity?.[0],
|
|
334
|
-
})
|
|
200
|
+
return {
|
|
201
|
+
credential: matchedCredential,
|
|
202
|
+
credentialBranding: credentialBranding[0]?.localeBranding,
|
|
203
|
+
issuerParty: issuerPartyIdentity?.[0],
|
|
204
|
+
subjectParty: subjectPartyIdentity?.[0],
|
|
335
205
|
}
|
|
336
|
-
}
|
|
337
|
-
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
const selectableCredentials: Array<SelectableCredential> = await Promise.all(mapSelectableCredentialPromises)
|
|
209
|
+
selectableCredentialsMap.set(key, selectableCredentials)
|
|
338
210
|
}
|
|
211
|
+
|
|
339
212
|
return selectableCredentialsMap
|
|
340
213
|
}
|
|
341
214
|
|