@sphereon/ssi-sdk.siopv2-oid4vp-op-auth 0.33.0 → 0.33.1-feature.vcdm2.4
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/agent/DidAuthSiopOpAuthenticator.js +319 -340
- package/dist/agent/DidAuthSiopOpAuthenticator.js.map +1 -1
- package/dist/index.js +7 -27
- package/dist/index.js.map +1 -1
- package/dist/link-handler/index.js +35 -47
- package/dist/link-handler/index.js.map +1 -1
- package/dist/localization/Localization.js +38 -43
- package/dist/localization/Localization.js.map +1 -1
- package/dist/machine/CallbackStateListener.js +9 -22
- package/dist/machine/CallbackStateListener.js.map +1 -1
- package/dist/machine/Siopv2Machine.js +129 -131
- package/dist/machine/Siopv2Machine.js.map +1 -1
- package/dist/services/IdentifierService.js +11 -24
- package/dist/services/IdentifierService.js.map +1 -1
- package/dist/services/Siopv2MachineService.js +117 -120
- package/dist/services/Siopv2MachineService.js.map +1 -1
- package/dist/session/OID4VP.js +184 -195
- package/dist/session/OID4VP.js.map +1 -1
- package/dist/session/OpSession.js +252 -288
- package/dist/session/OpSession.js.map +1 -1
- package/dist/session/functions.js +95 -111
- package/dist/session/functions.js.map +1 -1
- package/dist/session/index.js +3 -19
- package/dist/session/index.js.map +1 -1
- package/dist/types/IDidAuthSiopOpAuthenticator.js +4 -7
- package/dist/types/IDidAuthSiopOpAuthenticator.js.map +1 -1
- package/dist/types/error/index.js +1 -2
- package/dist/types/identifier/index.js +1 -4
- package/dist/types/identifier/index.js.map +1 -1
- package/dist/types/index.js +5 -21
- package/dist/types/index.js.map +1 -1
- package/dist/types/machine/index.js +10 -13
- package/dist/types/machine/index.js.map +1 -1
- package/dist/types/siop-service/index.js +4 -7
- package/dist/types/siop-service/index.js.map +1 -1
- package/dist/utils/CredentialUtils.js +18 -28
- package/dist/utils/CredentialUtils.js.map +1 -1
- package/dist/utils/dcql.js +6 -9
- package/dist/utils/dcql.js.map +1 -1
- package/package.json +15 -15
|
@@ -1,30 +1,18 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const ssi_sdk_data_store_1 = require("@sphereon/ssi-sdk.data-store");
|
|
15
|
-
const ssi_types_1 = require("@sphereon/ssi-types");
|
|
16
|
-
const uuid_1 = require("uuid");
|
|
17
|
-
const index_1 = require("../index");
|
|
18
|
-
const Siopv2Machine_1 = require("../machine/Siopv2Machine");
|
|
19
|
-
const Siopv2MachineService_1 = require("../services/Siopv2MachineService");
|
|
20
|
-
const session_1 = require("../session");
|
|
21
|
-
const pex_1 = require("@sphereon/pex");
|
|
22
|
-
const utils_1 = require("@veramo/utils");
|
|
23
|
-
const types_1 = require("../types");
|
|
24
|
-
const dcql_1 = require("dcql");
|
|
25
|
-
const logger = ssi_types_1.Loggers.DEFAULT.options(index_1.LOGGER_NAMESPACE, {}).get(index_1.LOGGER_NAMESPACE);
|
|
1
|
+
import { decodeUriAsJson, SupportedVersion } from '@sphereon/did-auth-siop';
|
|
2
|
+
import { ConnectionType, CorrelationIdentifierType, CredentialDocumentFormat, CredentialRole, DocumentType, IdentityOrigin, } from '@sphereon/ssi-sdk.data-store';
|
|
3
|
+
import { Loggers } from '@sphereon/ssi-types';
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
import { LOGGER_NAMESPACE, schema, } from '../index';
|
|
6
|
+
import { Siopv2Machine } from '../machine/Siopv2Machine';
|
|
7
|
+
import { getSelectableCredentials, siopSendAuthorizationResponse, translateCorrelationIdToName } from '../services/Siopv2MachineService';
|
|
8
|
+
import { OpSession } from '../session';
|
|
9
|
+
import { PEX, Status } from '@sphereon/pex';
|
|
10
|
+
import { computeEntryHash } from '@veramo/utils';
|
|
11
|
+
import { Siopv2HolderEvent, } from '../types';
|
|
12
|
+
import { DcqlQuery } from 'dcql';
|
|
13
|
+
const logger = Loggers.DEFAULT.options(LOGGER_NAMESPACE, {}).get(LOGGER_NAMESPACE);
|
|
26
14
|
// Exposing the methods here for any REST implementation
|
|
27
|
-
|
|
15
|
+
export const didAuthSiopOpAuthenticatorMethods = [
|
|
28
16
|
'cmGetContacts',
|
|
29
17
|
'cmGetContact',
|
|
30
18
|
'cmAddContact',
|
|
@@ -36,46 +24,31 @@ exports.didAuthSiopOpAuthenticatorMethods = [
|
|
|
36
24
|
'dataStoreORMGetVerifiableCredentials',
|
|
37
25
|
'createVerifiablePresentation',
|
|
38
26
|
];
|
|
39
|
-
class DidAuthSiopOpAuthenticator {
|
|
27
|
+
export class DidAuthSiopOpAuthenticator {
|
|
28
|
+
schema = schema.IDidAuthSiopOpAuthenticator;
|
|
29
|
+
methods = {
|
|
30
|
+
siopGetOPSession: this.siopGetOPSession.bind(this),
|
|
31
|
+
siopRegisterOPSession: this.siopRegisterOPSession.bind(this),
|
|
32
|
+
siopRemoveOPSession: this.siopRemoveOPSession.bind(this),
|
|
33
|
+
siopRegisterOPCustomApproval: this.siopRegisterOPCustomApproval.bind(this),
|
|
34
|
+
siopRemoveOPCustomApproval: this.siopRemoveOPCustomApproval.bind(this),
|
|
35
|
+
siopGetMachineInterpreter: this.siopGetMachineInterpreter.bind(this),
|
|
36
|
+
siopCreateConfig: this.siopCreateConfig.bind(this),
|
|
37
|
+
siopGetSiopRequest: this.siopGetSiopRequest.bind(this),
|
|
38
|
+
siopRetrieveContact: this.siopRetrieveContact.bind(this),
|
|
39
|
+
siopAddIdentity: this.siopAddContactIdentity.bind(this),
|
|
40
|
+
siopSendResponse: this.siopSendResponse.bind(this),
|
|
41
|
+
siopGetSelectableCredentials: this.siopGetSelectableCredentials.bind(this),
|
|
42
|
+
};
|
|
43
|
+
sessions;
|
|
44
|
+
customApprovals;
|
|
45
|
+
presentationSignCallback;
|
|
46
|
+
onContactIdentityCreated;
|
|
47
|
+
onIdentifierCreated;
|
|
48
|
+
eventEmitter;
|
|
49
|
+
hasher;
|
|
40
50
|
constructor(options) {
|
|
41
|
-
|
|
42
|
-
this.methods = {
|
|
43
|
-
siopGetOPSession: this.siopGetOPSession.bind(this),
|
|
44
|
-
siopRegisterOPSession: this.siopRegisterOPSession.bind(this),
|
|
45
|
-
siopRemoveOPSession: this.siopRemoveOPSession.bind(this),
|
|
46
|
-
siopRegisterOPCustomApproval: this.siopRegisterOPCustomApproval.bind(this),
|
|
47
|
-
siopRemoveOPCustomApproval: this.siopRemoveOPCustomApproval.bind(this),
|
|
48
|
-
siopGetMachineInterpreter: this.siopGetMachineInterpreter.bind(this),
|
|
49
|
-
siopCreateConfig: this.siopCreateConfig.bind(this),
|
|
50
|
-
siopGetSiopRequest: this.siopGetSiopRequest.bind(this),
|
|
51
|
-
siopRetrieveContact: this.siopRetrieveContact.bind(this),
|
|
52
|
-
siopAddIdentity: this.siopAddContactIdentity.bind(this),
|
|
53
|
-
siopSendResponse: this.siopSendResponse.bind(this),
|
|
54
|
-
siopGetSelectableCredentials: this.siopGetSelectableCredentials.bind(this),
|
|
55
|
-
};
|
|
56
|
-
this.hasMDocCredentials = (credentials) => {
|
|
57
|
-
return credentials.some(this.isMDocCredential);
|
|
58
|
-
};
|
|
59
|
-
this.isMDocCredential = (credential) => {
|
|
60
|
-
return (credential.digitalCredential.documentFormat === ssi_sdk_data_store_1.CredentialDocumentFormat.MSO_MDOC &&
|
|
61
|
-
credential.digitalCredential.documentType === ssi_sdk_data_store_1.DocumentType.VC);
|
|
62
|
-
};
|
|
63
|
-
this.hasSdJwtCredentials = (credentials) => {
|
|
64
|
-
return credentials.some(this.isSdJwtCredential);
|
|
65
|
-
};
|
|
66
|
-
this.isSdJwtCredential = (credential) => {
|
|
67
|
-
return (credential.digitalCredential.documentFormat === ssi_sdk_data_store_1.CredentialDocumentFormat.SD_JWT && credential.digitalCredential.documentType === ssi_sdk_data_store_1.DocumentType.VC);
|
|
68
|
-
};
|
|
69
|
-
this.retrieveEncodedCredential = (credential) => {
|
|
70
|
-
var _a, _b;
|
|
71
|
-
return credential.originalVerifiableCredential !== undefined &&
|
|
72
|
-
credential.originalVerifiableCredential !== null &&
|
|
73
|
-
((_a = credential === null || credential === void 0 ? void 0 : credential.originalVerifiableCredential) === null || _a === void 0 ? void 0 : _a.compactSdJwtVc) !== undefined &&
|
|
74
|
-
((_b = credential === null || credential === void 0 ? void 0 : credential.originalVerifiableCredential) === null || _b === void 0 ? void 0 : _b.compactSdJwtVc) !== null
|
|
75
|
-
? credential.originalVerifiableCredential.compactSdJwtVc
|
|
76
|
-
: credential.originalVerifiableCredential;
|
|
77
|
-
};
|
|
78
|
-
const { onContactIdentityCreated, onIdentifierCreated, hasher, customApprovals = {}, presentationSignCallback } = Object.assign({}, options);
|
|
51
|
+
const { onContactIdentityCreated, onIdentifierCreated, hasher, customApprovals = {}, presentationSignCallback } = { ...options };
|
|
79
52
|
this.hasher = hasher;
|
|
80
53
|
this.onContactIdentityCreated = onContactIdentityCreated;
|
|
81
54
|
this.onIdentifierCreated = onIdentifierCreated;
|
|
@@ -83,310 +56,316 @@ class DidAuthSiopOpAuthenticator {
|
|
|
83
56
|
this.sessions = new Map();
|
|
84
57
|
this.customApprovals = customApprovals;
|
|
85
58
|
}
|
|
86
|
-
onEvent(event, context) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return Promise.reject(Error(`Event type ${event.type} not supported`));
|
|
98
|
-
}
|
|
99
|
-
});
|
|
59
|
+
async onEvent(event, context) {
|
|
60
|
+
switch (event.type) {
|
|
61
|
+
case Siopv2HolderEvent.CONTACT_IDENTITY_CREATED:
|
|
62
|
+
this.onContactIdentityCreated?.(event.data);
|
|
63
|
+
break;
|
|
64
|
+
case Siopv2HolderEvent.IDENTIFIER_CREATED:
|
|
65
|
+
this.onIdentifierCreated?.(event.data);
|
|
66
|
+
break;
|
|
67
|
+
default:
|
|
68
|
+
return Promise.reject(Error(`Event type ${event.type} not supported`));
|
|
69
|
+
}
|
|
100
70
|
}
|
|
101
|
-
siopGetOPSession(args, context) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
return this.sessions.get(args.sessionId);
|
|
108
|
-
});
|
|
71
|
+
async siopGetOPSession(args, context) {
|
|
72
|
+
// TODO add cleaning up sessions https://sphereon.atlassian.net/browse/MYC-143
|
|
73
|
+
if (!this.sessions.has(args.sessionId)) {
|
|
74
|
+
throw Error(`No session found for id: ${args.sessionId}`);
|
|
75
|
+
}
|
|
76
|
+
return this.sessions.get(args.sessionId);
|
|
109
77
|
}
|
|
110
|
-
siopRegisterOPSession(args, context) {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
this.sessions.set(sessionId, session);
|
|
123
|
-
return session;
|
|
124
|
-
});
|
|
78
|
+
async siopRegisterOPSession(args, context) {
|
|
79
|
+
const sessionId = args.sessionId || uuidv4();
|
|
80
|
+
if (this.sessions.has(sessionId)) {
|
|
81
|
+
return Promise.reject(new Error(`Session with id: ${args.sessionId} already present`));
|
|
82
|
+
}
|
|
83
|
+
const opts = { ...args, sessionId, context };
|
|
84
|
+
if (!opts.op?.presentationSignCallback) {
|
|
85
|
+
opts.op = { ...opts.op, presentationSignCallback: this.presentationSignCallback };
|
|
86
|
+
}
|
|
87
|
+
const session = await OpSession.init(opts);
|
|
88
|
+
this.sessions.set(sessionId, session);
|
|
89
|
+
return session;
|
|
125
90
|
}
|
|
126
|
-
siopRemoveOPSession(args, context) {
|
|
127
|
-
return
|
|
128
|
-
return this.sessions.delete(args.sessionId);
|
|
129
|
-
});
|
|
91
|
+
async siopRemoveOPSession(args, context) {
|
|
92
|
+
return this.sessions.delete(args.sessionId);
|
|
130
93
|
}
|
|
131
|
-
siopRegisterOPCustomApproval(args, context) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.customApprovals[args.key] = args.customApproval;
|
|
137
|
-
});
|
|
94
|
+
async siopRegisterOPCustomApproval(args, context) {
|
|
95
|
+
if (this.customApprovals[args.key] !== undefined) {
|
|
96
|
+
return Promise.reject(new Error(`Custom approval with key: ${args.key} already present`));
|
|
97
|
+
}
|
|
98
|
+
this.customApprovals[args.key] = args.customApproval;
|
|
138
99
|
}
|
|
139
|
-
siopRemoveOPCustomApproval(args, context) {
|
|
140
|
-
return
|
|
141
|
-
return delete this.customApprovals[args.key];
|
|
142
|
-
});
|
|
100
|
+
async siopRemoveOPCustomApproval(args, context) {
|
|
101
|
+
return delete this.customApprovals[args.key];
|
|
143
102
|
}
|
|
144
|
-
siopGetMachineInterpreter(opts, context) {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
103
|
+
async siopGetMachineInterpreter(opts, context) {
|
|
104
|
+
const { stateNavigationListener, url } = opts;
|
|
105
|
+
const services = {
|
|
106
|
+
createConfig: (args) => this.siopCreateConfig(args),
|
|
107
|
+
getSiopRequest: (args) => this.siopGetSiopRequest(args, context),
|
|
108
|
+
getSelectableCredentials: (args) => this.siopGetSelectableCredentials(args, context),
|
|
109
|
+
retrieveContact: (args) => this.siopRetrieveContact(args, context),
|
|
110
|
+
addContactIdentity: (args) => this.siopAddContactIdentity(args, context),
|
|
111
|
+
sendResponse: (args) => this.siopSendResponse(args, context),
|
|
112
|
+
...opts?.services,
|
|
113
|
+
};
|
|
114
|
+
const siopv2MachineOpts = {
|
|
115
|
+
...opts,
|
|
116
|
+
url,
|
|
117
|
+
stateNavigationListener,
|
|
118
|
+
services: {
|
|
119
|
+
...services,
|
|
120
|
+
...opts.services,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
return Siopv2Machine.newInstance(siopv2MachineOpts);
|
|
152
124
|
}
|
|
153
|
-
siopCreateConfig(context) {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
};
|
|
165
|
-
});
|
|
125
|
+
async siopCreateConfig(context) {
|
|
126
|
+
const { url } = context;
|
|
127
|
+
if (!url) {
|
|
128
|
+
return Promise.reject(Error('Missing request uri in context'));
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
id: uuidv4(),
|
|
132
|
+
// FIXME: Update these values in SSI-SDK. Only the URI (not a redirectURI) would be available at this point
|
|
133
|
+
sessionId: uuidv4(),
|
|
134
|
+
redirectUrl: url,
|
|
135
|
+
};
|
|
166
136
|
}
|
|
167
|
-
siopGetSiopRequest(args, context) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
// logger.trace('Request: ' + JSON.stringify(verifiedAuthorizationRequest, null, 2))
|
|
189
|
-
const clientName = (_a = verifiedAuthorizationRequest.registrationMetadataPayload) === null || _a === void 0 ? void 0 : _a.client_name;
|
|
190
|
-
const url = (_b = verifiedAuthorizationRequest.responseURI) !== null && _b !== void 0 ? _b : (args.url.includes('request_uri')
|
|
137
|
+
async siopGetSiopRequest(args, context) {
|
|
138
|
+
const { agent } = context;
|
|
139
|
+
const { didAuthConfig } = args;
|
|
140
|
+
if (args.url === undefined) {
|
|
141
|
+
return Promise.reject(Error('Missing request uri in context'));
|
|
142
|
+
}
|
|
143
|
+
if (didAuthConfig === undefined) {
|
|
144
|
+
return Promise.reject(Error('Missing config in context'));
|
|
145
|
+
}
|
|
146
|
+
const { sessionId, redirectUrl } = didAuthConfig;
|
|
147
|
+
const session = await agent.siopGetOPSession({ sessionId }).catch(async () => await agent.siopRegisterOPSession({
|
|
148
|
+
requestJwtOrUri: redirectUrl,
|
|
149
|
+
sessionId,
|
|
150
|
+
op: { eventEmitter: this.eventEmitter, hasher: this.hasher },
|
|
151
|
+
}));
|
|
152
|
+
logger.debug(`session: ${JSON.stringify(session.id, null, 2)}`);
|
|
153
|
+
const verifiedAuthorizationRequest = await session.getAuthorizationRequest();
|
|
154
|
+
// logger.trace('Request: ' + JSON.stringify(verifiedAuthorizationRequest, null, 2))
|
|
155
|
+
const clientName = verifiedAuthorizationRequest.registrationMetadataPayload?.client_name;
|
|
156
|
+
const url = verifiedAuthorizationRequest.responseURI ??
|
|
157
|
+
(args.url.includes('request_uri')
|
|
191
158
|
? decodeURIComponent(args.url.split('?request_uri=')[1].trim())
|
|
192
|
-
: (
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
});
|
|
159
|
+
: (verifiedAuthorizationRequest.issuer ?? verifiedAuthorizationRequest.registrationMetadataPayload?.client_id));
|
|
160
|
+
const uri = url.includes('://') ? new URL(url) : undefined;
|
|
161
|
+
const correlationId = uri?.hostname ?? (await this.determineCorrelationId(uri, verifiedAuthorizationRequest, clientName, context));
|
|
162
|
+
const clientId = await verifiedAuthorizationRequest.authorizationRequest.getMergedProperty('client_id');
|
|
163
|
+
return {
|
|
164
|
+
issuer: verifiedAuthorizationRequest.issuer,
|
|
165
|
+
correlationId,
|
|
166
|
+
registrationMetadataPayload: verifiedAuthorizationRequest.registrationMetadataPayload,
|
|
167
|
+
uri,
|
|
168
|
+
name: clientName,
|
|
169
|
+
clientId,
|
|
170
|
+
presentationDefinitions: (await verifiedAuthorizationRequest.authorizationRequest.containsResponseType('vp_token')) ||
|
|
171
|
+
(verifiedAuthorizationRequest.versions.every((version) => version <= SupportedVersion.JWT_VC_PRESENTATION_PROFILE_v1) &&
|
|
172
|
+
verifiedAuthorizationRequest.presentationDefinitions &&
|
|
173
|
+
verifiedAuthorizationRequest.presentationDefinitions.length > 0)
|
|
174
|
+
? verifiedAuthorizationRequest.presentationDefinitions
|
|
175
|
+
: undefined,
|
|
176
|
+
dcqlQuery: verifiedAuthorizationRequest.dcqlQuery,
|
|
177
|
+
};
|
|
212
178
|
}
|
|
213
|
-
determineCorrelationId(uri, verifiedAuthorizationRequest, clientName, context) {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
}
|
|
226
|
-
throw new Error("Can't determine correlationId from request");
|
|
227
|
-
});
|
|
179
|
+
async determineCorrelationId(uri, verifiedAuthorizationRequest, clientName, context) {
|
|
180
|
+
if (uri) {
|
|
181
|
+
return (await translateCorrelationIdToName(uri.hostname, context)) ?? uri.hostname;
|
|
182
|
+
}
|
|
183
|
+
if (verifiedAuthorizationRequest.issuer) {
|
|
184
|
+
const issuerHostname = verifiedAuthorizationRequest.issuer.split('://')[1];
|
|
185
|
+
return (await translateCorrelationIdToName(issuerHostname, context)) ?? issuerHostname;
|
|
186
|
+
}
|
|
187
|
+
if (clientName) {
|
|
188
|
+
return clientName;
|
|
189
|
+
}
|
|
190
|
+
throw new Error("Can't determine correlationId from request");
|
|
228
191
|
}
|
|
229
|
-
siopRetrieveContact(args, context) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
correlationId: authorizationRequestData.correlationId,
|
|
243
|
-
},
|
|
192
|
+
async siopRetrieveContact(args, context) {
|
|
193
|
+
const { authorizationRequestData } = args;
|
|
194
|
+
const { agent } = context;
|
|
195
|
+
if (authorizationRequestData === undefined) {
|
|
196
|
+
return Promise.reject(Error('Missing authorization request data in context'));
|
|
197
|
+
}
|
|
198
|
+
return agent
|
|
199
|
+
.cmGetContacts({
|
|
200
|
+
filter: [
|
|
201
|
+
{
|
|
202
|
+
identities: {
|
|
203
|
+
identifier: {
|
|
204
|
+
correlationId: authorizationRequestData.correlationId,
|
|
244
205
|
},
|
|
245
206
|
},
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
})
|
|
210
|
+
.then((contacts) => (contacts.length === 1 ? contacts[0] : undefined));
|
|
250
211
|
}
|
|
251
|
-
siopAddContactIdentity(args, context) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
212
|
+
async siopAddContactIdentity(args, context) {
|
|
213
|
+
const { agent } = context;
|
|
214
|
+
const { contact, authorizationRequestData } = args;
|
|
215
|
+
if (contact === undefined) {
|
|
216
|
+
return Promise.reject(Error('Missing contact in context'));
|
|
217
|
+
}
|
|
218
|
+
if (authorizationRequestData === undefined) {
|
|
219
|
+
return Promise.reject(Error('Missing authorization request data in context'));
|
|
220
|
+
}
|
|
221
|
+
// TODO: Makes sense to move these types of common queries/retrievals to the SIOP auth request object
|
|
222
|
+
const clientId = authorizationRequestData.clientId ?? authorizationRequestData.issuer;
|
|
223
|
+
const correlationId = clientId
|
|
224
|
+
? clientId.startsWith('did:')
|
|
225
|
+
? clientId
|
|
226
|
+
: `${new URL(clientId).protocol}//${new URL(clientId).hostname}`
|
|
227
|
+
: undefined;
|
|
228
|
+
if (correlationId) {
|
|
229
|
+
const identity = {
|
|
230
|
+
alias: correlationId,
|
|
231
|
+
origin: IdentityOrigin.EXTERNAL,
|
|
232
|
+
roles: [CredentialRole.ISSUER],
|
|
233
|
+
identifier: {
|
|
234
|
+
type: correlationId.startsWith('did:') ? CorrelationIdentifierType.DID : CorrelationIdentifierType.URL,
|
|
235
|
+
correlationId,
|
|
236
|
+
},
|
|
237
|
+
};
|
|
238
|
+
const addedIdentity = await agent.cmAddIdentity({ contactId: contact.id, identity });
|
|
239
|
+
await context.agent.emit(Siopv2HolderEvent.CONTACT_IDENTITY_CREATED, {
|
|
240
|
+
contactId: contact.id,
|
|
241
|
+
identity: addedIdentity,
|
|
242
|
+
});
|
|
243
|
+
logger.info(`Contact identity created: ${JSON.stringify(addedIdentity)}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
async siopSendResponse(args, context) {
|
|
247
|
+
const { didAuthConfig, authorizationRequestData, selectedCredentials, isFirstParty } = args;
|
|
248
|
+
if (didAuthConfig === undefined) {
|
|
249
|
+
return Promise.reject(Error('Missing config in context'));
|
|
250
|
+
}
|
|
251
|
+
if (authorizationRequestData === undefined) {
|
|
252
|
+
return Promise.reject(Error('Missing authorization request data in context'));
|
|
253
|
+
}
|
|
254
|
+
const pex = new PEX({ hasher: this.hasher });
|
|
255
|
+
const verifiableCredentialsWithDefinition = [];
|
|
256
|
+
const dcqlCredentialsWithCredentials = new Map();
|
|
257
|
+
if (Array.isArray(authorizationRequestData.presentationDefinitions) && authorizationRequestData?.presentationDefinitions.length > 0) {
|
|
258
|
+
try {
|
|
259
|
+
authorizationRequestData.presentationDefinitions?.forEach((presentationDefinition) => {
|
|
260
|
+
const { areRequiredCredentialsPresent, verifiableCredential: verifiableCredentials } = pex.selectFrom(presentationDefinition.definition, selectedCredentials.map((udc) => udc.originalVerifiableCredential));
|
|
261
|
+
if (areRequiredCredentialsPresent !== Status.ERROR && verifiableCredentials) {
|
|
262
|
+
let uniqueDigitalCredentials = [];
|
|
263
|
+
uniqueDigitalCredentials = verifiableCredentials.map((vc) => {
|
|
264
|
+
// @ts-ignore FIXME Funke
|
|
265
|
+
const hash = typeof vc === 'string' ? computeEntryHash(vc.split('~'[0])) : computeEntryHash(vc);
|
|
266
|
+
const udc = selectedCredentials.find((udc) => udc.hash == hash || udc.originalVerifiableCredential == vc);
|
|
267
|
+
if (!udc) {
|
|
268
|
+
throw Error(`UniqueDigitalCredential could not be found in store. Either the credential is not present in the store or the hash is not correct.`);
|
|
269
|
+
}
|
|
270
|
+
return udc;
|
|
271
|
+
});
|
|
272
|
+
verifiableCredentialsWithDefinition.push({
|
|
273
|
+
definition: presentationDefinition,
|
|
274
|
+
credentials: uniqueDigitalCredentials,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
283
277
|
});
|
|
284
|
-
logger.info(`Contact identity created: ${JSON.stringify(addedIdentity)}`);
|
|
285
278
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
siopSendResponse(args, context) {
|
|
289
|
-
return __awaiter(this, void 0, void 0, function* () {
|
|
290
|
-
var _a;
|
|
291
|
-
const { didAuthConfig, authorizationRequestData, selectedCredentials, isFirstParty } = args;
|
|
292
|
-
if (didAuthConfig === undefined) {
|
|
293
|
-
return Promise.reject(Error('Missing config in context'));
|
|
279
|
+
catch (e) {
|
|
280
|
+
return Promise.reject(e);
|
|
294
281
|
}
|
|
295
|
-
if (
|
|
296
|
-
return Promise.reject(Error('
|
|
282
|
+
if (verifiableCredentialsWithDefinition.length === 0) {
|
|
283
|
+
return Promise.reject(Error('None of the selected credentials match any of the presentation definitions.'));
|
|
297
284
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if (
|
|
285
|
+
}
|
|
286
|
+
else if (authorizationRequestData.dcqlQuery) {
|
|
287
|
+
//TODO Only SD-JWT and MSO MDOC are supported at the moment
|
|
288
|
+
if (this.hasMDocCredentials(selectedCredentials) || this.hasSdJwtCredentials(selectedCredentials)) {
|
|
302
289
|
try {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
});
|
|
316
|
-
verifiableCredentialsWithDefinition.push({
|
|
317
|
-
definition: presentationDefinition,
|
|
318
|
-
credentials: uniqueDigitalCredentials,
|
|
319
|
-
});
|
|
290
|
+
selectedCredentials.forEach((vc) => {
|
|
291
|
+
if (this.isSdJwtCredential(vc)) {
|
|
292
|
+
const payload = vc.originalVerifiableCredential.decodedPayload;
|
|
293
|
+
const result = {
|
|
294
|
+
claims: payload,
|
|
295
|
+
vct: payload.vct,
|
|
296
|
+
credential_format: 'vc+sd-jwt',
|
|
297
|
+
};
|
|
298
|
+
dcqlCredentialsWithCredentials.set(result, vc);
|
|
299
|
+
//FIXME MDoc namespaces are incompatible: array of strings vs complex object - https://sphereon.atlassian.net/browse/SPRIND-143
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
throw Error(`Invalid credential format: ${vc.digitalCredential.documentFormat}`);
|
|
320
303
|
}
|
|
321
304
|
});
|
|
322
305
|
}
|
|
323
306
|
catch (e) {
|
|
324
307
|
return Promise.reject(e);
|
|
325
308
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
//TODO Only SD-JWT and MSO MDOC are supported at the moment
|
|
332
|
-
if (this.hasMDocCredentials(selectedCredentials) || this.hasSdJwtCredentials(selectedCredentials)) {
|
|
333
|
-
try {
|
|
334
|
-
selectedCredentials.forEach((vc) => {
|
|
335
|
-
if (this.isSdJwtCredential(vc)) {
|
|
336
|
-
const payload = vc.originalVerifiableCredential.decodedPayload;
|
|
337
|
-
const result = {
|
|
338
|
-
claims: payload,
|
|
339
|
-
vct: payload.vct,
|
|
340
|
-
credential_format: 'vc+sd-jwt',
|
|
341
|
-
};
|
|
342
|
-
dcqlCredentialsWithCredentials.set(result, vc);
|
|
343
|
-
//FIXME MDoc namespaces are incompatible: array of strings vs complex object - https://sphereon.atlassian.net/browse/SPRIND-143
|
|
344
|
-
}
|
|
345
|
-
else {
|
|
346
|
-
throw Error(`Invalid credential format: ${vc.digitalCredential.documentFormat}`);
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
}
|
|
350
|
-
catch (e) {
|
|
351
|
-
return Promise.reject(e);
|
|
352
|
-
}
|
|
353
|
-
const dcqlPresentationRecord = {};
|
|
354
|
-
const queryResult = dcql_1.DcqlQuery.query(authorizationRequestData.dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()));
|
|
355
|
-
for (const [key, value] of Object.entries(queryResult.credential_matches)) {
|
|
356
|
-
if (value.success) {
|
|
357
|
-
dcqlPresentationRecord[key] = this.retrieveEncodedCredential(dcqlCredentialsWithCredentials.get(value.output));
|
|
358
|
-
}
|
|
309
|
+
const dcqlPresentationRecord = {};
|
|
310
|
+
const queryResult = DcqlQuery.query(authorizationRequestData.dcqlQuery, Array.from(dcqlCredentialsWithCredentials.keys()));
|
|
311
|
+
for (const [key, value] of Object.entries(queryResult.credential_matches)) {
|
|
312
|
+
if (value.success) {
|
|
313
|
+
dcqlPresentationRecord[key] = this.retrieveEncodedCredential(dcqlCredentialsWithCredentials.get(value.output));
|
|
359
314
|
}
|
|
360
315
|
}
|
|
361
316
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
317
|
+
}
|
|
318
|
+
const response = await siopSendAuthorizationResponse(ConnectionType.SIOPv2_OpenID4VP, {
|
|
319
|
+
sessionId: didAuthConfig.sessionId,
|
|
320
|
+
...(args.idOpts && { idOpts: args.idOpts }),
|
|
321
|
+
...(authorizationRequestData.presentationDefinitions !== undefined && { verifiableCredentialsWithDefinition }),
|
|
322
|
+
isFirstParty,
|
|
323
|
+
hasher: this.hasher,
|
|
324
|
+
}, context);
|
|
325
|
+
const contentType = response.headers.get('content-type') || '';
|
|
326
|
+
let responseBody = null;
|
|
327
|
+
const text = await response.text();
|
|
328
|
+
if (text) {
|
|
329
|
+
responseBody = contentType.includes('application/json') || text.startsWith('{') ? JSON.parse(text) : text;
|
|
330
|
+
}
|
|
331
|
+
return {
|
|
332
|
+
body: responseBody,
|
|
333
|
+
url: response?.url,
|
|
334
|
+
queryParams: decodeUriAsJson(response?.url),
|
|
335
|
+
};
|
|
375
336
|
}
|
|
376
|
-
|
|
377
|
-
return
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
337
|
+
hasMDocCredentials = (credentials) => {
|
|
338
|
+
return credentials.some(this.isMDocCredential);
|
|
339
|
+
};
|
|
340
|
+
isMDocCredential = (credential) => {
|
|
341
|
+
return (credential.digitalCredential.documentFormat === CredentialDocumentFormat.MSO_MDOC &&
|
|
342
|
+
credential.digitalCredential.documentType === DocumentType.VC);
|
|
343
|
+
};
|
|
344
|
+
hasSdJwtCredentials = (credentials) => {
|
|
345
|
+
return credentials.some(this.isSdJwtCredential);
|
|
346
|
+
};
|
|
347
|
+
isSdJwtCredential = (credential) => {
|
|
348
|
+
return (credential.digitalCredential.documentFormat === CredentialDocumentFormat.SD_JWT && credential.digitalCredential.documentType === DocumentType.VC);
|
|
349
|
+
};
|
|
350
|
+
retrieveEncodedCredential = (credential) => {
|
|
351
|
+
return credential.originalVerifiableCredential !== undefined &&
|
|
352
|
+
credential.originalVerifiableCredential !== null &&
|
|
353
|
+
credential?.originalVerifiableCredential?.compactSdJwtVc !== undefined &&
|
|
354
|
+
credential?.originalVerifiableCredential?.compactSdJwtVc !== null
|
|
355
|
+
? credential.originalVerifiableCredential.compactSdJwtVc
|
|
356
|
+
: credential.originalVerifiableCredential;
|
|
357
|
+
};
|
|
358
|
+
async siopGetSelectableCredentials(args, context) {
|
|
359
|
+
const { authorizationRequestData } = args;
|
|
360
|
+
if (!authorizationRequestData ||
|
|
361
|
+
!authorizationRequestData.presentationDefinitions ||
|
|
362
|
+
authorizationRequestData.presentationDefinitions.length === 0) {
|
|
363
|
+
return Promise.reject(Error('Missing required fields in arguments or context'));
|
|
364
|
+
}
|
|
365
|
+
if (authorizationRequestData.presentationDefinitions.length > 1) {
|
|
366
|
+
return Promise.reject(Error('Multiple presentation definitions present'));
|
|
367
|
+
}
|
|
368
|
+
return getSelectableCredentials(authorizationRequestData.presentationDefinitions[0].definition, context);
|
|
389
369
|
}
|
|
390
370
|
}
|
|
391
|
-
exports.DidAuthSiopOpAuthenticator = DidAuthSiopOpAuthenticator;
|
|
392
371
|
//# sourceMappingURL=DidAuthSiopOpAuthenticator.js.map
|