@verii/agentdb-cli 1.0.0-pre.1752076816

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.
@@ -0,0 +1,4 @@
1
+ const DEFAULT_COLLECTION = 'keys';
2
+ const DEFAULT_SECRET_PROP = 'key';
3
+
4
+ module.exports = { DEFAULT_COLLECTION, DEFAULT_SECRET_PROP };
@@ -0,0 +1,55 @@
1
+ const { program } = require('commander');
2
+ const { printInfo, printError } = require('../helpers/common');
3
+ const { initMongoClient } = require('../helpers/init-mongo-client');
4
+ const { reencrypt } = require('./reencrypt');
5
+
6
+ program
7
+ .name('agentdb-cli rotate-key')
8
+ .description('Re-encrypt MongoDB protected data with new key')
9
+ .usage('[options]')
10
+ .requiredOption('-o, --old-key <oldKey>', 'Old encryption key')
11
+ .requiredOption('-n, --new-key <newKey>', 'New encryption key')
12
+ .requiredOption(
13
+ '-u, --mongo-uri <mongoUri>',
14
+ 'The url of the mongo database for credential agent'
15
+ )
16
+ .option(
17
+ '-c, --collection <collection>',
18
+ 'Override the default collection to update'
19
+ )
20
+ .option(
21
+ '-p, --secret-prop <secretProp>',
22
+ 'Override the default encrypted property on the collection'
23
+ )
24
+ .option(
25
+ '--dry-run',
26
+ 'Attempt re-encryption but not actually update the database'
27
+ )
28
+ .action(async () => {
29
+ const options = program.opts();
30
+ let client;
31
+ printInfo('Starting rotate-key script');
32
+ printInfo(options);
33
+ try {
34
+ client = await initMongoClient(options.mongoUri);
35
+ await reencrypt(
36
+ options.oldKey,
37
+ options.newKey,
38
+ options.collection,
39
+ options.secretProp,
40
+ {
41
+ ...options,
42
+ db: client.db(),
43
+ }
44
+ );
45
+ } catch (error) {
46
+ printError('rotate-key Script Failure');
47
+ printError(error);
48
+ } finally {
49
+ if (client) {
50
+ await client.close();
51
+ printInfo('rotate-key Script. Mongo client closed');
52
+ }
53
+ }
54
+ })
55
+ .parseAsync(process.argv);
@@ -0,0 +1,82 @@
1
+ const { map, flow, get, size, each } = require('lodash/fp');
2
+ const { decryptCollection, encryptCollection } = require('@verii/crypto');
3
+ const { printInfo } = require('../helpers/common');
4
+
5
+ const updateProp = (id, secretProp, encryptedKey, currentTime) => ({
6
+ updateOne: {
7
+ filter: {
8
+ _id: id,
9
+ },
10
+ update: {
11
+ $set: {
12
+ [secretProp]: encryptedKey,
13
+ updatedAt: currentTime,
14
+ },
15
+ },
16
+ },
17
+ });
18
+
19
+ const decryptField = ({ encryptedField, oldKey, entry }, options) => {
20
+ try {
21
+ return decryptCollection(encryptedField, oldKey);
22
+ } catch (e) {
23
+ printInfo(
24
+ `Error decrypting '${options.secretProp}' field of _id: ${entry._id}`
25
+ );
26
+ throw e;
27
+ }
28
+ };
29
+ const reencryptCollectionField = async (
30
+ oldKey,
31
+ newKey,
32
+ _collection,
33
+ secretProp,
34
+ { db, ...options }
35
+ ) => {
36
+ const currentTime = new Date();
37
+
38
+ const collection = db.collection(_collection);
39
+ const secretPropKey = secretProp;
40
+ const entries = await collection
41
+ .find({ [secretPropKey]: { $exists: true } })
42
+ .toArray();
43
+ if (!size(entries)) {
44
+ printInfo('No documents found');
45
+ return;
46
+ }
47
+ const updatedFields = map(
48
+ (entry) =>
49
+ flow(
50
+ get(secretPropKey),
51
+ (encryptedField) =>
52
+ decryptField({ encryptedField, oldKey, entry }, options),
53
+ (decryptedField) => encryptCollection(decryptedField, newKey),
54
+ (encryptedField) =>
55
+ updateProp(entry._id, secretPropKey, encryptedField, currentTime)
56
+ )(entry),
57
+ entries
58
+ );
59
+ const printEntries = () => {
60
+ each((eW) => printInfo(`${get('_id', eW)}`), entries);
61
+ };
62
+ if (options.dryRun) {
63
+ printInfo('Dry Run: not actually rotating:');
64
+ printEntries();
65
+ printInfo(
66
+ `Dry Run: would have rotated encryption of ${size(entries)} ${
67
+ options.collection
68
+ }`
69
+ );
70
+ return;
71
+ }
72
+ await collection.bulkWrite(updatedFields);
73
+ printInfo('Rotated encryption of:');
74
+ printEntries();
75
+ printInfo(
76
+ `Rotated encryption of ${size(entries)} '${secretPropKey}' fields in ${
77
+ options.collection
78
+ }`
79
+ );
80
+ };
81
+
82
+ module.exports = { reencryptCollectionField };
@@ -0,0 +1,36 @@
1
+ const { DEFAULT_COLLECTION, DEFAULT_SECRET_PROP } = require('./constants');
2
+ const { reencryptCollectionField } = require('./reencrypt-collection-field');
3
+
4
+ const reencrypt = async (
5
+ oldKey,
6
+ newKey,
7
+ collection,
8
+ secretProp,
9
+ { db, ...options }
10
+ ) => {
11
+ await reencryptCollectionField(
12
+ oldKey,
13
+ newKey,
14
+ collection || DEFAULT_COLLECTION,
15
+ secretProp || DEFAULT_SECRET_PROP,
16
+ {
17
+ db,
18
+ ...options,
19
+ }
20
+ );
21
+
22
+ if (!collection && !secretProp) {
23
+ await reencryptCollectionField(
24
+ oldKey,
25
+ newKey,
26
+ 'tenants',
27
+ 'webhookAuth.bearerToken',
28
+ {
29
+ db,
30
+ ...options,
31
+ }
32
+ );
33
+ }
34
+ };
35
+
36
+ module.exports = { reencrypt };
package/test/.env.test ADDED
@@ -0,0 +1 @@
1
+ MONGO_URI=mongodb://0.0.0.0:27017/test-credentialagent
@@ -0,0 +1,54 @@
1
+ const { ObjectId } = require('mongodb');
2
+
3
+ const persistOfferFactory =
4
+ (db) =>
5
+ async (overrides = {}) => {
6
+ const result = await db.collection('offers').insertOne({
7
+ type: ['PastEmploymentPosition'],
8
+ issuer: {
9
+ id: 'issuerId',
10
+ },
11
+ credentialSubject,
12
+ offerCreationDate: new Date('2020-08-04T21:13:32.019Z'),
13
+ offerExpirationDate: new Date('2050-08-04T21:13:32.019Z'),
14
+ offerId: 'offerId',
15
+ exchangeId: new ObjectId(),
16
+ updatedAt: new Date(),
17
+ ...overrides,
18
+ });
19
+ return db.collection('offers').findOne(result.insertedId);
20
+ };
21
+
22
+ const credentialSubject = {
23
+ vendorUserId: 'vendorUserId',
24
+ company: 'company',
25
+ companyName: {
26
+ localized: {
27
+ en: 'Microsoft Corporation',
28
+ },
29
+ },
30
+ title: {
31
+ localized: {
32
+ en: 'Director, Communications (HoloLens & Mixed Reality Experiences)',
33
+ },
34
+ },
35
+ startMonthYear: {
36
+ month: 10,
37
+ year: 2010,
38
+ },
39
+ endMonthYear: {
40
+ month: 6,
41
+ year: 2019,
42
+ },
43
+ location: {
44
+ countryCode: 'US',
45
+ regionCode: 'MA',
46
+ },
47
+ description: {
48
+ localized: {
49
+ en: 'l Data, AI, Hybrid, IoT, Datacenter, Mixed Reality/HoloLens, D365, Power Platform - all kinds of fun stuff!',
50
+ },
51
+ },
52
+ };
53
+
54
+ module.exports = { persistOfferFactory, credentialSubject };
@@ -0,0 +1,13 @@
1
+ const persistTenantFactory =
2
+ (db) =>
3
+ async (overrides = {}) => {
4
+ const result = await db.collection('tenants').insertOne({
5
+ did: 'did:foo:bar',
6
+ createdAt: new Date(),
7
+ updatedAt: new Date(),
8
+ ...overrides,
9
+ });
10
+ return db.collection('tenants').findOne(result.insertedId);
11
+ };
12
+
13
+ module.exports = { persistTenantFactory };
@@ -0,0 +1,97 @@
1
+ const { buildMongoConnection } = require('@verii/tests-helpers');
2
+ const { getMetrics } = require('../src/metrics/get-metrics');
3
+ const { initMongoClient } = require('../src/helpers/init-mongo-client');
4
+ const { persistOfferFactory } = require('./factories/offers-factory');
5
+
6
+ describe('metrics test suite', () => {
7
+ let client;
8
+ let db;
9
+ let persistOffer;
10
+
11
+ beforeAll(async () => {
12
+ client = await initMongoClient(
13
+ buildMongoConnection('test-credentialagent')
14
+ );
15
+ db = client.db();
16
+ persistOffer = persistOfferFactory(db);
17
+ });
18
+
19
+ afterAll(async () => {
20
+ await client.close();
21
+ });
22
+
23
+ beforeEach(async () => {
24
+ await db.collection('offers').deleteMany({});
25
+ });
26
+
27
+ it('should get empty metrics', async () => {
28
+ const start = new Date('2020-05-20T00:00:00.000Z');
29
+ const end = new Date('2025-05-20T00:00:00.000Z');
30
+ const did = 'did:ion:1';
31
+ const { total, unique } = await getMetrics({ start, end, did }, { db });
32
+
33
+ expect(total).toEqual(0);
34
+ expect(unique).toEqual(0);
35
+ });
36
+
37
+ it('should get metrics by filter', async () => {
38
+ const start = new Date('2020-05-20T00:00:00.000Z');
39
+ const end = new Date('2025-05-20T00:00:00.000Z');
40
+ const did = 'did:ion:123654';
41
+
42
+ await persistOffer({
43
+ consentedAt: new Date(1686225410000),
44
+ issuer: { id: did },
45
+ credentialSubject: {
46
+ vendorUserId: 'vendorUserId1',
47
+ },
48
+ did: 'mock',
49
+ });
50
+ await persistOffer({
51
+ consentedAt: new Date(1686484610000),
52
+ issuer: { id: did },
53
+ credentialSubject: {
54
+ vendorUserId: 'vendorUserId2',
55
+ },
56
+ did: 'mock',
57
+ });
58
+ await persistOffer({
59
+ consentedAt: new Date(1686484610000),
60
+ issuer: { id: did },
61
+ credentialSubject: {
62
+ vendorUserId: 'vendorUserId2',
63
+ },
64
+ did: 'mock',
65
+ });
66
+ await persistOffer({
67
+ type: ['IdDocument'],
68
+ consentedAt: new Date(1686484610000),
69
+ issuer: { id: did },
70
+ credentialSubject: {
71
+ vendorUserId: 'vendorUserId2',
72
+ },
73
+ did: 'mock',
74
+ });
75
+ await persistOffer({
76
+ consentedAt: new Date(1686484610000),
77
+ issuer: { id: 'did:no:1' },
78
+ credentialSubject: {
79
+ vendorUserId: 'vendorUserId3',
80
+ },
81
+ did: 'mock',
82
+ });
83
+ await persistOffer({
84
+ consentedAt: new Date(1670763410000),
85
+ issuer: did,
86
+ credentialSubject: {
87
+ vendorUserId: 'vendorUserId4',
88
+ },
89
+ did: 'mock',
90
+ });
91
+ await persistOffer({});
92
+ const { total, unique } = await getMetrics({ start, end, did }, { db });
93
+
94
+ expect(total).toEqual(3);
95
+ expect(unique).toEqual(2);
96
+ });
97
+ });
@@ -0,0 +1,97 @@
1
+ const { buildMongoConnection } = require('@verii/tests-helpers');
2
+ const {
3
+ updateOfferExpirationDates,
4
+ } = require('../src/offers/update-offer-expiration-dates');
5
+ const { initMongoClient } = require('../src/helpers/init-mongo-client');
6
+ const { persistOfferFactory } = require('./factories/offers-factory');
7
+
8
+ describe('offers test suite', () => {
9
+ let client;
10
+ let db;
11
+ let persistOffer;
12
+ beforeAll(async () => {
13
+ client = await initMongoClient(buildMongoConnection('test-mockvendor'));
14
+ db = client.db();
15
+ persistOffer = persistOfferFactory(db);
16
+ });
17
+ afterAll(async () => {
18
+ await client.close();
19
+ });
20
+ beforeEach(async () => {
21
+ await db.collection('offers').deleteMany({});
22
+ });
23
+
24
+ it('should handle empty set', async () => {
25
+ await updateOfferExpirationDates({ db });
26
+
27
+ const updatedOffers = await db.collection('offers').find({}).toArray();
28
+ expect(updatedOffers).toEqual([]);
29
+ });
30
+
31
+ it('should update offerExpirationDate on offers', async () => {
32
+ const offer1 = await persistOffer({
33
+ offerExpirationDate: new Date('2020-08-04T21:13:32.019Z'),
34
+ });
35
+ const offer2 = await persistOffer({});
36
+
37
+ await updateOfferExpirationDates({ db });
38
+
39
+ const updatedOffers = await db.collection('offers').find({}).toArray();
40
+ expect(updatedOffers).toEqual([
41
+ {
42
+ ...offer1,
43
+ offerExpirationDate: new Date('2030-01-01T00:00:00.000Z'),
44
+ updatedAt: expect.any(Date),
45
+ },
46
+ {
47
+ ...offer2,
48
+ offerExpirationDate: new Date('2030-01-01T00:00:00.000Z'),
49
+ updatedAt: expect.any(Date),
50
+ },
51
+ ]);
52
+ expect(updatedOffers[0].updatedAt.getTime()).toBeGreaterThan(
53
+ offer1.updatedAt.getTime()
54
+ );
55
+ expect(updatedOffers[1].updatedAt.getTime()).toBeGreaterThan(
56
+ offer2.updatedAt.getTime()
57
+ );
58
+ });
59
+
60
+ it('should respect the dids filter on offers', async () => {
61
+ const offer1 = await persistOffer({
62
+ offerExpirationDate: new Date('2020-08-04T21:13:32.019Z'),
63
+ issuer: {
64
+ id: 'foo',
65
+ },
66
+ });
67
+ const offer2 = await persistOffer({
68
+ offerExpirationDate: new Date('2020-08-04T21:13:32.019Z'),
69
+ });
70
+
71
+ await updateOfferExpirationDates({ db, dids: ['foo'] });
72
+
73
+ const updatedOffers = await db
74
+ .collection('offers')
75
+ .find({})
76
+ .sort({ _id: 1 })
77
+ .toArray();
78
+ expect(updatedOffers).toEqual([
79
+ {
80
+ ...offer1,
81
+ offerExpirationDate: new Date('2030-01-01T00:00:00.000Z'),
82
+ updatedAt: expect.any(Date),
83
+ },
84
+ {
85
+ ...offer2,
86
+ offerExpirationDate: new Date('2020-08-04T21:13:32.019Z'),
87
+ updatedAt: expect.any(Date),
88
+ },
89
+ ]);
90
+ expect(updatedOffers[0].updatedAt.getTime()).toBeGreaterThan(
91
+ offer1.updatedAt.getTime()
92
+ );
93
+ expect(updatedOffers[1].updatedAt.getTime()).toEqual(
94
+ offer2.updatedAt.getTime()
95
+ );
96
+ });
97
+ });
@@ -0,0 +1,112 @@
1
+ const { buildMongoConnection } = require('@verii/tests-helpers');
2
+ const {
3
+ removePiiFromFinalizedOffers,
4
+ } = require('../src/pii-purge/remove-pii-from-finalized-offers');
5
+ const { initMongoClient } = require('../src/helpers/init-mongo-client');
6
+ const {
7
+ persistOfferFactory,
8
+ credentialSubject,
9
+ } = require('./factories/offers-factory');
10
+
11
+ describe('pii-purge test suite', () => {
12
+ let client;
13
+ let db;
14
+ let persistOffer;
15
+ beforeAll(async () => {
16
+ client = await initMongoClient(
17
+ buildMongoConnection('test-credentialagent')
18
+ );
19
+ db = client.db();
20
+ persistOffer = persistOfferFactory(db);
21
+ });
22
+ afterAll(async () => {
23
+ await client.close();
24
+ });
25
+ beforeEach(async () => {
26
+ await db.collection('offers').deleteMany({});
27
+ });
28
+
29
+ it('should remove pii from offers which have consentedAt or rejectedAt', async () => {
30
+ const offer1 = await persistOffer({
31
+ consentedAt: new Date('2020-08-04T21:13:32.019Z'),
32
+ credentialSubject: {
33
+ ...credentialSubject,
34
+ vendorUserId: 'vendorUserId_1',
35
+ },
36
+ });
37
+ const offer2 = await persistOffer({
38
+ rejectedAt: new Date('2020-08-04T21:13:32.019Z'),
39
+ credentialSubject: {
40
+ ...credentialSubject,
41
+ vendorUserId: 'vendorUserId_2',
42
+ },
43
+ });
44
+ const offer3 = await persistOffer({
45
+ credentialSubject: {
46
+ ...credentialSubject,
47
+ vendorUserId: 'vendorUserId_3',
48
+ },
49
+ });
50
+ const offer4 = await persistOffer({
51
+ credentialSubject: {
52
+ ...credentialSubject,
53
+ vendorUserId: 'vendorUserId_4',
54
+ },
55
+ offerExpirationDate: new Date('2020-08-04T21:13:32.019Z'),
56
+ });
57
+
58
+ await removePiiFromFinalizedOffers({ db });
59
+
60
+ const updatedOffers = await db
61
+ .collection('offers')
62
+ .find({})
63
+ .sort({ _id: 1 })
64
+ .toArray();
65
+ expect(updatedOffers).toEqual([
66
+ {
67
+ ...offer1,
68
+ credentialSubject: {
69
+ vendorUserId: offer1.credentialSubject.vendorUserId,
70
+ },
71
+ updatedAt: expect.any(Date),
72
+ },
73
+ {
74
+ ...offer2,
75
+ credentialSubject: {
76
+ vendorUserId: offer2.credentialSubject.vendorUserId,
77
+ },
78
+ updatedAt: expect.any(Date),
79
+ },
80
+ offer3,
81
+ {
82
+ ...offer4,
83
+ credentialSubject: {
84
+ vendorUserId: offer4.credentialSubject.vendorUserId,
85
+ },
86
+ updatedAt: expect.any(Date),
87
+ },
88
+ ]);
89
+ });
90
+
91
+ it('should not remove pii if offers active', async () => {
92
+ const offers = await Promise.all([
93
+ persistOffer({
94
+ credentialSubject: {
95
+ ...credentialSubject,
96
+ vendorUserId: 'vendorUserId_1',
97
+ },
98
+ }),
99
+ persistOffer({
100
+ credentialSubject: {
101
+ ...credentialSubject,
102
+ vendorUserId: 'vendorUserId_2',
103
+ },
104
+ }),
105
+ ]);
106
+
107
+ await removePiiFromFinalizedOffers({ db });
108
+
109
+ const updatedOffers = await db.collection('offers').find({}).toArray();
110
+ expect(updatedOffers).toEqual(offers);
111
+ });
112
+ });