@velocitycareerlabs/server-webwallet 1.27.0-dev-build.15bd823d3 → 1.27.0-dev-build.142d72c39
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/README.md +16 -2
- package/migrate-mongo.config.js +4 -2
- package/migrations/20260224143000-normalize-did-key-metadatum-shape.js +127 -0
- package/migrations/environments/localdev.env +0 -0
- package/migrations/environments/prod.env +0 -0
- package/migrations/environments/qa.env +0 -0
- package/migrations/environments/staging.env +0 -0
- package/migrations/environments/test.env +0 -0
- package/package.json +3 -5
- package/src/controllers/accounts/controller.js +8 -1
- package/src/plugins/crypto-services/jwt-sign-service-impl.js +0 -2
- package/test/accounts-controller.test.js +82 -6
- package/test/crypro-services/jwt-sign-service-impl.test.js +18 -1
package/README.md
CHANGED
|
@@ -6,6 +6,20 @@
|
|
|
6
6
|
|
|
7
7
|
Run docker containers to develop Back-end locally and run tests.
|
|
8
8
|
|
|
9
|
-
### `cd ./servers/webwallet && yarn start:
|
|
9
|
+
### `cd ./servers/webwallet && yarn start:dev`
|
|
10
10
|
|
|
11
|
-
Run WebWallet backend
|
|
11
|
+
Run WebWallet backend locally on http://localhost:3010/.
|
|
12
|
+
|
|
13
|
+
## Data Migrations
|
|
14
|
+
|
|
15
|
+
From `servers/webwallet`:
|
|
16
|
+
- `yarn migrate:create migration-name-kebab-case-format`: create a migration in `migrations/`
|
|
17
|
+
- `yarn migrate:up`: run all pending migrations
|
|
18
|
+
- `yarn migrate:down`: revert the last executed migration
|
|
19
|
+
- `yarn migrate:status`: show migration status
|
|
20
|
+
|
|
21
|
+
### Notes for webwallet migrations
|
|
22
|
+
- Use `MIGRATION_ENV=[localdev|test|qa|staging|prod]` to load a local migration secrets file.
|
|
23
|
+
- Create `servers/webwallet/migrations/environments/<migration-env>.secrets.env` locally with `MONGO_URI=...`.
|
|
24
|
+
- `*.secrets.env` files are intentionally not checked in.
|
|
25
|
+
- If you prefer, skip `MIGRATION_ENV` and pass `MONGO_URI` directly when running migration commands.
|
package/migrate-mongo.config.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
const env = require('env-var');
|
|
2
2
|
const { injectEnv } = require('@velocitycareerlabs/migrations');
|
|
3
3
|
|
|
4
|
-
const migrationEnv = env.get('MIGRATION_ENV').required(
|
|
5
|
-
|
|
4
|
+
const migrationEnv = env.get('MIGRATION_ENV').required(false).asString();
|
|
5
|
+
if (migrationEnv) {
|
|
6
|
+
injectEnv({ migrationEnv, envDirPath: './migrations/environments' });
|
|
7
|
+
}
|
|
6
8
|
const config = {
|
|
7
9
|
mongodb: {
|
|
8
10
|
url: env.get('MONGO_URI').required(true).asString(),
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
const { isDeepStrictEqual } = require('node:util');
|
|
2
|
+
|
|
3
|
+
const hasOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key);
|
|
4
|
+
|
|
5
|
+
const isPlainObject = (value) =>
|
|
6
|
+
value != null && typeof value === 'object' && !Array.isArray(value);
|
|
7
|
+
|
|
8
|
+
const getFirstDefined = (...values) =>
|
|
9
|
+
values.find((value) => value !== undefined && value !== null);
|
|
10
|
+
|
|
11
|
+
const isPlainJwk = (value) =>
|
|
12
|
+
isPlainObject(value) &&
|
|
13
|
+
['kty', 'crv', 'x', 'y', 'n', 'e', 'd', 'p', 'q', 'dp', 'dq', 'qi'].some(
|
|
14
|
+
(key) => hasOwn(value, key),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
const LEGACY_ENTRY_KEYS = new Set([
|
|
18
|
+
'did',
|
|
19
|
+
'kid',
|
|
20
|
+
'keyId',
|
|
21
|
+
'publicJwk',
|
|
22
|
+
'payload',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const pickDefined = (fields) =>
|
|
26
|
+
Object.fromEntries(
|
|
27
|
+
Object.entries(fields).filter(
|
|
28
|
+
([, value]) => value !== undefined && value !== null,
|
|
29
|
+
),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const getNormalizedPublicJwk = (entry, payload) => {
|
|
33
|
+
const publicJwk = getFirstDefined(entry.publicJwk, payload.publicJwk);
|
|
34
|
+
if (!isPlainObject(publicJwk)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (isPlainObject(publicJwk.valueJson)) {
|
|
38
|
+
return publicJwk.valueJson;
|
|
39
|
+
}
|
|
40
|
+
if (isPlainJwk(publicJwk)) {
|
|
41
|
+
return publicJwk;
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getAdditionalEntryFields = (entry, normalized) =>
|
|
47
|
+
Object.fromEntries(
|
|
48
|
+
Object.entries(entry).filter(
|
|
49
|
+
([key]) => !LEGACY_ENTRY_KEYS.has(key) && !hasOwn(normalized, key),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
const normalizeDidKeyMetadatumEntry = (entry) => {
|
|
54
|
+
if (!isPlainObject(entry)) {
|
|
55
|
+
return entry;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const payload = isPlainObject(entry.payload) ? entry.payload : {};
|
|
59
|
+
const normalized = pickDefined({
|
|
60
|
+
did: getFirstDefined(entry.did, payload.did),
|
|
61
|
+
kid: getFirstDefined(entry.kid, payload.kid),
|
|
62
|
+
keyId: getFirstDefined(entry.keyId, payload.keyId),
|
|
63
|
+
publicJwk: getNormalizedPublicJwk(entry, payload),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
...normalized,
|
|
68
|
+
...getAdditionalEntryFields(entry, normalized),
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const buildDidKeyMetadatumWrite = (account) => {
|
|
73
|
+
if (!Array.isArray(account.didKeyMetadatum)) {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const normalizedDidKeyMetadatum = account.didKeyMetadatum.map(
|
|
78
|
+
normalizeDidKeyMetadatumEntry,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (isDeepStrictEqual(normalizedDidKeyMetadatum, account.didKeyMetadatum)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
updateOne: {
|
|
87
|
+
filter: { _id: account._id },
|
|
88
|
+
update: {
|
|
89
|
+
$set: {
|
|
90
|
+
didKeyMetadatum: normalizedDidKeyMetadatum,
|
|
91
|
+
updatedAt: new Date(),
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
module.exports = {
|
|
99
|
+
up: async (db) => {
|
|
100
|
+
const accountsCollection = db.collection('accounts');
|
|
101
|
+
const cursor = accountsCollection.find(
|
|
102
|
+
{
|
|
103
|
+
didKeyMetadatum: { $type: 'array', $ne: [] },
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
projection: {
|
|
107
|
+
_id: 1,
|
|
108
|
+
didKeyMetadatum: 1,
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const writes = [];
|
|
114
|
+
for await (const account of cursor) {
|
|
115
|
+
const write = buildDidKeyMetadatumWrite(account);
|
|
116
|
+
if (write != null) {
|
|
117
|
+
writes.push(write);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (writes.length > 0) {
|
|
122
|
+
await accountsCollection.bulkWrite(writes);
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
down: async () => {},
|
|
127
|
+
};
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velocitycareerlabs/server-webwallet",
|
|
3
|
-
"version": "1.27.0-dev-build.
|
|
3
|
+
"version": "1.27.0-dev-build.142d72c39",
|
|
4
4
|
"description": "Web Wallet application",
|
|
5
5
|
"repository": "https://github.com/velocitycareerlabs/packages",
|
|
6
6
|
"engines": {
|
|
@@ -10,10 +10,8 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "cross-env NODE_ENV=test node --test --test-concurrency=1 --experimental-test-module-mocks --experimental-test-coverage --test-reporter=spec --test-reporter-destination=stdout 'test/**/*.test.js'",
|
|
12
12
|
"test:ci": "NODE_ENV=test node --test --test-concurrency=1 --experimental-test-module-mocks --experimental-test-coverage --test-coverage-include='src/**' --test-reporter=spec --test-reporter=junit --test-reporter-destination=stdout --test-reporter-destination=test-results.junit.xml --test-reporter=lcov --test-reporter-destination=lcov.info 'test/**/*.test.js'",
|
|
13
|
-
"up": "npm run start:debug",
|
|
14
13
|
"start": "node src/index.js",
|
|
15
14
|
"start:dev": "nodemon src/standalone.js",
|
|
16
|
-
"start:debug": "nodemon --inspect src/standalone.js",
|
|
17
15
|
"lint": "eslint . --format json >> eslint.json",
|
|
18
16
|
"lint:fix": "eslint --fix.",
|
|
19
17
|
"migrate:status": "./node_modules/.bin/migrate-mongo status --file migrate-mongo.config.js",
|
|
@@ -34,7 +32,7 @@
|
|
|
34
32
|
"@fastify/swagger": "^9.0.0",
|
|
35
33
|
"@fastify/swagger-ui": "^5.0.0",
|
|
36
34
|
"@spencejs/spence-mongo-repos": "^0.10.2",
|
|
37
|
-
"@velocitycareerlabs/migrations": "1.27.0-dev-build.
|
|
35
|
+
"@velocitycareerlabs/migrations": "1.27.0-dev-build.142d72c39",
|
|
38
36
|
"@verii/auth": "1.1.0-pre.1771382986",
|
|
39
37
|
"@verii/common-functions": "1.1.0-pre.1771382986",
|
|
40
38
|
"@verii/common-schemas": "1.1.0-pre.1771382986",
|
|
@@ -74,5 +72,5 @@
|
|
|
74
72
|
"nodemon": "3.1.13",
|
|
75
73
|
"prettier": "3.8.1"
|
|
76
74
|
},
|
|
77
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "cd324e82e914e237970bc639ef61dd210750c262"
|
|
78
76
|
}
|
|
@@ -168,7 +168,14 @@ const buildDidData = async (didKeyMetadatum, accessToken, context) => {
|
|
|
168
168
|
|
|
169
169
|
return {
|
|
170
170
|
did: newDidKeyMetadatum.did,
|
|
171
|
-
didKeyMetadatum: [
|
|
171
|
+
didKeyMetadatum: [
|
|
172
|
+
{
|
|
173
|
+
did: newDidKeyMetadatum.did,
|
|
174
|
+
kid: newDidKeyMetadatum.kid,
|
|
175
|
+
keyId: newDidKeyMetadatum.keyId,
|
|
176
|
+
publicJwk: newDidKeyMetadatum.publicJwk.valueJson,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
172
179
|
};
|
|
173
180
|
};
|
|
174
181
|
|
|
@@ -32,7 +32,6 @@ const JwtSignServiceImpl = (context) => {
|
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
const KeyKeyId = 'keyId';
|
|
35
|
-
const KeyJwk = 'jwk';
|
|
36
35
|
const KeyKid = 'kid';
|
|
37
36
|
const KeyIss = 'iss';
|
|
38
37
|
const KeyAud = 'aud';
|
|
@@ -46,7 +45,6 @@ const KeyPayload = 'payload';
|
|
|
46
45
|
const generateJwtPayloadToSign = (jwtDescriptor, nonce, didJwk) => {
|
|
47
46
|
const header = didJwk
|
|
48
47
|
? {
|
|
49
|
-
[KeyJwk]: didJwk.publicJwk.valueJson,
|
|
50
48
|
[KeyKid]: didJwk.kid,
|
|
51
49
|
}
|
|
52
50
|
: {};
|
|
@@ -8,15 +8,22 @@ const {
|
|
|
8
8
|
mock,
|
|
9
9
|
} = require('node:test');
|
|
10
10
|
const { expect } = require('expect');
|
|
11
|
+
const { VCLDidJwk } = require('@verii/vnf-nodejs-wallet-sdk');
|
|
11
12
|
|
|
12
13
|
const usersDid = 'did:example:123';
|
|
14
|
+
const didJwkJson = {
|
|
15
|
+
did: usersDid,
|
|
16
|
+
kid: `${usersDid}#0`,
|
|
17
|
+
keyId: '6630f0a67b097c289711f583',
|
|
18
|
+
publicJwk: {
|
|
19
|
+
kty: 'EC',
|
|
20
|
+
crv: 'P-256',
|
|
21
|
+
y: 'pQTRa7Gkqc5kj6odeMqpgV5T4LjbZa4F5KTu2JDrWnc',
|
|
22
|
+
x: 'r9fya52IlmTo3ybS0w_GefeQ_RX2EH_HJmSWqYYO2JY',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
13
25
|
const mockGenerateDidJwk = mock.fn(() =>
|
|
14
|
-
Promise.resolve(
|
|
15
|
-
did: usersDid,
|
|
16
|
-
publicJwk: {},
|
|
17
|
-
kid: '',
|
|
18
|
-
keyId: '',
|
|
19
|
-
}),
|
|
26
|
+
Promise.resolve(VCLDidJwk.fromJSON(didJwkJson)),
|
|
20
27
|
);
|
|
21
28
|
|
|
22
29
|
mock.module('@verii/vnf-nodejs-wallet-sdk', {
|
|
@@ -114,6 +121,23 @@ describe('Test accounts controller', () => {
|
|
|
114
121
|
remoteCryptoServicesToken: 'fooAccessToken',
|
|
115
122
|
},
|
|
116
123
|
]);
|
|
124
|
+
const [dbAccount] = await mongoDb()
|
|
125
|
+
.collection('accounts')
|
|
126
|
+
.find({ userId: auth0userId })
|
|
127
|
+
.toArray();
|
|
128
|
+
expect(dbAccount).toEqual({
|
|
129
|
+
_id: expect.any(ObjectId),
|
|
130
|
+
accountType: 'temp',
|
|
131
|
+
careerWalletAccountId: 'careerwallet-account-id',
|
|
132
|
+
userId: auth0userId,
|
|
133
|
+
did: usersDid,
|
|
134
|
+
createdAt: expect.any(Date),
|
|
135
|
+
updatedAt: expect.any(Date),
|
|
136
|
+
verificationInfo: {
|
|
137
|
+
createAccountMessageDisplayed: false,
|
|
138
|
+
},
|
|
139
|
+
didKeyMetadatum: [didJwkJson],
|
|
140
|
+
});
|
|
117
141
|
const dbCredentials = await mongoDb()
|
|
118
142
|
.collection('credentials')
|
|
119
143
|
.find()
|
|
@@ -155,6 +179,23 @@ describe('Test accounts controller', () => {
|
|
|
155
179
|
remoteCryptoServicesToken: 'fooAccessToken',
|
|
156
180
|
},
|
|
157
181
|
]);
|
|
182
|
+
const [dbAccount] = await mongoDb()
|
|
183
|
+
.collection('accounts')
|
|
184
|
+
.find({ userId: auth0userId })
|
|
185
|
+
.toArray();
|
|
186
|
+
expect(dbAccount).toEqual({
|
|
187
|
+
_id: expect.any(ObjectId),
|
|
188
|
+
accountType: 'temp',
|
|
189
|
+
careerWalletAccountId: 'careerwallet-account-id',
|
|
190
|
+
userId: auth0userId,
|
|
191
|
+
did: usersDid,
|
|
192
|
+
createdAt: expect.any(Date),
|
|
193
|
+
updatedAt: expect.any(Date),
|
|
194
|
+
verificationInfo: {
|
|
195
|
+
createAccountMessageDisplayed: false,
|
|
196
|
+
},
|
|
197
|
+
didKeyMetadatum: [didJwkJson],
|
|
198
|
+
});
|
|
158
199
|
});
|
|
159
200
|
|
|
160
201
|
it('should return an existing account and not call generateDidJwk if did key exists', async () => {
|
|
@@ -191,6 +232,23 @@ describe('Test accounts controller', () => {
|
|
|
191
232
|
remoteCryptoServicesToken: 'fooAccessToken',
|
|
192
233
|
},
|
|
193
234
|
]);
|
|
235
|
+
const [dbAccount] = await mongoDb()
|
|
236
|
+
.collection('accounts')
|
|
237
|
+
.find({ userId: auth0userId })
|
|
238
|
+
.toArray();
|
|
239
|
+
expect(dbAccount).toEqual({
|
|
240
|
+
_id: expect.any(ObjectId),
|
|
241
|
+
accountType: 'temp',
|
|
242
|
+
careerWalletAccountId: 'careerwallet-account-id',
|
|
243
|
+
userId: auth0userId,
|
|
244
|
+
did: usersDid,
|
|
245
|
+
createdAt: expect.any(Date),
|
|
246
|
+
updatedAt: expect.any(Date),
|
|
247
|
+
verificationInfo: {
|
|
248
|
+
createAccountMessageDisplayed: false,
|
|
249
|
+
},
|
|
250
|
+
didKeyMetadatum: [didJwkJson],
|
|
251
|
+
});
|
|
194
252
|
});
|
|
195
253
|
|
|
196
254
|
it("should 502 fail to create account if personaEmail is specified and personas can't be loaded", async () => {
|
|
@@ -332,6 +390,24 @@ describe('Test accounts controller', () => {
|
|
|
332
390
|
expect(personasNock.isDone()).toBe(true);
|
|
333
391
|
expect(mockGenerateDidJwk.mock.calls).toEqual([]);
|
|
334
392
|
|
|
393
|
+
const [dbAccount] = await mongoDb()
|
|
394
|
+
.collection('accounts')
|
|
395
|
+
.find({ userId: auth0userId })
|
|
396
|
+
.toArray();
|
|
397
|
+
expect(dbAccount).toEqual({
|
|
398
|
+
_id: expect.any(ObjectId),
|
|
399
|
+
accountType: 'temp',
|
|
400
|
+
careerWalletAccountId: 'careerwallet-account-id',
|
|
401
|
+
userId: auth0userId,
|
|
402
|
+
did: usersDid,
|
|
403
|
+
createdAt: expect.any(Date),
|
|
404
|
+
updatedAt: expect.any(Date),
|
|
405
|
+
verificationInfo: {
|
|
406
|
+
createAccountMessageDisplayed: false,
|
|
407
|
+
},
|
|
408
|
+
didKeyMetadatum: [{ did: usersDid }],
|
|
409
|
+
});
|
|
410
|
+
|
|
335
411
|
const [credential] = await mongoDb()
|
|
336
412
|
.collection('credentials')
|
|
337
413
|
.find()
|
|
@@ -38,7 +38,24 @@ describe('sign service implementation test', () => {
|
|
|
38
38
|
|
|
39
39
|
it('should sign payload successfully', async () => {
|
|
40
40
|
const nockedRequest = nock(fastify.config.careerWalletUrl)
|
|
41
|
-
.post('/api/v0.6/jwt/sign')
|
|
41
|
+
.post('/api/v0.6/jwt/sign', (body) => {
|
|
42
|
+
expect(body).toEqual({
|
|
43
|
+
header: {
|
|
44
|
+
kid: DidJwkMocks.DidJwk.kid,
|
|
45
|
+
},
|
|
46
|
+
payload: {
|
|
47
|
+
...OriginalPayload,
|
|
48
|
+
nonce: 'nonce123',
|
|
49
|
+
aud: 'aud123',
|
|
50
|
+
jti: 'jti123',
|
|
51
|
+
iss: 'iss123',
|
|
52
|
+
},
|
|
53
|
+
options: {
|
|
54
|
+
keyId: DidJwkMocks.DidJwk.keyId,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
return true;
|
|
58
|
+
})
|
|
42
59
|
.reply(200, JwtResponseMock);
|
|
43
60
|
|
|
44
61
|
const jwt = await subject.sign(
|