@velocitycareerlabs/server-webwallet 1.26.0-dev-build.1d89674dc → 1.26.0-dev-build.1f5f235dd
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/package.json +3 -3
- package/src/controllers/disclosures/controller.js +76 -33
- package/src/controllers/disclosures/schemas/webwallet-accept-presentation-request-body.schema.js +1 -1
- package/src/controllers/disclosures/schemas/webwallet-accept-presentation-response.schema.js +7 -1
- package/src/entities/feeds/orchestrators/feedsService.js +2 -9
- package/src/entities/feeds/repos/feeds.repo.js +2 -0
- package/src/entities/feeds/schemas/webwallet-feed.schema.js +10 -0
- package/test/disclosures-controller/disclosure-credentials.test.js +51 -1
- package/test/factories/feeds.js +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velocitycareerlabs/server-webwallet",
|
|
3
|
-
"version": "1.26.0-dev-build.
|
|
3
|
+
"version": "1.26.0-dev-build.1f5f235dd",
|
|
4
4
|
"description": "Web Wallet application",
|
|
5
5
|
"repository": "https://github.com/velocitycareerlabs/packages",
|
|
6
6
|
"engines": {
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"@fastify/swagger": "^9.0.0",
|
|
34
34
|
"@fastify/swagger-ui": "^5.0.0",
|
|
35
35
|
"@spencejs/spence-mongo-repos": "^0.10.2",
|
|
36
|
-
"@velocitycareerlabs/migrations": "1.26.0-dev-build.
|
|
36
|
+
"@velocitycareerlabs/migrations": "1.26.0-dev-build.1f5f235dd",
|
|
37
37
|
"@verii/auth": "1.0.0-pre.1757279740",
|
|
38
38
|
"@verii/common-functions": "1.0.0-pre.1757279740",
|
|
39
39
|
"@verii/common-schemas": "1.0.0-pre.1757279740",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"nodemon": "3.1.10",
|
|
74
74
|
"prettier": "2.8.8"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "8150ae28eac5fe570bea11453ae545c9f3dd74e5"
|
|
77
77
|
}
|
|
@@ -99,15 +99,7 @@ const disclosureController = async (fastify) => {
|
|
|
99
99
|
repos: { accounts },
|
|
100
100
|
config: { careerWalletAdminAccessToken },
|
|
101
101
|
}) => {
|
|
102
|
-
const
|
|
103
|
-
filter: { userId: user.sub },
|
|
104
|
-
});
|
|
105
|
-
const didJwk =
|
|
106
|
-
didKeyMetadatum &&
|
|
107
|
-
Array.isArray(didKeyMetadatum) &&
|
|
108
|
-
didKeyMetadatum.length > 0
|
|
109
|
-
? VCLDidJwk.fromJSON(didKeyMetadatum[0])
|
|
110
|
-
: null;
|
|
102
|
+
const didJwk = await getDidJwk(accounts, user);
|
|
111
103
|
|
|
112
104
|
const presentationRequest = await vclSdk.getPresentationRequest(
|
|
113
105
|
new VCLPresentationRequestDescriptor(
|
|
@@ -118,38 +110,43 @@ const disclosureController = async (fastify) => {
|
|
|
118
110
|
)
|
|
119
111
|
);
|
|
120
112
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
new VCLAuthTokenDescriptor(presentationRequest)
|
|
113
|
+
if (!presentationRequest.feed && !body.credentials.length) {
|
|
114
|
+
throw newError.BadRequest(
|
|
115
|
+
'body/credentials must NOT have fewer than 1 items'
|
|
125
116
|
);
|
|
126
117
|
}
|
|
127
118
|
|
|
119
|
+
const authToken = await getAuthToken(vclSdk, presentationRequest);
|
|
120
|
+
|
|
128
121
|
const { payload: presentation } = jwtDecode(
|
|
129
122
|
presentationRequest.jwt.encodedJwt
|
|
130
123
|
);
|
|
131
124
|
|
|
132
|
-
|
|
133
|
-
new VCLPresentationSubmission(presentationRequest, body.credentials),
|
|
134
|
-
authToken
|
|
135
|
-
);
|
|
125
|
+
let disclosure = {};
|
|
136
126
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
127
|
+
if (body.credentials.length) {
|
|
128
|
+
const submissionResult = await vclSdk.submitPresentation(
|
|
129
|
+
new VCLPresentationSubmission(presentationRequest, body.credentials),
|
|
130
|
+
authToken
|
|
131
|
+
);
|
|
140
132
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
133
|
+
const { did: userDid } = await repos.accounts.findOne({
|
|
134
|
+
filter: { userId: user.sub },
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
disclosure = await repos.disclosures.insert({
|
|
138
|
+
auth0UserId: user.sub,
|
|
139
|
+
userDid,
|
|
140
|
+
presentation,
|
|
141
|
+
encodedJwt: presentationRequest.jwt.encodedJwt,
|
|
142
|
+
jti: submissionResult.jti,
|
|
143
|
+
submissionId: submissionResult.submissionId,
|
|
144
|
+
presentationDefinitionId: presentation.presentation_definition.id,
|
|
145
|
+
token: submissionResult.sessionToken.value,
|
|
146
|
+
sharedCredentials: body.credentials.map(({ id }) => id),
|
|
147
|
+
type: body.type,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
153
150
|
|
|
154
151
|
if (presentationRequest.feed) {
|
|
155
152
|
await repos.feeds.insert({
|
|
@@ -162,10 +159,13 @@ const disclosureController = async (fastify) => {
|
|
|
162
159
|
presentation.presentation_definition.input_descriptors.map(
|
|
163
160
|
(descriptor) => descriptor.id
|
|
164
161
|
),
|
|
162
|
+
inputDescriptors:
|
|
163
|
+
presentation.presentation_definition.input_descriptors,
|
|
164
|
+
presentationMetadata: presentation.metadata,
|
|
165
165
|
});
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
return { disclosure };
|
|
168
|
+
return { disclosure: mapDisclosureForResponse(disclosure) };
|
|
169
169
|
}
|
|
170
170
|
);
|
|
171
171
|
|
|
@@ -238,4 +238,47 @@ const disclosureController = async (fastify) => {
|
|
|
238
238
|
);
|
|
239
239
|
};
|
|
240
240
|
|
|
241
|
+
const getAuthToken = async (vclSdk, presentationRequest) => {
|
|
242
|
+
if (presentationRequest.feed) {
|
|
243
|
+
return vclSdk.getAuthToken(new VCLAuthTokenDescriptor(presentationRequest));
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const getDidJwk = async (accounts, user) => {
|
|
249
|
+
const { didKeyMetadatum } = await accounts.findOne({
|
|
250
|
+
filter: { userId: user.sub },
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return didKeyMetadatum &&
|
|
254
|
+
Array.isArray(didKeyMetadatum) &&
|
|
255
|
+
didKeyMetadatum.length > 0
|
|
256
|
+
? VCLDidJwk.fromJSON(didKeyMetadatum[0])
|
|
257
|
+
: null;
|
|
258
|
+
};
|
|
259
|
+
|
|
241
260
|
module.exports = disclosureController;
|
|
261
|
+
|
|
262
|
+
const mapDisclosureForResponse = (disclosure) => {
|
|
263
|
+
if (disclosure == null || Object.keys(disclosure).length === 0) {
|
|
264
|
+
return {};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const toIso = (d) =>
|
|
268
|
+
d && typeof d.toISOString === 'function' ? d.toISOString() : d;
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
id: disclosure.id ?? disclosure._id?.toString?.(),
|
|
272
|
+
jti: disclosure.jti,
|
|
273
|
+
submissionId: disclosure.submissionId,
|
|
274
|
+
presentationDefinitionId: disclosure.presentationDefinitionId,
|
|
275
|
+
encodedJwt: disclosure.encodedJwt,
|
|
276
|
+
presentation: disclosure.presentation,
|
|
277
|
+
token: disclosure.token,
|
|
278
|
+
sharedCredentials: disclosure.sharedCredentials,
|
|
279
|
+
type: disclosure.type,
|
|
280
|
+
feed: disclosure.feed,
|
|
281
|
+
createdAt: toIso(disclosure.createdAt),
|
|
282
|
+
updatedAt: toIso(disclosure.updatedAt),
|
|
283
|
+
};
|
|
284
|
+
};
|
package/src/controllers/disclosures/schemas/webwallet-accept-presentation-response.schema.js
CHANGED
|
@@ -5,10 +5,16 @@ const webWalletAcceptPresentationResponseSchema = {
|
|
|
5
5
|
type: 'object',
|
|
6
6
|
properties: {
|
|
7
7
|
disclosure: {
|
|
8
|
-
|
|
8
|
+
oneOf: [
|
|
9
|
+
{
|
|
10
|
+
$ref: 'https://velocitycareerlabs.io/webwallet-disclosure.schema.json',
|
|
11
|
+
},
|
|
12
|
+
{ type: 'object', properties: {}, additionalProperties: false },
|
|
13
|
+
],
|
|
9
14
|
},
|
|
10
15
|
},
|
|
11
16
|
required: ['disclosure'],
|
|
17
|
+
additionalProperties: false,
|
|
12
18
|
};
|
|
13
19
|
|
|
14
20
|
module.exports = {
|
|
@@ -24,7 +24,7 @@ const extractDisclosureId = (presentationDefinitionId) => {
|
|
|
24
24
|
/**
|
|
25
25
|
* Create a map of disclosures grouped by disclosureId
|
|
26
26
|
* @param {Array} disclosures - Array of disclosure objects
|
|
27
|
-
* @returns {Object} Map with disclosureId as key and {
|
|
27
|
+
* @returns {Object} Map with disclosureId as key and {sharedCredentials} as value
|
|
28
28
|
*/
|
|
29
29
|
const createDisclosureMap = (disclosures) => {
|
|
30
30
|
if (!Array.isArray(disclosures)) {
|
|
@@ -40,11 +40,7 @@ const createDisclosureMap = (disclosures) => {
|
|
|
40
40
|
return {
|
|
41
41
|
...disclosureMatched,
|
|
42
42
|
[disclosureId]: {
|
|
43
|
-
presentationMetadata: disclosure.presentation?.metadata || {},
|
|
44
43
|
sharedCredentials: disclosure.sharedCredentials || [],
|
|
45
|
-
inputDescriptors:
|
|
46
|
-
disclosure.presentation.presentation_definition
|
|
47
|
-
?.input_descriptors || [],
|
|
48
44
|
},
|
|
49
45
|
};
|
|
50
46
|
}
|
|
@@ -63,7 +59,7 @@ const createDisclosureMap = (disclosures) => {
|
|
|
63
59
|
};
|
|
64
60
|
|
|
65
61
|
/**
|
|
66
|
-
* Enrich feeds with
|
|
62
|
+
* Enrich feeds with shared credentials from disclosures
|
|
67
63
|
* @param {Array} feeds - Array of feed objects
|
|
68
64
|
* @param {Object} disclosureMap - Map of disclosures by disclosureId
|
|
69
65
|
* @returns {Array} Enriched feeds array
|
|
@@ -75,11 +71,8 @@ const enrichFeedsWithDisclosureData = (feeds, disclosureMap) => {
|
|
|
75
71
|
|
|
76
72
|
return feeds.map((feed) => ({
|
|
77
73
|
...feed,
|
|
78
|
-
presentationMetadata:
|
|
79
|
-
disclosureMap[feed.disclosureId]?.presentationMetadata || {},
|
|
80
74
|
sharedCredentials:
|
|
81
75
|
disclosureMap[feed.disclosureId]?.sharedCredentials || [],
|
|
82
|
-
inputDescriptors: disclosureMap[feed.disclosureId]?.inputDescriptors || [],
|
|
83
76
|
}));
|
|
84
77
|
};
|
|
85
78
|
|
|
@@ -16,6 +16,14 @@ const webWalletFeedSchema = {
|
|
|
16
16
|
type: 'array',
|
|
17
17
|
items: { type: 'string' },
|
|
18
18
|
},
|
|
19
|
+
inputDescriptors: {
|
|
20
|
+
type: 'array',
|
|
21
|
+
items: { type: 'object', additionalProperties: true },
|
|
22
|
+
},
|
|
23
|
+
presentationMetadata: {
|
|
24
|
+
type: 'object',
|
|
25
|
+
additionalProperties: true,
|
|
26
|
+
},
|
|
19
27
|
createdAt: { type: 'string' },
|
|
20
28
|
updatedAt: { type: 'string' },
|
|
21
29
|
revokedAt: { type: 'string' },
|
|
@@ -28,6 +36,8 @@ const webWalletFeedSchema = {
|
|
|
28
36
|
'disclosureId',
|
|
29
37
|
'inspectorDid',
|
|
30
38
|
'credentialTypes',
|
|
39
|
+
'inputDescriptors',
|
|
40
|
+
'presentationMetadata',
|
|
31
41
|
'createdAt',
|
|
32
42
|
'updatedAt',
|
|
33
43
|
],
|
|
@@ -309,6 +309,8 @@ describe('Test disclosure credentials controller', () => {
|
|
|
309
309
|
_id: expect.any(ObjectId),
|
|
310
310
|
auth0UserId: userId,
|
|
311
311
|
authToken: expect.any(Object),
|
|
312
|
+
inputDescriptors: expect.any(Object),
|
|
313
|
+
presentationMetadata: expect.any(Object),
|
|
312
314
|
deeplink: inspectionDeepLink,
|
|
313
315
|
disclosureId: expect.any(String),
|
|
314
316
|
inspectorDid: expect.any(String),
|
|
@@ -402,7 +404,55 @@ describe('Test disclosure credentials controller', () => {
|
|
|
402
404
|
);
|
|
403
405
|
});
|
|
404
406
|
|
|
407
|
+
it('should return 200 if no credentials but feed is true', async () => {
|
|
408
|
+
const presentationRequestFeedMock = {
|
|
409
|
+
...require('./mocks').presentationRequestMock,
|
|
410
|
+
feed: true,
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
vclSdk.getPresentationRequest.mockResolvedValueOnce(
|
|
414
|
+
presentationRequestFeedMock
|
|
415
|
+
);
|
|
416
|
+
|
|
417
|
+
const response = await fastify.injectJson({
|
|
418
|
+
method: 'POST',
|
|
419
|
+
url: '/disclosures/accept-presentation-request',
|
|
420
|
+
payload: {
|
|
421
|
+
link: inspectionDeepLink,
|
|
422
|
+
credentials: [],
|
|
423
|
+
},
|
|
424
|
+
headers: {
|
|
425
|
+
authorization: `Bearer ${idToken}`,
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
expect(response.statusCode).toBe(200);
|
|
430
|
+
|
|
431
|
+
expect(vclSdk.getAuthToken).toHaveBeenCalledTimes(1);
|
|
432
|
+
|
|
433
|
+
const savedDisclosure = await mongoDb()
|
|
434
|
+
.collection('disclosures')
|
|
435
|
+
.findOne();
|
|
436
|
+
|
|
437
|
+
expect(savedDisclosure).toBeNull();
|
|
438
|
+
|
|
439
|
+
const savedFeed = await mongoDb().collection('feeds').findOne();
|
|
440
|
+
|
|
441
|
+
expect(savedFeed).toBeTruthy();
|
|
442
|
+
|
|
443
|
+
expect(response.json).toHaveProperty('disclosure');
|
|
444
|
+
});
|
|
445
|
+
|
|
405
446
|
it('should return 400 if the list of credentials is empty array', async () => {
|
|
447
|
+
const presentationRequestFeedMock = {
|
|
448
|
+
...require('./mocks').presentationRequestMock,
|
|
449
|
+
feed: false,
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
vclSdk.getPresentationRequest.mockResolvedValueOnce(
|
|
453
|
+
presentationRequestFeedMock
|
|
454
|
+
);
|
|
455
|
+
|
|
406
456
|
const response = await fastify.injectJson({
|
|
407
457
|
method: 'POST',
|
|
408
458
|
url: '/disclosures/accept-presentation-request',
|
|
@@ -420,7 +470,7 @@ describe('Test disclosure credentials controller', () => {
|
|
|
420
470
|
expect(response.json).toEqual(
|
|
421
471
|
errorResponseMatcher({
|
|
422
472
|
error: 'Bad Request',
|
|
423
|
-
errorCode: '
|
|
473
|
+
errorCode: 'web_wallet_server_error',
|
|
424
474
|
message: 'body/credentials must NOT have fewer than 1 items',
|
|
425
475
|
statusCode: 400,
|
|
426
476
|
})
|
package/test/factories/feeds.js
CHANGED
|
@@ -72,6 +72,19 @@ module.exports = (app) => {
|
|
|
72
72
|
},
|
|
73
73
|
tokenType: 'Bearer',
|
|
74
74
|
},
|
|
75
|
+
presentationMetadata: {
|
|
76
|
+
client_name: 'University of Massachusetts Amherst',
|
|
77
|
+
logo_uri: 'https://example.com/logo.png',
|
|
78
|
+
tos_uri: 'https://example.com/terms',
|
|
79
|
+
feed: true,
|
|
80
|
+
},
|
|
81
|
+
inputDescriptors: [
|
|
82
|
+
{ id: 'certification_input_1', name: 'Certification' },
|
|
83
|
+
{ id: 'license_input_1', name: 'License' },
|
|
84
|
+
{ id: 'assessment_input_1', name: 'Assessment' },
|
|
85
|
+
{ id: 'open_badge_input_1', name: 'Open Badge' },
|
|
86
|
+
],
|
|
87
|
+
sharedCredentials: ['credential1', 'credential2'],
|
|
75
88
|
deeplink:
|
|
76
89
|
'velocity-network-devnet://inspect?request_uri=https://devagent.velocitycareerlabs.io/api/holder/v0.6/org/did:web:devregistrar.velocitynetwork.foundation:d:www.umass.edu/inspect/get-presentation-request?id=68736ac609c41e44796d040e%26inspectorDid%3Ddid%3Aweb%3Adevregistrar.velocitynetwork.foundation%3Ad%3Awww.umass.edu%26vendorOriginContext%3DAdam',
|
|
77
90
|
disclosureId: '68736ac609c41e44796d040e',
|