@velocitycareerlabs/server-webwallet 1.26.0-dev-build.1d30757e5 → 1.26.0-dev-build.1cce50406

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@velocitycareerlabs/server-webwallet",
3
- "version": "1.26.0-dev-build.1d30757e5",
3
+ "version": "1.26.0-dev-build.1cce50406",
4
4
  "description": "Web Wallet application",
5
5
  "repository": "https://github.com/velocitycareerlabs/packages",
6
6
  "engines": {
@@ -33,17 +33,17 @@
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.1d30757e5",
37
- "@verii/auth": "1.0.0-pre.1757279740",
38
- "@verii/common-functions": "1.0.0-pre.1757279740",
39
- "@verii/common-schemas": "1.0.0-pre.1757279740",
40
- "@verii/config": "1.0.0-pre.1757279740",
41
- "@verii/fastify-plugins": "1.0.0-pre.1757279740",
42
- "@verii/jwt": "1.0.0-pre.1757279740",
43
- "@verii/request": "1.0.0-pre.1757279740",
44
- "@verii/server-provider": "1.0.0-pre.1757279740",
45
- "@verii/vc-checks": "1.0.0-pre.1757279740",
46
- "@verii/vnf-nodejs-wallet-sdk": "1.0.0-pre.1757279740",
36
+ "@velocitycareerlabs/migrations": "1.26.0-dev-build.1cce50406",
37
+ "@verii/auth": "1.0.0-pre.1757456926",
38
+ "@verii/common-functions": "1.0.0-pre.1757456926",
39
+ "@verii/common-schemas": "1.0.0-pre.1757456926",
40
+ "@verii/config": "1.0.0-pre.1757456926",
41
+ "@verii/fastify-plugins": "1.0.0-pre.1757456926",
42
+ "@verii/jwt": "1.0.0-pre.1757456926",
43
+ "@verii/request": "1.0.0-pre.1757456926",
44
+ "@verii/server-provider": "1.0.0-pre.1757456926",
45
+ "@verii/vc-checks": "1.0.0-pre.1757456926",
46
+ "@verii/vnf-nodejs-wallet-sdk": "1.0.0-pre.1757456926",
47
47
  "blueimp-md5": "2.19.0",
48
48
  "env-var": "^7.0.0",
49
49
  "fastify": "^5.0.0",
@@ -56,8 +56,8 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@spencejs/spence-factories": "0.10.2",
59
- "@verii/crypto": "1.0.0-pre.1757279740",
60
- "@verii/tests-helpers": "1.0.0-pre.1757279740",
59
+ "@verii/crypto": "1.0.0-pre.1757456926",
60
+ "@verii/tests-helpers": "1.0.0-pre.1757456926",
61
61
  "dotenv": "16.6.1",
62
62
  "eslint": "8.57.1",
63
63
  "eslint-config-airbnb-base": "14.2.1",
@@ -73,5 +73,5 @@
73
73
  "nodemon": "3.1.10",
74
74
  "prettier": "2.8.8"
75
75
  },
76
- "gitHead": "8981e5ec5495b0aefb2fe58de95d584941994e60"
76
+ "gitHead": "58afda1bd7fef44c773b88a1fd6685f2ba006c3a"
77
77
  }
@@ -99,15 +99,7 @@ const disclosureController = async (fastify) => {
99
99
  repos: { accounts },
100
100
  config: { careerWalletAdminAccessToken },
101
101
  }) => {
102
- const { didKeyMetadatum } = await accounts.findOne({
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
- let authToken = null;
122
- if (presentationRequest.feed) {
123
- authToken = await vclSdk.getAuthToken(
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
- const submissionResult = await vclSdk.submitPresentation(
133
- new VCLPresentationSubmission(presentationRequest, body.credentials),
134
- authToken
135
- );
125
+ let disclosure = {};
136
126
 
137
- const { did: userDid } = await repos.accounts.findOne({
138
- filter: { userId: user.sub },
139
- });
127
+ if (body.credentials.length) {
128
+ const submissionResult = await vclSdk.submitPresentation(
129
+ new VCLPresentationSubmission(presentationRequest, body.credentials),
130
+ authToken
131
+ );
140
132
 
141
- const disclosure = await repos.disclosures.insert({
142
- auth0UserId: user.sub,
143
- userDid,
144
- presentation,
145
- encodedJwt: presentationRequest.jwt.encodedJwt,
146
- jti: submissionResult.jti,
147
- submissionId: submissionResult.submissionId,
148
- presentationDefinitionId: presentation.presentation_definition.id,
149
- token: submissionResult.sessionToken.value,
150
- sharedCredentials: body.credentials.map(({ id }) => id),
151
- type: body.type,
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
+ };
@@ -16,7 +16,7 @@ const webWalletAcceptPresentationRequestBodySchema = {
16
16
  },
17
17
  required: ['inputDescriptor', 'jwtVc', 'id'],
18
18
  },
19
- minItems: 1,
19
+ minItems: 0,
20
20
  },
21
21
  type: { type: 'string', enum: ['public', 'linkedin', 'inspection'] },
22
22
  },
@@ -5,10 +5,16 @@ const webWalletAcceptPresentationResponseSchema = {
5
5
  type: 'object',
6
6
  properties: {
7
7
  disclosure: {
8
- $ref: 'https://velocitycareerlabs.io/webwallet-disclosure.schema.json',
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 {presentationMetadata, sharedCredentials} as value
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 presentation metadata and shared credentials
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
 
@@ -17,6 +17,8 @@ module.exports = (app, _, next = () => {}) => {
17
17
  disclosureId: 1,
18
18
  inspectorDid: 1,
19
19
  credentialTypes: 1,
20
+ inputDescriptors: 1,
21
+ presentationMetadata: 1,
20
22
  createdAt: 1,
21
23
  updatedAt: 1,
22
24
  revokedAt: 1,
@@ -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: 'request_validation_failed',
473
+ errorCode: 'web_wallet_server_error',
424
474
  message: 'body/credentials must NOT have fewer than 1 items',
425
475
  statusCode: 400,
426
476
  })
@@ -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',