@friggframework/core 2.0.0-next.40 → 2.0.0-next.42

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 (196) hide show
  1. package/CLAUDE.md +693 -0
  2. package/README.md +931 -50
  3. package/application/commands/README.md +421 -0
  4. package/application/commands/credential-commands.js +224 -0
  5. package/application/commands/entity-commands.js +315 -0
  6. package/application/commands/integration-commands.js +160 -0
  7. package/application/commands/integration-commands.test.js +123 -0
  8. package/application/commands/user-commands.js +213 -0
  9. package/application/index.js +69 -0
  10. package/core/CLAUDE.md +690 -0
  11. package/core/create-handler.js +0 -6
  12. package/credential/repositories/credential-repository-factory.js +47 -0
  13. package/credential/repositories/credential-repository-interface.js +98 -0
  14. package/credential/repositories/credential-repository-mongo.js +301 -0
  15. package/credential/repositories/credential-repository-postgres.js +307 -0
  16. package/credential/repositories/credential-repository.js +307 -0
  17. package/credential/use-cases/get-credential-for-user.js +21 -0
  18. package/credential/use-cases/update-authentication-status.js +15 -0
  19. package/database/config.js +117 -0
  20. package/database/encryption/README.md +683 -0
  21. package/database/encryption/encryption-integration.test.js +553 -0
  22. package/database/encryption/encryption-schema-registry.js +141 -0
  23. package/database/encryption/encryption-schema-registry.test.js +392 -0
  24. package/database/encryption/field-encryption-service.js +226 -0
  25. package/database/encryption/field-encryption-service.test.js +525 -0
  26. package/database/encryption/logger.js +79 -0
  27. package/database/encryption/mongo-decryption-fix-verification.test.js +348 -0
  28. package/database/encryption/postgres-decryption-fix-verification.test.js +371 -0
  29. package/database/encryption/postgres-relation-decryption.test.js +245 -0
  30. package/database/encryption/prisma-encryption-extension.js +222 -0
  31. package/database/encryption/prisma-encryption-extension.test.js +439 -0
  32. package/database/index.js +25 -12
  33. package/database/models/readme.md +1 -0
  34. package/database/prisma.js +162 -0
  35. package/database/repositories/health-check-repository-factory.js +38 -0
  36. package/database/repositories/health-check-repository-interface.js +86 -0
  37. package/database/repositories/health-check-repository-mongodb.js +72 -0
  38. package/database/repositories/health-check-repository-postgres.js +75 -0
  39. package/database/repositories/health-check-repository.js +108 -0
  40. package/database/use-cases/check-database-health-use-case.js +34 -0
  41. package/database/use-cases/check-encryption-health-use-case.js +82 -0
  42. package/database/use-cases/test-encryption-use-case.js +252 -0
  43. package/encrypt/Cryptor.js +20 -152
  44. package/encrypt/index.js +1 -2
  45. package/encrypt/test-encrypt.js +0 -2
  46. package/handlers/app-definition-loader.js +38 -0
  47. package/handlers/app-handler-helpers.js +0 -3
  48. package/handlers/auth-flow.integration.test.js +147 -0
  49. package/handlers/backend-utils.js +25 -45
  50. package/handlers/integration-event-dispatcher.js +54 -0
  51. package/handlers/integration-event-dispatcher.test.js +141 -0
  52. package/handlers/routers/HEALTHCHECK.md +103 -1
  53. package/handlers/routers/auth.js +3 -14
  54. package/handlers/routers/health.js +63 -424
  55. package/handlers/routers/health.test.js +7 -0
  56. package/handlers/routers/integration-defined-routers.js +8 -5
  57. package/handlers/routers/user.js +25 -5
  58. package/handlers/routers/websocket.js +5 -3
  59. package/handlers/use-cases/check-external-apis-health-use-case.js +81 -0
  60. package/handlers/use-cases/check-integrations-health-use-case.js +32 -0
  61. package/handlers/workers/integration-defined-workers.js +6 -3
  62. package/index.js +45 -22
  63. package/integrations/index.js +12 -10
  64. package/integrations/integration-base.js +224 -53
  65. package/integrations/integration-router.js +386 -178
  66. package/integrations/options.js +1 -1
  67. package/integrations/repositories/integration-mapping-repository-factory.js +50 -0
  68. package/integrations/repositories/integration-mapping-repository-interface.js +106 -0
  69. package/integrations/repositories/integration-mapping-repository-mongo.js +161 -0
  70. package/integrations/repositories/integration-mapping-repository-postgres.js +227 -0
  71. package/integrations/repositories/integration-mapping-repository.js +156 -0
  72. package/integrations/repositories/integration-repository-factory.js +44 -0
  73. package/integrations/repositories/integration-repository-interface.js +115 -0
  74. package/integrations/repositories/integration-repository-mongo.js +271 -0
  75. package/integrations/repositories/integration-repository-postgres.js +319 -0
  76. package/integrations/tests/doubles/dummy-integration-class.js +90 -0
  77. package/integrations/tests/doubles/test-integration-repository.js +99 -0
  78. package/integrations/tests/use-cases/create-integration.test.js +131 -0
  79. package/integrations/tests/use-cases/delete-integration-for-user.test.js +150 -0
  80. package/integrations/tests/use-cases/find-integration-context-by-external-entity-id.test.js +92 -0
  81. package/integrations/tests/use-cases/get-integration-for-user.test.js +150 -0
  82. package/integrations/tests/use-cases/get-integration-instance.test.js +176 -0
  83. package/integrations/tests/use-cases/get-integrations-for-user.test.js +176 -0
  84. package/integrations/tests/use-cases/get-possible-integrations.test.js +188 -0
  85. package/integrations/tests/use-cases/update-integration-messages.test.js +142 -0
  86. package/integrations/tests/use-cases/update-integration-status.test.js +103 -0
  87. package/integrations/tests/use-cases/update-integration.test.js +141 -0
  88. package/integrations/use-cases/create-integration.js +83 -0
  89. package/integrations/use-cases/delete-integration-for-user.js +73 -0
  90. package/integrations/use-cases/find-integration-context-by-external-entity-id.js +72 -0
  91. package/integrations/use-cases/get-integration-for-user.js +78 -0
  92. package/integrations/use-cases/get-integration-instance-by-definition.js +67 -0
  93. package/integrations/use-cases/get-integration-instance.js +83 -0
  94. package/integrations/use-cases/get-integrations-for-user.js +87 -0
  95. package/integrations/use-cases/get-possible-integrations.js +27 -0
  96. package/integrations/use-cases/index.js +11 -0
  97. package/integrations/use-cases/load-integration-context-full.test.js +329 -0
  98. package/integrations/use-cases/load-integration-context.js +71 -0
  99. package/integrations/use-cases/load-integration-context.test.js +114 -0
  100. package/integrations/use-cases/update-integration-messages.js +44 -0
  101. package/integrations/use-cases/update-integration-status.js +32 -0
  102. package/integrations/use-cases/update-integration.js +93 -0
  103. package/integrations/utils/map-integration-dto.js +36 -0
  104. package/jest-global-setup-noop.js +3 -0
  105. package/jest-global-teardown-noop.js +3 -0
  106. package/{module-plugin → modules}/entity.js +1 -0
  107. package/{module-plugin → modules}/index.js +0 -8
  108. package/modules/module-factory.js +56 -0
  109. package/modules/module-hydration.test.js +205 -0
  110. package/modules/module.js +221 -0
  111. package/modules/repositories/module-repository-factory.js +33 -0
  112. package/modules/repositories/module-repository-interface.js +129 -0
  113. package/modules/repositories/module-repository-mongo.js +386 -0
  114. package/modules/repositories/module-repository-postgres.js +437 -0
  115. package/modules/repositories/module-repository.js +327 -0
  116. package/{module-plugin → modules}/test/mock-api/api.js +8 -3
  117. package/{module-plugin → modules}/test/mock-api/definition.js +12 -8
  118. package/modules/tests/doubles/test-module-factory.js +16 -0
  119. package/modules/tests/doubles/test-module-repository.js +39 -0
  120. package/modules/use-cases/get-entities-for-user.js +32 -0
  121. package/modules/use-cases/get-entity-options-by-id.js +59 -0
  122. package/modules/use-cases/get-entity-options-by-type.js +34 -0
  123. package/modules/use-cases/get-module-instance-from-type.js +31 -0
  124. package/modules/use-cases/get-module.js +56 -0
  125. package/modules/use-cases/process-authorization-callback.js +121 -0
  126. package/modules/use-cases/refresh-entity-options.js +59 -0
  127. package/modules/use-cases/test-module-auth.js +55 -0
  128. package/modules/utils/map-module-dto.js +18 -0
  129. package/package.json +14 -6
  130. package/prisma-mongodb/schema.prisma +321 -0
  131. package/prisma-postgresql/migrations/20250930193005_init/migration.sql +315 -0
  132. package/prisma-postgresql/migrations/20251006135218_init/migration.sql +9 -0
  133. package/prisma-postgresql/migrations/migration_lock.toml +3 -0
  134. package/prisma-postgresql/schema.prisma +303 -0
  135. package/syncs/manager.js +468 -443
  136. package/syncs/repositories/sync-repository-factory.js +38 -0
  137. package/syncs/repositories/sync-repository-interface.js +109 -0
  138. package/syncs/repositories/sync-repository-mongo.js +239 -0
  139. package/syncs/repositories/sync-repository-postgres.js +319 -0
  140. package/syncs/sync.js +0 -1
  141. package/token/repositories/token-repository-factory.js +33 -0
  142. package/token/repositories/token-repository-interface.js +131 -0
  143. package/token/repositories/token-repository-mongo.js +212 -0
  144. package/token/repositories/token-repository-postgres.js +257 -0
  145. package/token/repositories/token-repository.js +219 -0
  146. package/types/integrations/index.d.ts +2 -6
  147. package/types/module-plugin/index.d.ts +5 -57
  148. package/types/syncs/index.d.ts +0 -2
  149. package/user/repositories/user-repository-factory.js +46 -0
  150. package/user/repositories/user-repository-interface.js +198 -0
  151. package/user/repositories/user-repository-mongo.js +250 -0
  152. package/user/repositories/user-repository-postgres.js +311 -0
  153. package/user/tests/doubles/test-user-repository.js +72 -0
  154. package/user/tests/use-cases/create-individual-user.test.js +24 -0
  155. package/user/tests/use-cases/create-organization-user.test.js +28 -0
  156. package/user/tests/use-cases/create-token-for-user-id.test.js +19 -0
  157. package/user/tests/use-cases/get-user-from-bearer-token.test.js +64 -0
  158. package/user/tests/use-cases/login-user.test.js +140 -0
  159. package/user/use-cases/create-individual-user.js +61 -0
  160. package/user/use-cases/create-organization-user.js +47 -0
  161. package/user/use-cases/create-token-for-user-id.js +30 -0
  162. package/user/use-cases/get-user-from-bearer-token.js +77 -0
  163. package/user/use-cases/login-user.js +122 -0
  164. package/user/user.js +77 -0
  165. package/websocket/repositories/websocket-connection-repository-factory.js +37 -0
  166. package/websocket/repositories/websocket-connection-repository-interface.js +106 -0
  167. package/websocket/repositories/websocket-connection-repository-mongo.js +155 -0
  168. package/websocket/repositories/websocket-connection-repository-postgres.js +195 -0
  169. package/websocket/repositories/websocket-connection-repository.js +160 -0
  170. package/database/models/State.js +0 -9
  171. package/database/models/Token.js +0 -70
  172. package/database/mongo.js +0 -171
  173. package/encrypt/Cryptor.test.js +0 -32
  174. package/encrypt/encrypt.js +0 -104
  175. package/encrypt/encrypt.test.js +0 -1069
  176. package/handlers/routers/middleware/loadUser.js +0 -15
  177. package/handlers/routers/middleware/requireLoggedInUser.js +0 -12
  178. package/integrations/create-frigg-backend.js +0 -31
  179. package/integrations/integration-factory.js +0 -251
  180. package/integrations/integration-mapping.js +0 -43
  181. package/integrations/integration-model.js +0 -46
  182. package/integrations/integration-user.js +0 -144
  183. package/integrations/test/integration-base.test.js +0 -144
  184. package/module-plugin/auther.js +0 -393
  185. package/module-plugin/credential.js +0 -22
  186. package/module-plugin/entity-manager.js +0 -70
  187. package/module-plugin/manager.js +0 -169
  188. package/module-plugin/module-factory.js +0 -61
  189. package/module-plugin/test/auther.test.js +0 -97
  190. /package/{module-plugin → modules}/ModuleConstants.js +0 -0
  191. /package/{module-plugin → modules}/requester/api-key.js +0 -0
  192. /package/{module-plugin → modules}/requester/basic.js +0 -0
  193. /package/{module-plugin → modules}/requester/oauth-2.js +0 -0
  194. /package/{module-plugin → modules}/requester/requester.js +0 -0
  195. /package/{module-plugin → modules}/requester/requester.test.js +0 -0
  196. /package/{module-plugin → modules}/test/mock-api/mocks/hubspot.js +0 -0
@@ -1,1069 +0,0 @@
1
- const AWS = require('aws-sdk');
2
- const { mongoose } = require('../database/mongoose');
3
- const crypto = require('crypto');
4
- const {
5
- expectValidSecret,
6
- expectValidRawDoc,
7
- expectValidRawDocById,
8
- createModel,
9
- saveTestDocument,
10
- } = require('./test-encrypt');
11
- const { TestMongo } = require('@friggframework/test');
12
-
13
- const testMongo = new TestMongo();
14
- const originalEnv = process.env;
15
-
16
- // Default LocalStack endpoint
17
- AWS.config.update({
18
- endpoint: 'localhost:4566',
19
- });
20
-
21
- describe('Encrypt', () => {
22
- beforeAll(async () => {
23
- await testMongo.start();
24
- await mongoose.connect(process.env.MONGO_URI);
25
- });
26
-
27
- afterAll(async () => {
28
- await mongoose.disconnect();
29
- await testMongo.stop();
30
- });
31
-
32
- describe('Disabled mode', () => {
33
- it('can be disabled', async () => {
34
- process.env = {
35
- ...originalEnv,
36
- STAGE: 'not-encryption-test',
37
- BYPASS_ENCRYPTION_STAGE: 'not-encryption-test',
38
- };
39
-
40
- try {
41
- const { Model } = createModel();
42
- const { doc, secret } = await saveTestDocument(Model);
43
- const rawDoc = await Model.collection.findOne({ _id: doc._id });
44
-
45
- // Test it was not encrypted in Mongo.
46
- expect(rawDoc).toHaveProperty('secret', secret);
47
- } finally {
48
- process.env = originalEnv;
49
- }
50
- });
51
-
52
- it('throws an error if both modes are set', async () => {
53
- process.env = {
54
- ...originalEnv,
55
- STAGE: 'encryption-test',
56
- AES_KEY_ID: '123',
57
- KMS_KEY_ARN: '321',
58
- };
59
-
60
- try {
61
- createModel();
62
- } catch (error) {
63
- expect(error).toHaveProperty(
64
- 'message',
65
- 'Local and AWS encryption keys are both set in the environment.'
66
- );
67
- return;
68
- } finally {
69
- process.env = originalEnv;
70
- }
71
-
72
- throw new Error('Expected error not caught.');
73
- });
74
- });
75
-
76
- describe('Local encryption functions', () => {
77
- beforeAll(() => {
78
- process.env = {
79
- ...originalEnv,
80
- STAGE: 'encryption-test',
81
- AES_KEY: crypto
82
- .createHash('sha256')
83
- .update('secret sauce')
84
- .digest(),
85
- AES_KEY_ID: '12345',
86
- };
87
- });
88
-
89
- afterAll(() => {
90
- process.env = originalEnv;
91
- });
92
-
93
- let Model;
94
- beforeAll(() => {
95
- Model = createModel().Model;
96
- });
97
-
98
- it('can instantiate the model', async () => {
99
- new Model();
100
- });
101
-
102
- it('works when no document is found with findOne', async () => {
103
- const notSecret = new mongoose.Types.ObjectId();
104
- const doc = await Model.findOne({ notSecret });
105
- expect(doc).toBe(null);
106
- });
107
-
108
- it('works when no documents are found with find', async () => {
109
- const notSecret = new mongoose.Types.ObjectId();
110
- const docs = await Model.find({ notSecret });
111
- expect(docs).toHaveLength(0);
112
- });
113
-
114
- it('can be saved', async function () {
115
- await saveTestDocument(Model);
116
- });
117
-
118
- it('can be reloaded', async function () {
119
- const { doc, notSecret } = await saveTestDocument(Model);
120
-
121
- const reloaded = await Model.findOne({ _id: doc._id });
122
- expect(reloaded).toHaveProperty('notSecret');
123
- expect(reloaded.notSecret.toString()).toBe(notSecret.toString());
124
- });
125
-
126
- it('can be reloaded (nested field)', async function () {
127
- const notSecret = new mongoose.Types.ObjectId();
128
- const secret = 'abcdefg';
129
- const doc = new Model({
130
- notSecret,
131
- 'deeply.nested.secret': secret,
132
- });
133
-
134
- expect(doc).toHaveProperty('notSecret');
135
- expect(doc.notSecret.toString()).toBe(notSecret.toString());
136
- expect(doc).toHaveProperty('deeply');
137
- expect(doc.deeply).toHaveProperty('nested');
138
- expect(doc.deeply.nested).toHaveProperty('secret', secret);
139
-
140
- await doc.save();
141
-
142
- expect(doc).toHaveProperty('notSecret');
143
- expect(doc.notSecret.toString()).toBe(notSecret.toString());
144
- expect(doc).toHaveProperty('deeply');
145
- expect(doc.deeply).toHaveProperty('nested');
146
- expect(doc.deeply.nested).toHaveProperty('secret', secret);
147
-
148
- const reloaded = await Model.findOne({ _id: doc._id });
149
- expect(reloaded).toHaveProperty('notSecret');
150
- expect(reloaded.notSecret.toString()).toBe(notSecret.toString());
151
- expect(reloaded).toHaveProperty('deeply');
152
- expect(reloaded.deeply).toHaveProperty('nested');
153
- expect(reloaded.deeply.nested).toHaveProperty('secret', secret);
154
- });
155
-
156
- it('automatically encrypts a secret field when saved', async () => {
157
- const { doc, secret, notSecret } = await saveTestDocument(Model);
158
- const rawDoc = await expectValidRawDoc(Model, doc);
159
-
160
- const reloaded = await Model.findOne({ _id: doc._id });
161
- expect(reloaded).toHaveProperty('notSecret');
162
- expect(reloaded.notSecret.toString()).toBe(notSecret.toString());
163
- expect(reloaded).toHaveProperty('secret');
164
- expect(reloaded.secret).toBe(secret);
165
- });
166
-
167
- it('automatically encrypts a secret field when using updateOne', async () => {
168
- const { doc, notSecret } = await saveTestDocument(Model);
169
- const rawDoc = await expectValidRawDoc(Model, doc);
170
-
171
- await Model.updateOne({ _id: doc._id }, { secret: 'hijklmn' });
172
-
173
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
174
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
175
-
176
- const reloaded = await Model.findOne({ _id: doc._id });
177
- expect(reloaded).toHaveProperty('secret');
178
- expect(reloaded.secret).toBe('hijklmn');
179
- });
180
-
181
- it('automatically encrypts a secret field when using updateOne and $set', async () => {
182
- const { doc, notSecret } = await saveTestDocument(Model);
183
- const rawDoc = await expectValidRawDoc(Model, doc);
184
-
185
- await Model.updateOne(
186
- { _id: doc._id },
187
- { $set: { secret: 'hijklmn' } }
188
- );
189
-
190
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
191
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
192
-
193
- const reloaded = await Model.findOne({ _id: doc._id });
194
- expect(reloaded).toHaveProperty('secret');
195
- expect(reloaded.secret).toBe('hijklmn');
196
- });
197
-
198
- it('automatically encrypts a secret field when using updateOne on document', async () => {
199
- const { doc, notSecret } = await saveTestDocument(Model);
200
- const rawDoc = await expectValidRawDoc(Model, doc);
201
-
202
- await doc.updateOne({ secret: 'hijklmn' });
203
-
204
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
205
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
206
-
207
- const reloaded = await Model.findOne({ _id: doc._id });
208
- expect(reloaded).toHaveProperty('secret');
209
- expect(reloaded.secret).toBe('hijklmn');
210
- });
211
-
212
- it('automatically encrypts and decrypts secret field when using findOneAndUpdate', async () => {
213
- const { doc, secret } = await saveTestDocument(Model);
214
- const reloaded = await Model.findOneAndUpdate(
215
- { _id: doc._id },
216
- { secret: 'beets' }
217
- );
218
-
219
- expect(reloaded).toHaveProperty('secret', secret);
220
-
221
- const updatedDoc = await Model.findOne({ _id: doc._id });
222
- expect(updatedDoc).toHaveProperty('secret', 'beets');
223
- });
224
-
225
- it('handles findOneAndUpdate with `new: true` option', async () => {
226
- const { doc, secret } = await saveTestDocument(Model);
227
- const updatedDoc = await Model.findOneAndUpdate(
228
- { _id: doc._id },
229
- { secret: 'beets' },
230
- { new: true }
231
- );
232
- expect(updatedDoc).toHaveProperty('secret', 'beets');
233
-
234
- const reloaded = await Model.findOne({ _id: doc._id });
235
- expect(reloaded).toHaveProperty('secret', 'beets');
236
- });
237
-
238
- it('automatically encrypts and decrypts secret field when using findOneAndReplace', async () => {
239
- const { doc, secret } = await saveTestDocument(Model);
240
- const reloaded = await Model.findOneAndReplace(
241
- { _id: doc._id },
242
- { secret: 'beets' }
243
- );
244
-
245
- expect(reloaded).toHaveProperty('secret', secret);
246
-
247
- const updatedDoc = await Model.findOne({ _id: doc._id });
248
- expect(updatedDoc).toHaveProperty('secret', 'beets');
249
- });
250
-
251
- it('automatically decrypts secret field when using findOneAndDelete', async () => {
252
- const { doc, secret, notSecret } = await saveTestDocument(Model);
253
-
254
- const deleted = await Model.findOneAndDelete({ _id: doc._id });
255
- expect(deleted).toHaveProperty('secret', secret);
256
-
257
- const docsForUser = await Model.find({ notSecret });
258
- expect(docsForUser).toHaveLength(0);
259
- });
260
-
261
- it('correctly handles `rawResult: true` option when using findOneAndDelete', async () => {
262
- const { doc, secret } = await saveTestDocument(Model);
263
- const deleted = await Model.findOneAndDelete(
264
- { _id: doc._id },
265
- { rawResult: true }
266
- );
267
-
268
- expect(deleted).toHaveProperty('value');
269
- expectValidSecret(deleted.value.secret);
270
- });
271
-
272
- it('automatically decrypts secret field when using findOneAndRemove', async () => {
273
- const { doc, secret, notSecret } = await saveTestDocument(Model);
274
-
275
- const deleted = await Model.findOneAndRemove({ _id: doc._id });
276
- expect(deleted).toHaveProperty('secret', secret);
277
-
278
- const docsForUser = await Model.find({ notSecret });
279
- expect(docsForUser).toHaveLength(0);
280
- });
281
-
282
- it('correctly handles `rawResult: true` option when using findOneAndRemove', async () => {
283
- const { doc, secret } = await saveTestDocument(Model);
284
- const rawDoc = await Model.findOneAndRemove(
285
- { _id: doc._id },
286
- { rawResult: true }
287
- );
288
-
289
- expect(rawDoc).toHaveProperty('value');
290
- expectValidSecret(rawDoc.value.secret);
291
- });
292
-
293
- it('automatically encrypts a secret field when using insertMany', async () => {
294
- // First create documents with secret values
295
- const notSecret = new mongoose.Types.ObjectId();
296
- const insertedDocs = await Model.insertMany([
297
- { notSecret, secret: 'qwerty' },
298
- { notSecret, secret: 'zxcvbn' },
299
- ]);
300
-
301
- expect(insertedDocs).toHaveLength(2);
302
- expect(insertedDocs[0]).toHaveProperty('secret', 'qwerty');
303
- expect(insertedDocs[1]).toHaveProperty('secret', 'zxcvbn');
304
-
305
- const rawDocs = await Model.collection.find({
306
- notSecret: notSecret.toString(),
307
- });
308
-
309
- for (const rawDoc of await rawDocs.toArray()) {
310
- expectValidSecret(rawDoc.secret);
311
- }
312
-
313
- // Finally, reload the docs with Mongoose, and ensure that the values
314
- // were successfully decrypted.
315
- const reloadedDocs = await Model.find({ notSecret });
316
- expect(reloadedDocs).toHaveLength(2);
317
- expect(reloadedDocs[0]).toHaveProperty('secret', 'qwerty');
318
- expect(reloadedDocs[1]).toHaveProperty('secret', 'zxcvbn');
319
- });
320
-
321
- it('throws if rawResult is used with insertMany', async () => {
322
- const notSecret = new mongoose.Types.ObjectId();
323
-
324
- try {
325
- await Model.insertMany([{ notSecret, secret: 'qwerty' }], {
326
- rawResult: true,
327
- });
328
- throw new Error('Expected error did not occurr.');
329
- } catch (error) {
330
- expect(error).toHaveProperty(
331
- 'message',
332
- 'Raw result not supported for insertMany with Encrypt plugin'
333
- );
334
- }
335
- });
336
-
337
- it('automatically encrypts a secret field when using updateOne', async () => {
338
- const { doc, secret } = await saveTestDocument(Model);
339
- const rawDoc = await expectValidRawDoc(Model, doc);
340
-
341
- await Model.updateOne({ _id: doc._id }, { secret: 'hijklmn' });
342
-
343
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
344
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
345
-
346
- const reloadedDoc = await Model.findOne({ _id: doc._id });
347
- expect(reloadedDoc).toHaveProperty('secret');
348
- expect(reloadedDoc.secret).toBe('hijklmn');
349
- });
350
-
351
- it('automatically encrypts a secret field when using replaceOne', async () => {
352
- const { doc, secret } = await saveTestDocument(Model);
353
-
354
- await Model.replaceOne({ _id: doc._id }, { secret: '012a457' });
355
-
356
- const rawDoc = await Model.collection.findOne({ _id: doc._id });
357
- expect(rawDoc).toHaveProperty('secret');
358
- expect(rawDoc).not.toHaveProperty('secret', secret);
359
- expectValidSecret(rawDoc.secret);
360
-
361
- const reloadedDoc = await Model.findOne({ _id: doc._id });
362
- expect(reloadedDoc).toHaveProperty('secret');
363
- expect(reloadedDoc.secret).toBe('012a457');
364
- });
365
-
366
- it('automatically encrypts a secret field when using update and $set', async () => {
367
- const { doc, notSecret } = await saveTestDocument(Model);
368
- const rawDoc = await expectValidRawDoc(Model, doc);
369
-
370
- await Model.update(
371
- { _id: doc._id },
372
- { $set: { secret: 'hijklmn' } }
373
- );
374
-
375
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
376
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
377
-
378
- const reloaded = await Model.findOne({ _id: doc._id });
379
- expect(reloaded).toHaveProperty('secret');
380
- expect(reloaded.secret).toBe('hijklmn');
381
- });
382
-
383
- it('automatically encrypts a secret field when using $setOnInsert', async () => {
384
- const { doc, notSecret, secret } = await saveTestDocument(Model);
385
- const rawDoc = await expectValidRawDoc(Model, doc);
386
-
387
- const updateResult = await Model.update(
388
- { notSecret },
389
- { $setOnInsert: { secret: 'hijklmn' } },
390
- { upsert: true }
391
- );
392
-
393
- expect(updateResult).not.toHaveProperty('upserted');
394
-
395
- const notUpdatedRawDoc = await expectValidRawDoc(Model, doc);
396
- expect(notUpdatedRawDoc.secret).toBe(rawDoc.secret);
397
-
398
- const reloadedNotUpdated = await Model.findOne({ _id: doc._id });
399
- expect(reloadedNotUpdated).toHaveProperty('secret', secret);
400
-
401
- const notSecret2 = new mongoose.Types.ObjectId();
402
- const updateResult2 = await Model.update(
403
- { notSecret: notSecret2 },
404
- { $setOnInsert: { secret: 'hijklmn' } },
405
- { upsert: true }
406
- );
407
-
408
- expect(updateResult2).toHaveProperty('upsertedCount', 1);
409
-
410
- // TODO update upsert tests. Model.update is deprecated
411
- // const upsertedRawDoc = await expectValidRawDocById(
412
- // Model,
413
- // updateResult2.upserted[0]._id
414
- // );
415
- // expect(upsertedRawDoc.secret).not.toBe(rawDoc.secret);
416
-
417
- // const reloaded = await Model.findOne({ _id: upsertedRawDoc._id });
418
- // expect(reloaded).toHaveProperty('secret', 'hijklmn');
419
- });
420
-
421
- it('throws an error if update uses an encrypted field with `multi: true`', async () => {
422
- const { doc, secret, notSecret } = await saveTestDocument(Model);
423
-
424
- try {
425
- await Model.update(
426
- { notSecret },
427
- { secret: 'change all passwords' },
428
- { multiple: true }
429
- );
430
- } catch (error) {
431
- expect(error).toHaveProperty('message');
432
- expect(error.message).toBe(
433
- 'Attempted to update encrypted field of multiple documents'
434
- );
435
-
436
- const reloaded = await Model.findOne({ _id: doc._id });
437
- expect(reloaded).toHaveProperty('secret', secret);
438
-
439
- return;
440
- }
441
-
442
- throw new Error('Did not catch expected error');
443
- });
444
-
445
- it('throws an error if updateMany uses an encrypted field', async () => {
446
- const { doc, secret, notSecret } = await saveTestDocument(Model);
447
-
448
- try {
449
- await Model.updateMany(
450
- { notSecret },
451
- { secret: 'change all passwords' }
452
- );
453
- } catch (error) {
454
- expect(error).toHaveProperty('message');
455
- expect(error.message).toBe(
456
- 'Attempted to update encrypted field of multiple documents'
457
- );
458
-
459
- const reloaded = await Model.findOne({ _id: doc._id });
460
- expect(reloaded).toHaveProperty('secret', secret);
461
-
462
- return;
463
- }
464
-
465
- throw new Error('Did not catch expected error');
466
- });
467
-
468
- it('automatically encrypts a nested field when using updateOne', async () => {
469
- const { doc, secret } = await saveTestDocument(Model);
470
-
471
- await Model.updateOne(
472
- { _id: doc._id },
473
- { 'deeply.nested.secret': 'hij2lmn' }
474
- );
475
-
476
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
477
- expectValidSecret(updatedRawDoc.deeply.nested.secret);
478
-
479
- const reloadedDoc = await Model.findOne({ _id: doc._id });
480
- expect(reloadedDoc).toHaveProperty('deeply');
481
- expect(reloadedDoc.deeply).toHaveProperty('nested');
482
- expect(reloadedDoc.deeply.nested).toHaveProperty(
483
- 'secret',
484
- 'hij2lmn'
485
- );
486
- });
487
-
488
- it('automatically encrypts a nested field when using update and $set', async () => {
489
- const { doc, notSecret } = await saveTestDocument(Model);
490
-
491
- await Model.update(
492
- { _id: doc._id },
493
- {
494
- $set: { deeply: { nested: { secret: 'h3jklmn' } } },
495
- }
496
- );
497
-
498
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
499
- expectValidSecret(updatedRawDoc.deeply.nested.secret);
500
-
501
- const reloaded = await Model.findOne({ _id: doc._id });
502
- expect(reloaded).toHaveProperty('deeply');
503
- expect(reloaded.deeply).toHaveProperty('nested');
504
- expect(reloaded.deeply.nested).toHaveProperty('secret', 'h3jklmn');
505
- });
506
-
507
- it('automatically encrypts a secret field when using $setOnInsert', async () => {
508
- const { doc, notSecret, secret } = await saveTestDocument(Model);
509
-
510
- const updateResult = await Model.update(
511
- { notSecret },
512
- { $setOnInsert: { deeply: { 'nested.secret': 'hijkl4n' } } },
513
- { upsert: true }
514
- );
515
-
516
- expect(updateResult).toHaveProperty('upsertedCount', 0);
517
-
518
- const notUpdatedRawDoc = await expectValidRawDoc(Model, doc);
519
- expect(notUpdatedRawDoc).not.toHaveProperty('deeply');
520
-
521
- const notSecret2 = new mongoose.Types.ObjectId();
522
- const updateResult2 = await Model.update(
523
- { notSecret: notSecret2 },
524
- { $setOnInsert: { deeply: { 'nested.secret': 'hijkl4n' } } },
525
- { upsert: true }
526
- );
527
-
528
- expect(updateResult2).toHaveProperty('upsertedCount', 1);
529
-
530
- // TODO Model.update is deprecated
531
- // const upsertedRawDoc = await Model.collection.findOne({
532
- // _id: updateResult2.upserted[0]._id,
533
- // });
534
- // expectValidSecret(upsertedRawDoc.deeply.nested.secret);
535
-
536
- // const reloaded = await Model.findOne({ _id: upsertedRawDoc._id });
537
- // expect(reloaded).toHaveProperty('deeply');
538
- // expect(reloaded.deeply).toHaveProperty('nested');
539
- // expect(reloaded.deeply.nested).toHaveProperty('secret', 'hijkl4n');
540
- });
541
-
542
- it('can use the deprecated key', async () => {
543
- const { Model } = createModel();
544
- const key = crypto
545
- .createHash('sha256')
546
- .update('secret sauce')
547
- .digest();
548
-
549
- process.env = {
550
- ...originalEnv,
551
- STAGE: 'encryption-test',
552
- AES_KEY: key,
553
- AES_KEY_ID: '12345',
554
- };
555
-
556
- try {
557
- const { doc } = await saveTestDocument(Model);
558
- await expectValidRawDoc(Model, doc);
559
-
560
- process.env = {
561
- ...originalEnv,
562
- STAGE: 'encryption-test',
563
- AES_KEY: crypto
564
- .createHash('sha256')
565
- .update('secret 2')
566
- .digest(),
567
- AES_KEY_ID: '67890',
568
- DEPRECATED_AES_KEY: key,
569
- DEPRECATED_AES_KEY_ID: '12345',
570
- };
571
-
572
- const { doc: doc2 } = await saveTestDocument(Model);
573
- await expectValidRawDoc(Model, doc2);
574
- } finally {
575
- process.env = originalEnv;
576
- }
577
- });
578
- });
579
-
580
- describe.skip('Using KMS', () => {
581
- beforeAll(() => {
582
- process.env = {
583
- ...originalEnv,
584
- STAGE: 'encryption-test',
585
- AWS_ACCESS_KEY_ID: 'test',
586
- AWS_SECRET_ACCESS_KEY: 'test',
587
- AWS_REGION: 'us-east-1',
588
- // This is needed for testing because LocalStack uses a self-signed certificate
589
- NODE_TLS_REJECT_UNAUTHORIZED: '0',
590
- };
591
- });
592
-
593
- // Create a CMK for testing
594
- beforeAll(async () => {
595
- const kmsClient = new AWS.KMS();
596
- const { KeyMetadata: keyMetadata } = await kmsClient
597
- .createKey()
598
- .promise();
599
- process.env.KMS_KEY_ARN = keyMetadata.KeyId;
600
- });
601
-
602
- afterAll(() => {
603
- process.env = originalEnv;
604
- });
605
-
606
- let Model;
607
- beforeAll(() => {
608
- Model = createModel().Model;
609
- });
610
-
611
- it('can instantiate the model', async () => {
612
- new Model();
613
- });
614
-
615
- it('works when no document is found with findOne', async () => {
616
- const notSecret = new mongoose.Types.ObjectId();
617
- const doc = await Model.findOne({ notSecret });
618
- expect(doc).toBe(null);
619
- });
620
-
621
- it('works when no documents are found with find', async () => {
622
- const notSecret = new mongoose.Types.ObjectId();
623
- const docs = await Model.find({ notSecret });
624
- expect(docs).toHaveLength(0);
625
- });
626
-
627
- it('can be saved', async function () {
628
- await saveTestDocument(Model);
629
- });
630
-
631
- it('can be reloaded', async function () {
632
- const { doc, notSecret } = await saveTestDocument(Model);
633
-
634
- const reloaded = await Model.findOne({ _id: doc._id });
635
- expect(reloaded).toHaveProperty('notSecret');
636
- expect(reloaded.notSecret.toString()).toBe(notSecret.toString());
637
- });
638
-
639
- it('can be reloaded (nested field)', async function () {
640
- const notSecret = new mongoose.Types.ObjectId();
641
- const secret = 'abcdefg';
642
- const doc = new Model({
643
- notSecret,
644
- 'deeply.nested.secret': secret,
645
- });
646
-
647
- expect(doc).toHaveProperty('notSecret');
648
- expect(doc.notSecret.toString()).toBe(notSecret.toString());
649
- expect(doc).toHaveProperty('deeply');
650
- expect(doc.deeply).toHaveProperty('nested');
651
- expect(doc.deeply.nested).toHaveProperty('secret', secret);
652
-
653
- await doc.save();
654
-
655
- expect(doc).toHaveProperty('notSecret');
656
- expect(doc.notSecret.toString()).toBe(notSecret.toString());
657
- expect(doc).toHaveProperty('deeply');
658
- expect(doc.deeply).toHaveProperty('nested');
659
- expect(doc.deeply.nested).toHaveProperty('secret', secret);
660
-
661
- const reloaded = await Model.findOne({ _id: doc._id });
662
- expect(reloaded).toHaveProperty('notSecret');
663
- expect(reloaded.notSecret.toString()).toBe(notSecret.toString());
664
- expect(reloaded).toHaveProperty('deeply');
665
- expect(reloaded.deeply).toHaveProperty('nested');
666
- expect(reloaded.deeply.nested).toHaveProperty('secret', secret);
667
- });
668
-
669
- it('automatically encrypts a secret field when saved', async () => {
670
- const { doc, secret, notSecret } = await saveTestDocument(Model);
671
- const rawDoc = await expectValidRawDoc(Model, doc);
672
-
673
- const reloaded = await Model.findOne({ _id: doc._id });
674
- expect(reloaded).toHaveProperty('notSecret');
675
- expect(reloaded.notSecret.toString()).toBe(notSecret.toString());
676
- expect(reloaded).toHaveProperty('secret');
677
- expect(reloaded.secret).toBe(secret);
678
- });
679
-
680
- it('automatically encrypts a secret field when using updateOne', async () => {
681
- const { doc, notSecret } = await saveTestDocument(Model);
682
- const rawDoc = await expectValidRawDoc(Model, doc);
683
-
684
- await Model.updateOne({ _id: doc._id }, { secret: 'hijklmn' });
685
-
686
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
687
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
688
-
689
- const reloaded = await Model.findOne({ _id: doc._id });
690
- expect(reloaded).toHaveProperty('secret');
691
- expect(reloaded.secret).toBe('hijklmn');
692
- });
693
-
694
- it('automatically encrypts a secret field when using updateOne and $set', async () => {
695
- const { doc, notSecret } = await saveTestDocument(Model);
696
- const rawDoc = await expectValidRawDoc(Model, doc);
697
-
698
- await Model.updateOne(
699
- { _id: doc._id },
700
- { $set: { secret: 'hijklmn' } }
701
- );
702
-
703
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
704
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
705
-
706
- const reloaded = await Model.findOne({ _id: doc._id });
707
- expect(reloaded).toHaveProperty('secret');
708
- expect(reloaded.secret).toBe('hijklmn');
709
- });
710
-
711
- it('automatically encrypts a secret field when using updateOne on document', async () => {
712
- const { doc, notSecret } = await saveTestDocument(Model);
713
- const rawDoc = await expectValidRawDoc(Model, doc);
714
-
715
- await doc.updateOne({ secret: 'hijklmn' });
716
-
717
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
718
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
719
-
720
- const reloaded = await Model.findOne({ _id: doc._id });
721
- expect(reloaded).toHaveProperty('secret');
722
- expect(reloaded.secret).toBe('hijklmn');
723
- });
724
-
725
- it('automatically encrypts and decrypts secret field when using findOneAndUpdate', async () => {
726
- const { doc, secret } = await saveTestDocument(Model);
727
- const reloaded = await Model.findOneAndUpdate(
728
- { _id: doc._id },
729
- { secret: 'beets' }
730
- );
731
-
732
- expect(reloaded).toHaveProperty('secret', secret);
733
-
734
- const updatedDoc = await Model.findOne({ _id: doc._id });
735
- expect(updatedDoc).toHaveProperty('secret', 'beets');
736
- });
737
-
738
- it('handles findOneAndUpdate with `new: true` option', async () => {
739
- const { doc, secret } = await saveTestDocument(Model);
740
- const updatedDoc = await Model.findOneAndUpdate(
741
- { _id: doc._id },
742
- { secret: 'beets' },
743
- { new: true }
744
- );
745
- expect(updatedDoc).toHaveProperty('secret', 'beets');
746
-
747
- const reloaded = await Model.findOne({ _id: doc._id });
748
- expect(reloaded).toHaveProperty('secret', 'beets');
749
- });
750
-
751
- it('automatically encrypts and decrypts secret field when using findOneAndReplace', async () => {
752
- const { doc, secret } = await saveTestDocument(Model);
753
- const reloaded = await Model.findOneAndReplace(
754
- { _id: doc._id },
755
- { secret: 'beets' }
756
- );
757
-
758
- expect(reloaded).toHaveProperty('secret', secret);
759
-
760
- const updatedDoc = await Model.findOne({ _id: doc._id });
761
- expect(updatedDoc).toHaveProperty('secret', 'beets');
762
- });
763
-
764
- it('automatically decrypts secret field when using findOneAndDelete', async () => {
765
- const { doc, secret, notSecret } = await saveTestDocument(Model);
766
-
767
- const deleted = await Model.findOneAndDelete({ _id: doc._id });
768
- expect(deleted).toHaveProperty('secret', secret);
769
-
770
- const docsForUser = await Model.find({ notSecret });
771
- expect(docsForUser).toHaveLength(0);
772
- });
773
-
774
- it('correctly handles `rawResult: true` option when using findOneAndDelete', async () => {
775
- const { doc, secret } = await saveTestDocument(Model);
776
- const deleted = await Model.findOneAndDelete(
777
- { _id: doc._id },
778
- { rawResult: true }
779
- );
780
-
781
- expect(deleted).toHaveProperty('value');
782
- expectValidSecret(deleted.value.secret);
783
- });
784
-
785
- it('automatically decrypts secret field when using findOneAndRemove', async () => {
786
- const { doc, secret, notSecret } = await saveTestDocument(Model);
787
-
788
- const deleted = await Model.findOneAndRemove({ _id: doc._id });
789
- expect(deleted).toHaveProperty('secret', secret);
790
-
791
- const docsForUser = await Model.find({ notSecret });
792
- expect(docsForUser).toHaveLength(0);
793
- });
794
-
795
- it('correctly handles `rawResult: true` option when using findOneAndRemove', async () => {
796
- const { doc, secret } = await saveTestDocument(Model);
797
- const rawDoc = await Model.findOneAndRemove(
798
- { _id: doc._id },
799
- { rawResult: true }
800
- );
801
-
802
- expect(rawDoc).toHaveProperty('value');
803
- expectValidSecret(rawDoc.value.secret);
804
- });
805
-
806
- it('automatically encrypts a secret field when using insertMany', async () => {
807
- // First create documents with secret values
808
- const notSecret = new mongoose.Types.ObjectId();
809
- const insertedDocs = await Model.insertMany([
810
- { notSecret, secret: 'qwerty' },
811
- { notSecret, secret: 'zxcvbn' },
812
- ]);
813
-
814
- expect(insertedDocs).toHaveLength(2);
815
- expect(insertedDocs[0]).toHaveProperty('secret', 'qwerty');
816
- expect(insertedDocs[1]).toHaveProperty('secret', 'zxcvbn');
817
-
818
- const rawDocs = await Model.collection.find({
819
- notSecret: notSecret.toString(),
820
- });
821
-
822
- for (const rawDoc of await rawDocs.toArray()) {
823
- expectValidSecret(rawDoc.secret);
824
- }
825
-
826
- // Finally, reload the docs with Mongoose, and ensure that the values
827
- // were successfully decrypted.
828
- const reloadedDocs = await Model.find({ notSecret });
829
- expect(reloadedDocs).toHaveLength(2);
830
- expect(reloadedDocs[0]).toHaveProperty('secret', 'qwerty');
831
- expect(reloadedDocs[1]).toHaveProperty('secret', 'zxcvbn');
832
- });
833
-
834
- it('throws if rawResult is used with insertMany', async () => {
835
- const notSecret = new mongoose.Types.ObjectId();
836
-
837
- try {
838
- await Model.insertMany([{ notSecret, secret: 'qwerty' }], {
839
- rawResult: true,
840
- });
841
- throw new Error('Expected error did not occurr.');
842
- } catch (error) {
843
- expect(error).toHaveProperty(
844
- 'message',
845
- 'Raw result not supported for insertMany with Encrypt plugin'
846
- );
847
- }
848
- });
849
-
850
- it('automatically encrypts a secret field when using updateOne', async () => {
851
- const { doc, secret } = await saveTestDocument(Model);
852
- const rawDoc = await expectValidRawDoc(Model, doc);
853
-
854
- await Model.updateOne({ _id: doc._id }, { secret: 'hijklmn' });
855
-
856
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
857
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
858
-
859
- const reloadedDoc = await Model.findOne({ _id: doc._id });
860
- expect(reloadedDoc).toHaveProperty('secret');
861
- expect(reloadedDoc.secret).toBe('hijklmn');
862
- });
863
-
864
- it('automatically encrypts a secret field when using replaceOne', async () => {
865
- const { doc, secret } = await saveTestDocument(Model);
866
-
867
- await Model.replaceOne({ _id: doc._id }, { secret: '012a457' });
868
-
869
- const rawDoc = await Model.collection.findOne({ _id: doc._id });
870
- expect(rawDoc).toHaveProperty('secret');
871
- expect(rawDoc).not.toHaveProperty('secret', secret);
872
- expectValidSecret(rawDoc.secret);
873
-
874
- const reloadedDoc = await Model.findOne({ _id: doc._id });
875
- expect(reloadedDoc).toHaveProperty('secret');
876
- expect(reloadedDoc.secret).toBe('012a457');
877
- });
878
-
879
- it('automatically encrypts a secret field when using update and $set', async () => {
880
- const { doc, notSecret } = await saveTestDocument(Model);
881
- const rawDoc = await expectValidRawDoc(Model, doc);
882
-
883
- await Model.update(
884
- { _id: doc._id },
885
- { $set: { secret: 'hijklmn' } }
886
- );
887
-
888
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
889
- expect(updatedRawDoc.secret).not.toBe(rawDoc.secret);
890
-
891
- const reloaded = await Model.findOne({ _id: doc._id });
892
- expect(reloaded).toHaveProperty('secret');
893
- expect(reloaded.secret).toBe('hijklmn');
894
- });
895
-
896
- it('automatically encrypts a secret field when using $setOnInsert', async () => {
897
- const { doc, notSecret, secret } = await saveTestDocument(Model);
898
- const rawDoc = await expectValidRawDoc(Model, doc);
899
-
900
- const updateResult = await Model.update(
901
- { notSecret },
902
- { $setOnInsert: { secret: 'hijklmn' } },
903
- { upsert: true }
904
- );
905
-
906
- expect(updateResult).not.toHaveProperty('upserted');
907
-
908
- const notUpdatedRawDoc = await expectValidRawDoc(Model, doc);
909
- expect(notUpdatedRawDoc.secret).toBe(rawDoc.secret);
910
-
911
- const reloadedNotUpdated = await Model.findOne({ _id: doc._id });
912
- expect(reloadedNotUpdated).toHaveProperty('secret', secret);
913
-
914
- const notSecret2 = new mongoose.Types.ObjectId();
915
- const updateResult2 = await Model.update(
916
- { notSecret: notSecret2 },
917
- { $setOnInsert: { secret: 'hijklmn' } },
918
- { upsert: true }
919
- );
920
-
921
- expect(updateResult2).toHaveProperty('upsertedCount', 1);
922
-
923
- // TODO Model.update deprecated
924
- // expect(updateResult2.upserted).toHaveLength(1);
925
- // expect(updateResult2.upserted[0]).toHaveProperty('_id');
926
- // expect(updateResult2.upserted[0]).not.toHaveProperty(
927
- // '_id',
928
- // doc._id.toString()
929
- // );
930
-
931
- // const upsertedRawDoc = await expectValidRawDocById(
932
- // Model,
933
- // updateResult2.upserted[0]._id
934
- // );
935
- // expect(upsertedRawDoc.secret).not.toBe(rawDoc.secret);
936
-
937
- // const reloaded = await Model.findOne({ _id: upsertedRawDoc._id });
938
- // expect(reloaded).toHaveProperty('secret', 'hijklmn');
939
- });
940
-
941
- it('throws an error if update uses an encrypted field with `multi: true`', async () => {
942
- const { doc, secret, notSecret } = await saveTestDocument(Model);
943
-
944
- try {
945
- await Model.update(
946
- { notSecret },
947
- { secret: 'change all passwords' },
948
- { multiple: true }
949
- );
950
- } catch (error) {
951
- expect(error).toHaveProperty('message');
952
- expect(error.message).toBe(
953
- 'Attempted to update encrypted field of multiple documents'
954
- );
955
-
956
- const reloaded = await Model.findOne({ _id: doc._id });
957
- expect(reloaded).toHaveProperty('secret', secret);
958
-
959
- return;
960
- }
961
-
962
- throw new Error('Did not catch expected error');
963
- });
964
-
965
- it('throws an error if updateMany uses an encrypted field', async () => {
966
- const { doc, secret, notSecret } = await saveTestDocument(Model);
967
-
968
- try {
969
- await Model.updateMany(
970
- { notSecret },
971
- { secret: 'change all passwords' }
972
- );
973
- } catch (error) {
974
- expect(error).toHaveProperty('message');
975
- expect(error.message).toBe(
976
- 'Attempted to update encrypted field of multiple documents'
977
- );
978
-
979
- const reloaded = await Model.findOne({ _id: doc._id });
980
- expect(reloaded).toHaveProperty('secret', secret);
981
-
982
- return;
983
- }
984
-
985
- throw new Error('Did not catch expected error');
986
- });
987
-
988
- it('automatically encrypts a nested field when using updateOne', async () => {
989
- const { doc, secret } = await saveTestDocument(Model);
990
-
991
- await Model.updateOne(
992
- { _id: doc._id },
993
- { 'deeply.nested.secret': 'hij2lmn' }
994
- );
995
-
996
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
997
- expectValidSecret(updatedRawDoc.deeply.nested.secret);
998
-
999
- const reloadedDoc = await Model.findOne({ _id: doc._id });
1000
- expect(reloadedDoc).toHaveProperty('deeply');
1001
- expect(reloadedDoc.deeply).toHaveProperty('nested');
1002
- expect(reloadedDoc.deeply.nested).toHaveProperty(
1003
- 'secret',
1004
- 'hij2lmn'
1005
- );
1006
- });
1007
-
1008
- it('automatically encrypts a nested field when using update and $set', async () => {
1009
- const { doc, notSecret } = await saveTestDocument(Model);
1010
-
1011
- await Model.update(
1012
- { _id: doc._id },
1013
- {
1014
- $set: { deeply: { nested: { secret: 'h3jklmn' } } },
1015
- }
1016
- );
1017
-
1018
- const updatedRawDoc = await expectValidRawDoc(Model, doc);
1019
- expectValidSecret(updatedRawDoc.deeply.nested.secret);
1020
-
1021
- const reloaded = await Model.findOne({ _id: doc._id });
1022
- expect(reloaded).toHaveProperty('deeply');
1023
- expect(reloaded.deeply).toHaveProperty('nested');
1024
- expect(reloaded.deeply.nested).toHaveProperty('secret', 'h3jklmn');
1025
- });
1026
-
1027
- it('automatically encrypts a secret field when using $setOnInsert', async () => {
1028
- const { doc, notSecret, secret } = await saveTestDocument(Model);
1029
-
1030
- const updateResult = await Model.update(
1031
- { notSecret },
1032
- { $setOnInsert: { deeply: { 'nested.secret': 'hijkl4n' } } },
1033
- { upsert: true }
1034
- );
1035
-
1036
- expect(updateResult).not.toHaveProperty('upserted');
1037
-
1038
- const notUpdatedRawDoc = await expectValidRawDoc(Model, doc);
1039
- expect(notUpdatedRawDoc).not.toHaveProperty('deeply');
1040
-
1041
- const notSecret2 = new mongoose.Types.ObjectId();
1042
- const updateResult2 = await Model.update(
1043
- { notSecret: notSecret2 },
1044
- { $setOnInsert: { deeply: { 'nested.secret': 'hijkl4n' } } },
1045
- { upsert: true }
1046
- );
1047
-
1048
- expect(updateResult2).toHaveProperty('upsertedCount', 1);
1049
-
1050
- // TODO Model.update is deprecated
1051
- // expect(updateResult2.upserted).toHaveLength(1);
1052
- // expect(updateResult2.upserted[0]).toHaveProperty('_id');
1053
- // expect(updateResult2.upserted[0]).not.toHaveProperty(
1054
- // '_id',
1055
- // doc._id.toString()
1056
- // );
1057
-
1058
- // const upsertedRawDoc = await Model.collection.findOne({
1059
- // _id: updateResult2.upserted[0]._id,
1060
- // });
1061
- // expectValidSecret(upsertedRawDoc.deeply.nested.secret);
1062
-
1063
- // const reloaded = await Model.findOne({ _id: upsertedRawDoc._id });
1064
- // expect(reloaded).toHaveProperty('deeply');
1065
- // expect(reloaded.deeply).toHaveProperty('nested');
1066
- // expect(reloaded.deeply.nested).toHaveProperty('secret', 'hijkl4n');
1067
- });
1068
- });
1069
- });