@velocitycareerlabs/server-webwallet 1.26.0-dev-build.1ad63e207 → 1.26.0-dev-build.15052cf8c
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/feeds/controller.js +19 -1
- package/src/controllers/feeds/schemas/webwallet-get-feeds-response.schema.js +40 -27
- package/src/entities/feeds/index.js +1 -0
- package/src/entities/feeds/orchestrators/feedsService.js +106 -0
- package/src/entities/feeds/orchestrators/index.js +3 -0
- package/test/feeds-controller.test.js +37 -2
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.15052cf8c",
|
|
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.15052cf8c",
|
|
37
37
|
"@verii/auth": "1.0.0-pre.1754473517",
|
|
38
38
|
"@verii/common-functions": "1.0.0-pre.1754473517",
|
|
39
39
|
"@verii/common-schemas": "1.0.0-pre.1754473517",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"nodemon": "3.1.10",
|
|
74
74
|
"prettier": "2.8.8"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "0784b85e60ee15b52086d6e1ff0196b24e355881"
|
|
77
77
|
}
|
|
@@ -7,6 +7,7 @@ const {
|
|
|
7
7
|
VCLDidJwk,
|
|
8
8
|
VCLAuthTokenDescriptor,
|
|
9
9
|
} = require('@verii/vnf-nodejs-wallet-sdk');
|
|
10
|
+
const { feedsService } = require('../../entities/feeds');
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @param {import('fastify').FastifyInstance} fastify
|
|
@@ -31,9 +32,26 @@ const feedsController = async (fastify) => {
|
|
|
31
32
|
filter: { auth0UserId: user.sub },
|
|
32
33
|
});
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
if (feeds.length === 0) {
|
|
36
|
+
return { feeds: [] };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const disclosureIds = feeds.map((feed) => feed.disclosureId);
|
|
40
|
+
|
|
41
|
+
const disclosures = await repos.disclosures.find({
|
|
42
|
+
filter: feedsService.buildDisclosureFilter(user.sub, disclosureIds),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const disclosureMap = feedsService.createDisclosureMap(disclosures);
|
|
46
|
+
const enrichedFeeds = feedsService.enrichFeedsWithDisclosureData(
|
|
47
|
+
feeds,
|
|
48
|
+
disclosureMap
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
return { feeds: enrichedFeeds };
|
|
35
52
|
}
|
|
36
53
|
);
|
|
54
|
+
|
|
37
55
|
fastify.delete(
|
|
38
56
|
'/:feedId',
|
|
39
57
|
{
|
|
@@ -2,36 +2,49 @@ const webWalletGetFeedsResponseSchema = {
|
|
|
2
2
|
$id: 'https://velocitycareerlabs.io/webwallet-get-feeds-response.schema.json',
|
|
3
3
|
title: 'webwallet-get-feeds-response',
|
|
4
4
|
description: 'an array of feed objects returned by webwallet get feeds',
|
|
5
|
-
type: '
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
5
|
+
type: 'object',
|
|
6
|
+
properties: {
|
|
7
|
+
feeds: {
|
|
8
|
+
type: 'array',
|
|
9
|
+
items: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
id: { type: 'string' },
|
|
13
|
+
inspectorDid: { type: 'string' },
|
|
14
|
+
credentialTypes: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: { type: 'string' },
|
|
17
|
+
},
|
|
18
|
+
presentationMetadata: {
|
|
19
|
+
type: 'object',
|
|
20
|
+
additionalProperties: true,
|
|
21
|
+
},
|
|
22
|
+
sharedCredentials: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
items: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
disclosureId: { type: 'string' },
|
|
29
|
+
createdAt: { type: 'string' },
|
|
30
|
+
updatedAt: { type: 'string' },
|
|
31
|
+
},
|
|
32
|
+
required: [
|
|
33
|
+
'id',
|
|
34
|
+
'disclosureId',
|
|
35
|
+
'inspectorDid',
|
|
36
|
+
'credentialTypes',
|
|
37
|
+
'presentationMetadata',
|
|
38
|
+
'sharedCredentials',
|
|
39
|
+
'createdAt',
|
|
40
|
+
'updatedAt',
|
|
41
|
+
],
|
|
42
|
+
additionalProperties: false,
|
|
18
43
|
},
|
|
19
|
-
createdAt: { type: 'string' },
|
|
20
|
-
updatedAt: { type: 'string' },
|
|
21
|
-
revokedAt: { type: 'string' },
|
|
22
44
|
},
|
|
23
|
-
required: [
|
|
24
|
-
'auth0UserId',
|
|
25
|
-
'authToken',
|
|
26
|
-
'deeplink',
|
|
27
|
-
'disclosureId',
|
|
28
|
-
'inspectorDid',
|
|
29
|
-
'credentialTypes',
|
|
30
|
-
'createdAt',
|
|
31
|
-
'updatedAt',
|
|
32
|
-
],
|
|
33
|
-
additionalProperties: false,
|
|
34
45
|
},
|
|
46
|
+
required: ['feeds'],
|
|
47
|
+
additionalProperties: false,
|
|
35
48
|
};
|
|
36
49
|
|
|
37
50
|
module.exports = {
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract disclosureId from presentationDefinitionId
|
|
3
|
+
* @param {string} presentationDefinitionId - Format: "exchangeId.disclosureId"
|
|
4
|
+
* @returns {string} disclosureId
|
|
5
|
+
*/
|
|
6
|
+
const extractDisclosureId = (presentationDefinitionId) => {
|
|
7
|
+
if (
|
|
8
|
+
!presentationDefinitionId ||
|
|
9
|
+
typeof presentationDefinitionId !== 'string'
|
|
10
|
+
) {
|
|
11
|
+
throw new Error('Invalid presentationDefinitionId');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const parts = presentationDefinitionId.split('.');
|
|
15
|
+
if (parts.length < 2) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
'presentationDefinitionId must contain exchangeId and disclosureId separated by "."'
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return parts[1];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a map of disclosures grouped by disclosureId
|
|
26
|
+
* @param {Array} disclosures - Array of disclosure objects
|
|
27
|
+
* @returns {Object} Map with disclosureId as key and {presentationMetadata, sharedCredentials} as value
|
|
28
|
+
*/
|
|
29
|
+
const createDisclosureMap = (disclosures) => {
|
|
30
|
+
if (!Array.isArray(disclosures)) {
|
|
31
|
+
return {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return disclosures.reduce((disclosureMatched, disclosure) => {
|
|
35
|
+
const disclosureId = extractDisclosureId(
|
|
36
|
+
disclosure.presentationDefinitionId
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (!disclosureMatched[disclosureId]) {
|
|
40
|
+
return {
|
|
41
|
+
...disclosureMatched,
|
|
42
|
+
[disclosureId]: {
|
|
43
|
+
presentationMetadata: disclosure.presentation?.metadata || {},
|
|
44
|
+
sharedCredentials: disclosure.sharedCredentials || [],
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
...disclosureMatched,
|
|
51
|
+
[disclosureId]: {
|
|
52
|
+
...disclosureMatched[disclosureId],
|
|
53
|
+
sharedCredentials: [
|
|
54
|
+
...disclosureMatched[disclosureId].sharedCredentials,
|
|
55
|
+
...(disclosure.sharedCredentials || []),
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}, {});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Enrich feeds with presentation metadata and shared credentials
|
|
64
|
+
* @param {Array} feeds - Array of feed objects
|
|
65
|
+
* @param {Object} disclosureMap - Map of disclosures by disclosureId
|
|
66
|
+
* @returns {Array} Enriched feeds array
|
|
67
|
+
*/
|
|
68
|
+
const enrichFeedsWithDisclosureData = (feeds, disclosureMap) => {
|
|
69
|
+
if (!Array.isArray(feeds)) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return feeds.map((feed) => ({
|
|
74
|
+
...feed,
|
|
75
|
+
presentationMetadata:
|
|
76
|
+
disclosureMap[feed.disclosureId]?.presentationMetadata || {},
|
|
77
|
+
sharedCredentials:
|
|
78
|
+
disclosureMap[feed.disclosureId]?.sharedCredentials || [],
|
|
79
|
+
}));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Build MongoDB filter for finding disclosures by disclosureIds
|
|
84
|
+
* @param {string} auth0UserId - User ID
|
|
85
|
+
* @param {Array} disclosureIds - Array of disclosure IDs
|
|
86
|
+
* @returns {Object} MongoDB filter object
|
|
87
|
+
*/
|
|
88
|
+
const buildDisclosureFilter = (auth0UserId, disclosureIds) => {
|
|
89
|
+
return {
|
|
90
|
+
auth0UserId,
|
|
91
|
+
feed: true,
|
|
92
|
+
$expr: {
|
|
93
|
+
$in: [
|
|
94
|
+
{ $arrayElemAt: [{ $split: ['$presentationDefinitionId', '.'] }, 1] },
|
|
95
|
+
disclosureIds,
|
|
96
|
+
],
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
extractDisclosureId,
|
|
103
|
+
createDisclosureMap,
|
|
104
|
+
enrichFeedsWithDisclosureData,
|
|
105
|
+
buildDisclosureFilter,
|
|
106
|
+
};
|
|
@@ -2,6 +2,7 @@ const { mongoDb } = require('@spencejs/spence-mongo-repos');
|
|
|
2
2
|
const nock = require('nock');
|
|
3
3
|
const buildFastify = require('./helpers/webwallet-build-fastify');
|
|
4
4
|
const initFeedsFactory = require('./factories/feeds');
|
|
5
|
+
const initDisclosuresFactory = require('./factories/disclosures');
|
|
5
6
|
|
|
6
7
|
const auth0Token =
|
|
7
8
|
// eslint-disable-next-line max-len
|
|
@@ -23,18 +24,22 @@ jest.mock('@verii/vnf-nodejs-wallet-sdk', () => {
|
|
|
23
24
|
describe('Feeds Controller', () => {
|
|
24
25
|
let fastify;
|
|
25
26
|
let persistFeeds;
|
|
27
|
+
let persistDisclosures;
|
|
26
28
|
|
|
27
29
|
beforeAll(async () => {
|
|
28
30
|
fastify = buildFastify();
|
|
29
31
|
nock('https://localhost/').get('/.well-known/jwks.json').reply(200, jwks);
|
|
30
32
|
await fastify.ready();
|
|
31
33
|
await mongoDb().collection('feeds').deleteMany({});
|
|
34
|
+
await mongoDb().collection('disclosures').deleteMany({});
|
|
32
35
|
({ persistFeeds } = initFeedsFactory(fastify));
|
|
36
|
+
({ persistDisclosures } = initDisclosuresFactory(fastify));
|
|
33
37
|
});
|
|
34
38
|
|
|
35
39
|
afterEach(async () => {
|
|
36
40
|
nock.cleanAll();
|
|
37
41
|
await mongoDb().collection('feeds').deleteMany({});
|
|
42
|
+
await mongoDb().collection('disclosures').deleteMany({});
|
|
38
43
|
});
|
|
39
44
|
|
|
40
45
|
afterAll(async () => {
|
|
@@ -43,9 +48,27 @@ describe('Feeds Controller', () => {
|
|
|
43
48
|
});
|
|
44
49
|
|
|
45
50
|
describe('getFeeds', () => {
|
|
46
|
-
it('should return list of feeds
|
|
51
|
+
it('should return list of feeds with presentation metadata and shared credentials', async () => {
|
|
52
|
+
const disclosureId = '68736ac609c41e44796d040e';
|
|
53
|
+
const exchangeId = '657b15a1b4de9ac4f06b63ee';
|
|
54
|
+
|
|
47
55
|
await persistFeeds({
|
|
48
56
|
auth0UserId,
|
|
57
|
+
disclosureId,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
await persistDisclosures({
|
|
61
|
+
auth0UserId,
|
|
62
|
+
presentationDefinitionId: `${exchangeId}.${disclosureId}`,
|
|
63
|
+
feed: true,
|
|
64
|
+
sharedCredentials: ['credential1', 'credential2'],
|
|
65
|
+
presentation: {
|
|
66
|
+
metadata: {
|
|
67
|
+
client_name: 'University of Massachusetts Amherst',
|
|
68
|
+
logo_uri: 'https://example.com/logo.png',
|
|
69
|
+
tos_uri: 'https://example.com/terms',
|
|
70
|
+
},
|
|
71
|
+
},
|
|
49
72
|
});
|
|
50
73
|
|
|
51
74
|
const response = await fastify.inject({
|
|
@@ -57,7 +80,19 @@ describe('Feeds Controller', () => {
|
|
|
57
80
|
});
|
|
58
81
|
|
|
59
82
|
expect(response.statusCode).toBe(200);
|
|
60
|
-
|
|
83
|
+
const responseData = response.json();
|
|
84
|
+
expect(responseData).toHaveProperty('feeds');
|
|
85
|
+
expect(responseData.feeds).toHaveLength(1);
|
|
86
|
+
|
|
87
|
+
const feed = responseData.feeds[0];
|
|
88
|
+
expect(feed).toHaveProperty('presentationMetadata');
|
|
89
|
+
expect(feed.presentationMetadata).toEqual({
|
|
90
|
+
client_name: 'University of Massachusetts Amherst',
|
|
91
|
+
logo_uri: 'https://example.com/logo.png',
|
|
92
|
+
tos_uri: 'https://example.com/terms',
|
|
93
|
+
});
|
|
94
|
+
expect(feed).toHaveProperty('sharedCredentials');
|
|
95
|
+
expect(feed.sharedCredentials).toEqual(['credential1', 'credential2']);
|
|
61
96
|
});
|
|
62
97
|
});
|
|
63
98
|
|