@friggframework/core 0.2.31-v1-alpha.6 → 1.0.1-v1-alpha-package-update.0

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.
Files changed (193) hide show
  1. package/assertions/CHANGELOG.md +87 -0
  2. package/assertions/LICENSE.md +9 -0
  3. package/assertions/README.md +3 -0
  4. package/assertions/bump.txt +1 -0
  5. package/assertions/get.js +139 -0
  6. package/assertions/index.js +19 -0
  7. package/assertions/is-equal.js +17 -0
  8. package/associations/LICENSE.md +9 -0
  9. package/associations/README.md +3 -0
  10. package/associations/association.js +78 -0
  11. package/associations/bump3.txt +0 -0
  12. package/associations/jest.config.js +5 -0
  13. package/associations/model.js +54 -0
  14. package/bump.txt +0 -0
  15. package/core/.eslintrc.json +3 -0
  16. package/{Delegate.js → core/Delegate.js} +1 -1
  17. package/core/LICENSE.md +9 -0
  18. package/{Worker.js → core/Worker.js} +2 -2
  19. package/core/bump3.txt +0 -0
  20. package/{create-handler.js → core/create-handler.js} +2 -2
  21. package/core/index.js +6 -0
  22. package/core/jest.config.js +5 -0
  23. package/database/.eslintrc.json +3 -0
  24. package/database/CHANGELOG.md +97 -0
  25. package/database/LICENSE.md +9 -0
  26. package/database/README.md +3 -0
  27. package/database/bump3.txt +0 -0
  28. package/database/index.js +17 -0
  29. package/database/jest.config.js +5 -0
  30. package/database/models/IndividualUser.js +76 -0
  31. package/database/models/OrganizationUser.js +29 -0
  32. package/database/models/State.js +9 -0
  33. package/database/models/Token.js +70 -0
  34. package/database/models/UserModel.js +7 -0
  35. package/database/mongo.js +45 -0
  36. package/database/mongoose.js +5 -0
  37. package/encrypt/.eslintrc.json +3 -0
  38. package/encrypt/CHANGELOG.md +65 -0
  39. package/encrypt/Cryptor.js +236 -0
  40. package/encrypt/Cryptor.test.js +32 -0
  41. package/encrypt/LICENSE.md +9 -0
  42. package/encrypt/README.md +3 -0
  43. package/encrypt/aes.js +27 -0
  44. package/encrypt/bump3.txt +0 -0
  45. package/encrypt/encrypt.js +124 -0
  46. package/encrypt/encrypt.test.js +1068 -0
  47. package/encrypt/index.js +3 -0
  48. package/encrypt/jest.config.js +5 -0
  49. package/encrypt/test-encrypt.js +107 -0
  50. package/errors/.eslintrc.json +3 -0
  51. package/errors/CHANGELOG.md +44 -0
  52. package/errors/LICENSE.md +9 -0
  53. package/errors/README.md +3 -0
  54. package/errors/base-error.js +23 -0
  55. package/errors/base-error.test.js +32 -0
  56. package/errors/bump.txt +1 -0
  57. package/errors/bump3.txt +0 -0
  58. package/errors/fetch-error.js +72 -0
  59. package/errors/fetch-error.test.js +79 -0
  60. package/errors/halt-error.js +10 -0
  61. package/errors/halt-error.test.js +11 -0
  62. package/errors/index.js +15 -0
  63. package/errors/jest.config.js +5 -0
  64. package/errors/validation-errors.js +23 -0
  65. package/errors/validation-errors.test.js +120 -0
  66. package/eslint-config/.eslintrc.json +3 -0
  67. package/eslint-config/CHANGELOG.md +17 -0
  68. package/eslint-config/LICENSE.md +9 -0
  69. package/eslint-config/README.md +3 -0
  70. package/eslint-config/bump3.txt +0 -0
  71. package/eslint-config/index.js +38 -0
  72. package/index.js +29 -5
  73. package/integrations/.eslintrc.json +3 -0
  74. package/integrations/CHANGELOG.md +244 -0
  75. package/integrations/LICENSE.md +9 -0
  76. package/integrations/README.md +3 -0
  77. package/integrations/bump3.txt +0 -0
  78. package/integrations/create-frigg-backend.js +31 -0
  79. package/integrations/index.js +19 -0
  80. package/integrations/integration-base.js +162 -0
  81. package/integrations/integration-factory.js +166 -0
  82. package/integrations/integration-mapping.js +43 -0
  83. package/integrations/integration-model.js +42 -0
  84. package/integrations/integration-router.js +367 -0
  85. package/integrations/integration-user.js +144 -0
  86. package/integrations/jest-setup.js +2 -0
  87. package/integrations/jest-teardown.js +2 -0
  88. package/integrations/jest.config.js +12 -0
  89. package/integrations/options.js +54 -0
  90. package/integrations/test/integration-base.test.js +143 -0
  91. package/lambda/README.md +3 -0
  92. package/lambda/TimeoutCatcher.js +43 -0
  93. package/lambda/TimeoutCatcher.test.js +68 -0
  94. package/lambda/bump3.txt +0 -0
  95. package/lambda/index.js +3 -0
  96. package/lambda/jest.config.js +3 -0
  97. package/logs/.eslintrc.json +3 -0
  98. package/logs/CHANGELOG.md +57 -0
  99. package/logs/LICENSE.md +9 -0
  100. package/logs/README.md +3 -0
  101. package/logs/bump3.txt +0 -0
  102. package/logs/index.js +7 -0
  103. package/logs/jest.config.js +5 -0
  104. package/logs/logger.js +69 -0
  105. package/logs/logger.test.js +76 -0
  106. package/migrations/README.md +3 -0
  107. package/migrations/bump3.txt +0 -0
  108. package/migrations/index.js +9 -0
  109. package/migrations/jest.config.js +3 -0
  110. package/migrations/manager.js +33 -0
  111. package/migrations/migrator.js +170 -0
  112. package/migrations/options.js +28 -0
  113. package/module-plugin/.eslintrc.json +3 -0
  114. package/module-plugin/CHANGELOG.md +224 -0
  115. package/module-plugin/LICENSE.md +9 -0
  116. package/module-plugin/ModuleConstants.js +10 -0
  117. package/module-plugin/README.md +3 -0
  118. package/module-plugin/auther.js +342 -0
  119. package/module-plugin/bump3.txt +0 -0
  120. package/module-plugin/credential.js +22 -0
  121. package/module-plugin/entity-manager.js +70 -0
  122. package/module-plugin/entity.js +46 -0
  123. package/module-plugin/index.js +25 -0
  124. package/module-plugin/jest-setup.js +3 -0
  125. package/module-plugin/jest-teardown.js +2 -0
  126. package/module-plugin/jest.config.js +20 -0
  127. package/module-plugin/manager.js +169 -0
  128. package/module-plugin/module-factory.js +60 -0
  129. package/module-plugin/requester/api-key.js +36 -0
  130. package/module-plugin/requester/basic.js +43 -0
  131. package/module-plugin/requester/oauth-2.js +219 -0
  132. package/module-plugin/requester/requester.js +150 -0
  133. package/module-plugin/requester/requester.test.js +28 -0
  134. package/module-plugin/test/auther.test.js +97 -0
  135. package/module-plugin/test/mock-api/api.js +29 -0
  136. package/module-plugin/test/mock-api/definition.js +48 -0
  137. package/module-plugin/test/mock-api/mocks/hubspot.js +43 -0
  138. package/package.json +37 -13
  139. package/prettier-config/.eslintrc.json +3 -0
  140. package/prettier-config/CHANGELOG.md +17 -0
  141. package/prettier-config/LICENSE.md +9 -0
  142. package/prettier-config/README.md +3 -0
  143. package/prettier-config/bump3.txt +0 -0
  144. package/prettier-config/index.js +6 -0
  145. package/syncs/README.md +3 -0
  146. package/syncs/bump3.txt +0 -0
  147. package/syncs/jest.config.js +5 -0
  148. package/syncs/manager.js +466 -0
  149. package/syncs/model.js +62 -0
  150. package/syncs/sync.js +114 -0
  151. package/test-environment/.eslintrc.json +3 -0
  152. package/test-environment/Authenticator.js +73 -0
  153. package/test-environment/CHANGELOG.md +46 -0
  154. package/test-environment/LICENSE.md +9 -0
  155. package/test-environment/README.md +3 -0
  156. package/test-environment/auther-definition-method-tester.js +45 -0
  157. package/test-environment/auther-definition-tester.js +104 -0
  158. package/test-environment/bump3.txt +0 -0
  159. package/test-environment/index.js +24 -0
  160. package/test-environment/integration-validator.js +2 -0
  161. package/test-environment/jest-global-setup.js +6 -0
  162. package/test-environment/jest-global-teardown.js +3 -0
  163. package/test-environment/jest-preset.js +14 -0
  164. package/test-environment/mock-api-readme.md +102 -0
  165. package/test-environment/mock-api.js +284 -0
  166. package/test-environment/mock-integration.js +82 -0
  167. package/test-environment/mongodb.js +22 -0
  168. package/test-environment/override-environment.js +11 -0
  169. package/types/CHANGELOG.md +49 -0
  170. package/types/README.md +24 -0
  171. package/types/assertions/index.d.ts +83 -0
  172. package/types/associations/index.d.ts +74 -0
  173. package/types/bump3.txt +0 -0
  174. package/types/core/index.d.ts +54 -0
  175. package/types/database/index.d.ts +3 -0
  176. package/types/encrypt/index.d.ts +5 -0
  177. package/types/errors/index.d.ts +66 -0
  178. package/types/eslint-config/index.d.ts +41 -0
  179. package/types/index.d.ts +14 -0
  180. package/types/integrations/index.d.ts +191 -0
  181. package/types/lambda/index.d.ts +31 -0
  182. package/types/logs/index.d.ts +5 -0
  183. package/types/module-plugin/index.d.ts +293 -0
  184. package/types/prettier-config/index.d.ts +6 -0
  185. package/types/syncs/index.d.ts +128 -0
  186. package/types/test-environment/index.d.ts +17 -0
  187. package/types/tsconfig.json +103 -0
  188. /package/{.eslintrc.json → assertions/.eslintrc.json} +0 -0
  189. /package/{bump3.txt → assertions/bump3.txt} +0 -0
  190. /package/{jest.config.js → assertions/jest.config.js} +0 -0
  191. /package/{CHANGELOG.md → core/CHANGELOG.md} +0 -0
  192. /package/{README.md → core/README.md} +0 -0
  193. /package/{load-installed-modules.js → core/load-installed-modules.js} +0 -0
@@ -0,0 +1,76 @@
1
+ const { mongoose } = require('../mongoose');
2
+ const bcrypt = require('bcryptjs');
3
+ const { UserModel: Parent } = require('./UserModel');
4
+
5
+ const collectionName = 'IndividualUser';
6
+
7
+ const schema = new mongoose.Schema({
8
+ email: { type: String },
9
+ username: { type: String, unique: true },
10
+ hashword: { type: String },
11
+ appUserId: { type: String },
12
+ organizationUser: { type: mongoose.Schema.Types.ObjectId, ref: 'User' },
13
+ });
14
+
15
+ schema.pre('save', async function () {
16
+ if (this.hashword) {
17
+ this.hashword = await bcrypt.hashSync(
18
+ this.hashword,
19
+ parseInt(this.schema.statics.decimals)
20
+ )
21
+ }
22
+ })
23
+
24
+ schema.static({
25
+ decimals: 10,
26
+ update: async function (id, options) {
27
+ if ('password' in options) {
28
+ options.hashword = await bcrypt.hashSync(
29
+ options.password,
30
+ parseInt(this.decimals)
31
+ );
32
+ delete options.password;
33
+ }
34
+ return this.findOneAndUpdate(
35
+ {_id: id},
36
+ options,
37
+ {new: true, useFindAndModify: true}
38
+ );
39
+ },
40
+ getUserByUsername: async function (username) {
41
+ let getByUser;
42
+ try{
43
+ getByUser = await this.find({username});
44
+ } catch (e) {
45
+ console.log('oops')
46
+ }
47
+
48
+ if (getByUser.length > 1) {
49
+ throw new Error(
50
+ 'Unique username or email? Please reach out to our developers'
51
+ );
52
+ }
53
+
54
+ if (getByUser.length === 1) {
55
+ return getByUser[0];
56
+ }
57
+ },
58
+ getUserByAppUserId: async function (appUserId) {
59
+ const getByUser = await this.find({ appUserId });
60
+
61
+ if (getByUser.length > 1) {
62
+ throw new Error(
63
+ 'Supposedly using a unique appUserId? Please reach out to our developers'
64
+ );
65
+ }
66
+
67
+
68
+ if (getByUser.length === 1) {
69
+ return getByUser[0];
70
+ }
71
+ }
72
+ })
73
+
74
+ const IndividualUser = Parent.discriminators?.IndividualUser || Parent.discriminator(collectionName, schema);
75
+
76
+ module.exports = {IndividualUser};
@@ -0,0 +1,29 @@
1
+ const { mongoose } = require('../mongoose');
2
+ const { UserModel: Parent } = require('./UserModel');
3
+
4
+ const collectionName = 'OrganizationUser';
5
+
6
+ const schema = new mongoose.Schema({
7
+ appOrgId: { type: String, required: true, unique: true },
8
+ name: { type: String },
9
+ });
10
+
11
+ schema.static({
12
+ getUserByAppOrgId: async function (appOrgId) {
13
+ const getByUser = await this.find({ appOrgId });
14
+
15
+ if (getByUser.length > 1) {
16
+ throw new Error(
17
+ 'Supposedly using a unique appOrgId? Please reach out to our developers'
18
+ );
19
+ }
20
+
21
+ if (getByUser.length === 1) {
22
+ return getByUser[0];
23
+ }
24
+ }
25
+ })
26
+
27
+ const OrganizationUser = Parent.discriminators?.OrganizationUser || Parent.discriminator(collectionName, schema);
28
+
29
+ module.exports = {OrganizationUser};
@@ -0,0 +1,9 @@
1
+ const { mongoose } = require('../mongoose');
2
+
3
+ const schema = new mongoose.Schema({
4
+ state: { type: mongoose.Schema.Types.Mixed }
5
+ });
6
+
7
+ const State = mongoose.models.State || mongoose.model('State', schema);
8
+
9
+ module.exports = { State };
@@ -0,0 +1,70 @@
1
+ const { mongoose } = require('../mongoose');
2
+ const bcrypt = require('bcryptjs');
3
+
4
+ const collectionName = 'Token';
5
+ const decimals = 10;
6
+
7
+ const schema = new mongoose.Schema({
8
+ token: { type: String, required: true },
9
+ created: { type: Date, default: Date.now },
10
+ expires: { type: Date },
11
+ user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
12
+ });
13
+
14
+ schema.static({
15
+ createTokenWithExpire: async function (userId, rawToken, minutes) {
16
+ // Create user token
17
+ let tokenHash = await bcrypt.hashSync(rawToken, parseInt(decimals));
18
+
19
+ let session = {
20
+ token: tokenHash,
21
+ expires: new Date(Date.now() + minutes * 60000).toISOString(),
22
+ user: userId,
23
+ };
24
+
25
+ return this.create(session);
26
+ },
27
+ // Takes in a token object and that has been created in the database and the raw token value.
28
+ // Returns a json of just the token and id to return to the browser
29
+ createJSONToken: function (token, rawToken) {
30
+ let returnArr = {
31
+ id: token.id,
32
+ token: rawToken,
33
+ };
34
+ return JSON.stringify(returnArr);
35
+ },
36
+ // Takes in a token object and that has been created in the database and the raw token value.
37
+ // Returns a base64 buffer of just the token and id to return to the browser
38
+ createBase64BufferToken: function (token, rawToken) {
39
+ let jsonVal = Token.createJSONToken(token, rawToken);
40
+ return Buffer.from(jsonVal).toString('base64');
41
+ },
42
+ getJSONTokenFromBase64BufferToken: function (buffer) {
43
+ let tokenStr = Buffer.from(buffer.trim(), 'base64').toString('ascii');
44
+ return JSON.parse(tokenStr);
45
+ },
46
+
47
+ // Takes in a JSON Token with id and token in it and verifies the token
48
+ // is valid from the database. If it is not va
49
+ validateAndGetTokenFromJSONToken: async function (tokenObj) {
50
+ let sessionToken = await this.findById(tokenObj.id);
51
+ if (sessionToken) {
52
+ if (
53
+ !(await bcrypt.compareSync(tokenObj.token, sessionToken.token))
54
+ ) {
55
+ throw new Error('Invalid Token: Token does not match');
56
+ }
57
+ if (new Date(sessionToken.expires) < new Date()) {
58
+ throw new Error('Invalid Token: Token is expired');
59
+ }
60
+
61
+ return sessionToken;
62
+ } else {
63
+ throw new Error('Invalid Token: Token does not exist');
64
+ }
65
+ }
66
+ })
67
+
68
+ const Token = mongoose.models.Token || mongoose.model(collectionName, schema);
69
+
70
+ module.exports = { Token };
@@ -0,0 +1,7 @@
1
+ const { mongoose } = require('../mongoose');
2
+
3
+ const schema = new mongoose.Schema({}, {timestamps: true})
4
+
5
+ const UserModel = mongoose.models.User || mongoose.model('User',schema)
6
+
7
+ module.exports = { UserModel: UserModel };
@@ -0,0 +1,45 @@
1
+ // Best Practices Connecting from AWS Lambda:
2
+ // https://dev.to/adnanrahic/building-a-serverless-rest-api-with-nodejs-and-mongodb-43db
3
+ // https://mongoosejs.com/docs/lambda.html
4
+ // https://www.mongodb.com/blog/post/optimizing-aws-lambda-performance-with-mongodb-atlas-and-nodejs
5
+ const { Encrypt } = require('../encrypt');
6
+ const { mongoose } = require('./mongoose');
7
+ const { debug, flushDebugLog } = require('../logs');
8
+
9
+ mongoose.plugin(Encrypt);
10
+ mongoose.set('applyPluginsToDiscriminators', true); // Needed for LHEncrypt
11
+
12
+ // Buffering means mongoose will queue up operations if it gets
13
+ // With serverless, better to fail fast if not connected.
14
+ // disconnected from MongoDB and send them when it reconnects.
15
+ const mongoConfig = {
16
+ useNewUrlParser: true,
17
+ bufferCommands: false, // Disable mongoose buffering
18
+ autoCreate: false, // Disable because auto creation does not work without buffering
19
+ useUnifiedTopology: true,
20
+ serverSelectionTimeoutMS: 5000,
21
+ };
22
+
23
+ const checkIsConnected = () => mongoose.connection?.readyState > 0;
24
+
25
+ const connectToDatabase = async () => {
26
+ if (checkIsConnected()) {
27
+ debug('=> using existing database connection');
28
+ return;
29
+ }
30
+
31
+ debug('=> using new database connection');
32
+ await mongoose.connect(process.env.MONGO_URI, mongoConfig);
33
+ debug('Connection state:', mongoose.STATES[mongoose.connection.readyState]);
34
+ mongoose.connection.on('error', (error) => flushDebugLog(error));
35
+ };
36
+
37
+ const disconnectFromDatabase = async () => mongoose.disconnect();
38
+
39
+ const createObjectId = () => new mongoose.Types.ObjectId();
40
+
41
+ module.exports = {
42
+ connectToDatabase,
43
+ disconnectFromDatabase,
44
+ createObjectId,
45
+ };
@@ -0,0 +1,5 @@
1
+ const mongoose = require('mongoose');
2
+ mongoose.set('strictQuery', false);
3
+ module.exports = {
4
+ mongoose
5
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "@friggframework/eslint-config"
3
+ }
@@ -0,0 +1,65 @@
1
+ # v1.1.7 (Tue Apr 04 2023)
2
+
3
+ :tada: This release contains work from a new contributor! :tada:
4
+
5
+ Thank you, null[@debbie-yu](https://github.com/debbie-yu), for all your work!
6
+
7
+ #### 🐛 Bug Fix
8
+
9
+ - Adding new IntegrationMapping collection [#142](https://github.com/friggframework/frigg/pull/142) ([@debbie-yu](https://github.com/debbie-yu))
10
+ - Merge branch 'main' of https://github.com/friggframework/frigg into debbie.yu/integration-mapping ([@debbie-yu](https://github.com/debbie-yu))
11
+ - addressing PR feedback and adding unit tests around IntegrationMapping ([@debbie-yu](https://github.com/debbie-yu))
12
+ - Bump independent versions \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
13
+
14
+ #### Authors: 2
15
+
16
+ - [@debbie-yu](https://github.com/debbie-yu)
17
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
18
+
19
+ ---
20
+
21
+ # v1.1.6 (Tue Jan 31 2023)
22
+
23
+ #### 🐛 Bug Fix
24
+
25
+ - Bump independent versions \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
26
+
27
+ #### Authors: 1
28
+
29
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
30
+
31
+ ---
32
+
33
+ # v1.1.5 (Mon Jan 09 2023)
34
+
35
+ #### 🐛 Bug Fix
36
+
37
+ - Merge remote-tracking branch 'origin/main' into gitbook-updates [#48](https://github.com/friggframework/frigg/pull/48) ([@seanspeaks](https://github.com/seanspeaks))
38
+ - Bump independent versions \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
39
+ - Merge remote-tracking branch 'origin/main' into simplify-mongoose-models ([@seanspeaks](https://github.com/seanspeaks))
40
+ - Add READMEs for all packages and api-modules [#20](https://github.com/friggframework/frigg/pull/20) ([@seanspeaks](https://github.com/seanspeaks))
41
+ - Add READMEs for all packages and api-modules ([@seanspeaks](https://github.com/seanspeaks))
42
+
43
+ #### ⚠️ Pushed to `main`
44
+
45
+ - Merge branch 'main' into gitbook-updates ([@seanspeaks](https://github.com/seanspeaks))
46
+ - Refactored for more conventional naming (at least for packages) ([@seanspeaks](https://github.com/seanspeaks))
47
+
48
+ #### Authors: 1
49
+
50
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
51
+
52
+ ---
53
+
54
+ # v1.1.4 (Tue Dec 06 2022)
55
+
56
+ #### 🐛 Bug Fix
57
+
58
+ - fix modules to @friggframework [#74](https://github.com/friggframework/frigg/pull/74) ([@sheehantoufiq](https://github.com/sheehantoufiq))
59
+ - fix modules to @friggframework ([@sheehantoufiq](https://github.com/sheehantoufiq))
60
+ - Bump independent versions \[skip ci\] ([@seanspeaks](https://github.com/seanspeaks))
61
+
62
+ #### Authors: 2
63
+
64
+ - Sean Matthews ([@seanspeaks](https://github.com/seanspeaks))
65
+ - Sheehan Toufiq Khan ([@sheehantoufiq](https://github.com/sheehantoufiq))
@@ -0,0 +1,236 @@
1
+ const crypto = require('crypto');
2
+ const AWS = require('aws-sdk');
3
+ const { get, set } = require('lodash');
4
+ const aes = require('./aes');
5
+
6
+ const hasValue = (a) => a !== undefined && a !== null && a !== '';
7
+
8
+ class Cryptor {
9
+ constructor({ fields, shouldUseAws }) {
10
+ this.shouldUseAws = shouldUseAws;
11
+ this.fields = fields;
12
+
13
+ this.permutationsByField = {};
14
+
15
+ for (const field of fields) {
16
+ this.permutationsByField[field] = this.calculatePermutations(
17
+ field.split('.')
18
+ );
19
+ }
20
+ }
21
+
22
+ async generateDataKey() {
23
+ if (this.shouldUseAws) {
24
+ const kmsClient = new AWS.KMS();
25
+ const dataKey = await kmsClient
26
+ .generateDataKey({
27
+ KeyId: process.env.KMS_KEY_ARN,
28
+ KeySpec: 'AES_256',
29
+ })
30
+ .promise();
31
+
32
+ const keyId = Buffer.from(dataKey.KeyId).toString('base64');
33
+ const encryptedKey = dataKey.CiphertextBlob.toString('base64');
34
+ const plaintext = dataKey.Plaintext;
35
+ return { keyId, encryptedKey, plaintext };
36
+ }
37
+
38
+ const { AES_KEY, AES_KEY_ID } = process.env;
39
+ const randomKey = crypto.randomBytes(32).toString('hex').slice(0, 32);
40
+
41
+ return {
42
+ keyId: Buffer.from(AES_KEY_ID).toString('base64'),
43
+ encryptedKey: Buffer.from(aes.encrypt(randomKey, AES_KEY)).toString(
44
+ 'base64'
45
+ ),
46
+ plaintext: randomKey,
47
+ };
48
+ }
49
+
50
+ getKeyFromEnvironment(keyId) {
51
+ const availableKeys = {
52
+ [process.env.AES_KEY_ID]: process.env.AES_KEY,
53
+ [process.env.DEPRECATED_AES_KEY_ID]: process.env.DEPRECATED_AES_KEY,
54
+ };
55
+
56
+ const key = availableKeys[keyId];
57
+
58
+ if (!key) {
59
+ throw new Error(`No encryption key found with ID "${keyId}"`);
60
+ }
61
+
62
+ return key;
63
+ }
64
+
65
+ async decryptDataKey(keyId, encryptedKey) {
66
+ if (this.shouldUseAws) {
67
+ const kmsClient = new AWS.KMS();
68
+ const dataKey = await kmsClient
69
+ .decrypt({
70
+ KeyId: keyId,
71
+ CiphertextBlob: encryptedKey,
72
+ })
73
+ .promise();
74
+
75
+ return dataKey.Plaintext;
76
+ }
77
+
78
+ const key = this.getKeyFromEnvironment(keyId);
79
+ return aes.decrypt(encryptedKey, key);
80
+ }
81
+
82
+ // If the field has a value in the document, apply async function f to that field.
83
+ async setInDocument(doc, f) {
84
+ // Use the Mongoose document get/set when available (not for insertMany)
85
+ if (doc.get) {
86
+ for (const field of this.fields) {
87
+ const value = doc.get(field);
88
+ if (hasValue(value)) {
89
+ doc.set(field, await f(value));
90
+ }
91
+ }
92
+ return;
93
+ }
94
+
95
+ // Otherwise use permutations.
96
+ for (const field of this.fields) {
97
+ const updatedDoc = await this.applyAll(doc, field, f);
98
+ Object.assign(doc, updatedDoc);
99
+ }
100
+ }
101
+
102
+ // Calculate all possible permutations for a nested field. For example a
103
+ // field "deeply.nested.field" might be referred to in a Mongo query as
104
+ // { deeply: { 'nested.field': {} } } or { 'deeply.nested.field': {} }
105
+ // etc. For a given path, this gives all path parts to check in a format
106
+ // that lodash understands when using get and set with an array of path
107
+ // parts e.g. get(o, ['deeply', 'nested.parts'])
108
+ calculatePermutations = (parts) => {
109
+ if (!parts.length) return [];
110
+ if (parts.length === 1) return [parts];
111
+
112
+ const combos = [];
113
+
114
+ for (let i = 0; i < parts.length; i += 1) {
115
+ const frontPath = parts.slice(0, i + 1).join('.');
116
+ const rest = parts.slice(i + 1);
117
+
118
+ if (rest.length) {
119
+ combos.push(
120
+ ...this.calculatePermutations(rest).map((child) => [
121
+ frontPath,
122
+ ...child,
123
+ ])
124
+ );
125
+ } else {
126
+ combos.push([frontPath]);
127
+ }
128
+ }
129
+
130
+ return combos;
131
+ };
132
+
133
+ // Encrypt all possible permutations of a field (possibly nested), if there
134
+ // is a value at that path permutation.
135
+ async applyAll(o, field, f) {
136
+ const clone = { ...o };
137
+ const permutations = this.permutationsByField[field];
138
+
139
+ for (const path of permutations) {
140
+ const value = get(o, path);
141
+ if (hasValue(value)) {
142
+ set(clone, path, await f(value));
143
+ }
144
+ }
145
+
146
+ return clone;
147
+ }
148
+
149
+ async processFieldsInDocuments(docs, f) {
150
+ const promises = docs
151
+ .filter(Boolean)
152
+ .flatMap((doc) => this.setInDocument(doc, f));
153
+
154
+ return Promise.all(promises);
155
+ }
156
+
157
+ async encryptFieldsInDocuments(docs) {
158
+ await this.processFieldsInDocuments(docs, this.encrypt.bind(this));
159
+ }
160
+
161
+ async decryptFieldsInDocuments(docs) {
162
+ await this.processFieldsInDocuments(docs, this.decrypt.bind(this));
163
+ }
164
+
165
+ async encryptFieldsInQuery(query) {
166
+ for (const field of this.fields) {
167
+ const originalUpdate = query.getUpdate();
168
+ const updatedUpdate = await this.applyAll(
169
+ originalUpdate,
170
+ field,
171
+ this.encrypt.bind(this)
172
+ );
173
+
174
+ if (originalUpdate.$set) {
175
+ const updatedSetUpdate = await this.applyAll(
176
+ originalUpdate.$set,
177
+ field,
178
+ this.encrypt.bind(this)
179
+ );
180
+ updatedUpdate.$set = { ...updatedSetUpdate };
181
+ }
182
+
183
+ if (originalUpdate.$setOnInsert) {
184
+ const updatedSetOnInsertUpdate = await this.applyAll(
185
+ originalUpdate.$setOnInsert,
186
+ field,
187
+ this.encrypt.bind(this)
188
+ );
189
+ updatedUpdate.$setOnInsert = { ...updatedSetOnInsertUpdate };
190
+ }
191
+
192
+ query.setUpdate(updatedUpdate);
193
+ }
194
+ }
195
+
196
+ expectNotToUpdateManyEncrypted(update) {
197
+ for (const field of this.fields) {
198
+ if (update.$set && hasValue(update.$set[field])) {
199
+ throw new Error(
200
+ 'Attempted to update encrypted field of multiple documents'
201
+ );
202
+ }
203
+
204
+ if (update.$setOnInsert && hasValue(update.$setOnInsert[field])) {
205
+ throw new Error(
206
+ 'Attempted to update encrypted field of multiple documents'
207
+ );
208
+ }
209
+
210
+ if (hasValue(update[field])) {
211
+ throw new Error(
212
+ 'Attempted to update encrypted field of multiple documents'
213
+ );
214
+ }
215
+ }
216
+ }
217
+
218
+ async encrypt(text) {
219
+ const { keyId, encryptedKey, plaintext } = await this.generateDataKey();
220
+ const encryptedText = aes.encrypt(text, plaintext);
221
+
222
+ return `${keyId}:${encryptedText}:${encryptedKey}`;
223
+ }
224
+
225
+ async decrypt(text) {
226
+ const split = text.split(':');
227
+ const keyId = Buffer.from(split[0], 'base64').toString();
228
+ const encryptedText = `${split[1]}:${split[2]}`;
229
+ const encryptedKey = Buffer.from(split[3], 'base64');
230
+ const plaintext = await this.decryptDataKey(keyId, encryptedKey);
231
+
232
+ return aes.decrypt(encryptedText, plaintext);
233
+ }
234
+ }
235
+
236
+ module.exports = { Cryptor };
@@ -0,0 +1,32 @@
1
+ const { Cryptor } = require('./Cryptor');
2
+
3
+ describe('Cryptor', () => {
4
+ describe('Permutations', () => {
5
+ it('calculates permutations correctly', async () => {
6
+ // Given a nested field, we want all possible paths that could access it.
7
+ const cryptor = new Cryptor({ fields: ['a.b.c.d', 'e'] });
8
+ expect(cryptor.permutationsByField).toEqual({
9
+ 'a.b.c.d': [
10
+ ['a', 'b', 'c', 'd'],
11
+ ['a', 'b', 'c.d'],
12
+ ['a', 'b.c', 'd'],
13
+ ['a', 'b.c.d'],
14
+ ['a.b', 'c', 'd'],
15
+ ['a.b', 'c.d'],
16
+ ['a.b.c', 'd'],
17
+ ['a.b.c.d'],
18
+ ],
19
+ e: [['e']],
20
+ });
21
+ });
22
+ });
23
+
24
+ describe('Keys', () => {
25
+ it('raises error on missing environment', () => {
26
+ const cryptor = new Cryptor({ fields: ['a.b.c.d', 'e'] });
27
+ expect(cryptor.getKeyFromEnvironment).toThrow(
28
+ 'No encryption key found with ID "undefined"'
29
+ );
30
+ });
31
+ });
32
+ });
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Left Hook Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,3 @@
1
+ # encrypt
2
+
3
+ This package exports the `encrypt` mongoose plugin used in [Frigg](https://friggframework.org). You can find its documentation [on Frigg's website](https://docs.friggframework.org/packages/encrypt).
package/encrypt/aes.js ADDED
@@ -0,0 +1,27 @@
1
+ const crypto = require('crypto');
2
+
3
+ const algorithm = 'aes-256-ctr';
4
+
5
+ function encrypt(text, key) {
6
+ const iv = crypto.randomBytes(16);
7
+ const randomString = iv.toString('hex');
8
+
9
+ const cipher = crypto.createCipheriv(algorithm, key, iv);
10
+ let crypted = cipher.update(text, 'utf8', 'hex');
11
+ crypted += cipher.final('hex');
12
+ return `${randomString}:${crypted}`;
13
+ }
14
+
15
+ function decrypt(text, key) {
16
+ const parts = text.toString().split(':');
17
+ const iv = Buffer.from(parts[0], 'hex');
18
+ const decipher = crypto.createDecipheriv(algorithm, key, iv);
19
+ let dec = decipher.update(parts[1], 'hex', 'utf8');
20
+ dec += decipher.final('utf8');
21
+ return dec;
22
+ }
23
+
24
+ module.exports = {
25
+ encrypt,
26
+ decrypt,
27
+ };
File without changes