Skip to content

Commit eab7e49

Browse files
comments
1 parent 7634218 commit eab7e49

File tree

8 files changed

+125
-114
lines changed

8 files changed

+125
-114
lines changed

docs/field-level-encryption.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,9 +164,9 @@ const UserModel = connection.model('User', encryptedUserSchema);
164164

165165
### Connecting and configuring encryption options
166166

167-
CSFLE/QE in Mongoose work by generating the encryption schema that the MongoDB driver expects for each encrypted model on the connection. This happens automatically the model's connection is established.
167+
CSFLE/QE in Mongoose work by generating the encryption schema that the MongoDB driver expects for each encrypted model on the connection. This happens automatically when the model's connection is established.
168168

169-
Queryable encryption and CSFLE requires all the same configuration as outlined in <>, except for the schemaMap or encryptedFieldsMap options.
169+
Queryable encryption and CSFLE requires all the same configuration as outlined in the [MongoDB encryption in-use documentation](https://www.mongodb.com/docs/manual/core/security-in-use-encryption/), except for the schemaMap or encryptedFieldsMap options.
170170

171171
```javascript
172172
const keyVaultNamespace = 'client.encryption';
@@ -215,7 +215,7 @@ const ModelWithBirthday = model.discriminator('ModelWithBirthday', new Schema({
215215
}));
216216
```
217217

218-
When generating encryption schemas, Mongoose merges all discriminators together for the all discriminators declared on the same namespace. As a result, discriminators that declare the same key with different types are not supported. Furthermore, all discriminators must share the same encryption type - it is not possible to configure discriminators on the same model for both CSFLE and QE.
218+
When generating encryption schemas, Mongoose merges all discriminators together for all of the discriminators declared on the same namespace. As a result, discriminators that declare the same key with different types are not supported. Furthermore, all discriminators must share the same encryption type - it is not possible to configure discriminators on the same model for both CSFLE and QE.
219219

220220
## Managing Data Keys
221221

lib/model.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const minimize = require('./helpers/minimize');
6969
const MongooseBulkSaveIncompleteError = require('./error/bulkSaveIncompleteError');
7070
const ObjectExpectedError = require('./error/objectExpected');
7171
const decorateBulkWriteResult = require('./helpers/model/decorateBulkWriteResult');
72+
const { ClientEncryption } = require('mongodb');
7273
const modelCollectionSymbol = Symbol('mongoose#Model#collection');
7374
const modelDbSymbol = Symbol('mongoose#Model#db');
7475
const modelSymbol = require('./helpers/symbols').modelSymbol;
@@ -4879,6 +4880,12 @@ Model.compile = function compile(name, schema, collectionName, connection, base)
48794880
return model;
48804881
};
48814882

4883+
/**
4884+
* If auto encryption is enabled, returns a ClientEncryption instance that is configured with the same settings that
4885+
* Mongoose's underlying MongoClient is using. If the client has not yet been configured, returns null.
4886+
*
4887+
* @returns {ClientEncryption | null}
4888+
*/
48824889
Model.clientEncryption = function clientEncryption() {
48834890
const ClientEncryption = this.base.driver.get().ClientEncryption;
48844891
if (!ClientEncryption) {

lib/schema.js

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -499,7 +499,7 @@ Schema.prototype.pick = function(paths, options) {
499499
}
500500

501501
for (const path of paths) {
502-
if (path in this.encryptedFields) {
502+
if (this._hasEncryptedField(path)) {
503503
const encrypt = this.encryptedFields[path];
504504
const schemaType = this.path(path);
505505
newSchema.add({
@@ -508,8 +508,7 @@ Schema.prototype.pick = function(paths, options) {
508508
[this.options.typeKey]: schemaType
509509
}
510510
});
511-
}
512-
else if (this.nested[path]) {
511+
} else if (this.nested[path]) {
513512
newSchema.add({ [path]: get(this.tree, path) });
514513
} else {
515514
const schematype = this.path(path);
@@ -689,14 +688,16 @@ Schema.prototype._defaultToObjectOptions = function(json) {
689688
* Sets the encryption type of the schema, if a value is provided, otherwise
690689
* returns the encryption type.
691690
*
692-
* @param {'csfle' | 'queryableEncryption' | undefined} encryptionType plain object with paths to add, or another schema
691+
* @param {'csfle' | 'queryableEncryption' | null | undefined} encryptionType plain object with paths to add, or another schema
693692
*/
694693
Schema.prototype.encryptionType = function encryptionType(encryptionType) {
695-
if (typeof encryptionType === 'string' || encryptionType === null) {
696-
this.options.encryptionType = encryptionType;
697-
} else {
694+
if (arguments.length === 0) {
698695
return this.options.encryptionType;
699696
}
697+
if (!(typeof encryptionType === 'string' || encryptionType === null)) {
698+
throw new Error('invalid `encryptionType`: ${encryptionType}');
699+
}
700+
this.options.encryptionType = encryptionType;
700701
};
701702

702703
/**
@@ -860,8 +861,7 @@ Schema.prototype.add = function add(obj, prefix) {
860861
const path = fullPath + '.' + encryptedField;
861862
this._addEncryptedField(path, encryptedFieldConfig);
862863
}
863-
}
864-
else if (typeof val === 'object' && 'encrypt' in val) {
864+
} else if (typeof val === 'object' && 'encrypt' in val) {
865865
// schema.add({ field: { type: <schema type>, encrypt: { ... }}})
866866
const { encrypt } = val;
867867

@@ -900,6 +900,8 @@ Schema.prototype._addEncryptedField = function _addEncryptedField(path, fieldCon
900900
};
901901

902902
/**
903+
* @param {string} path
904+
*
903905
* @api private
904906
*/
905907
Schema.prototype._removeEncryptedField = function _removeEncryptedField(path) {
@@ -908,12 +910,17 @@ Schema.prototype._removeEncryptedField = function _removeEncryptedField(path) {
908910

909911
/**
910912
* @api private
913+
*
914+
* @returns {boolean}
911915
*/
912916
Schema.prototype._hasEncryptedFields = function _hasEncryptedFields() {
913917
return Object.keys(this.encryptedFields).length > 0;
914918
};
915919

916920
/**
921+
* @param {string} path
922+
* @returns {boolean}
923+
*
917924
* @api private
918925
*/
919926
Schema.prototype._hasEncryptedField = function _hasEncryptedField(path) {
@@ -923,6 +930,8 @@ Schema.prototype._hasEncryptedField = function _hasEncryptedField(path) {
923930

924931
/**
925932
* Builds an encryptedFieldsMap for the schema.
933+
*
934+
* @api private
926935
*/
927936
Schema.prototype._buildEncryptedFields = function() {
928937
const fields = Object.entries(this.encryptedFields).map(
@@ -937,6 +946,8 @@ Schema.prototype._buildEncryptedFields = function() {
937946

938947
/**
939948
* Builds a schemaMap for the schema, if the schema is configured for client-side field level encryption.
949+
*
950+
* @api private
940951
*/
941952
Schema.prototype._buildSchemaMap = function() {
942953
/**
@@ -947,7 +958,7 @@ Schema.prototype._buildSchemaMap = function() {
947958
* `{ a: { bsonType: 'object', properties: { b: < encryption configuration > } } }`
948959
*
949960
* This function takes an array of path segments, an output object (that gets mutated) and
950-
* a value to associated with the full path, and constructs a valid CSFLE JSON schema path for
961+
* a value to be associated with the full path, and constructs a valid CSFLE JSON schema path for
951962
* the object. This works for deeply nested properties as well.
952963
*
953964
* @param {string[]} path array of path components

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,4 +148,5 @@
148148
"target": "ES2017"
149149
}
150150
}
151-
}
151+
}
152+

scripts/setup-encryption-tests.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
'use strict';
2+
23
const { downloadMongoDb } = require('@mongodb-js/mongodb-downloader');
34
const { instances, start } = require('mongodb-runner');
45
const { rm, readdir, writeFile } = require('fs/promises');
56
const { tmpdir } = require('os');
67
const { join, resolve } = require('path');
78

8-
99
async function main() {
1010
const runnerDir = join(resolve(__dirname), '../data');
1111
const serverVersion = '8.0';
@@ -38,12 +38,14 @@ async function main() {
3838
'sharded', runnerDir, tmpDir: tmpdir() });
3939

4040
for await (const instance of instances({ runnerDir })) {
41-
if (instance.id === 'encryption-test-cluster') return {
42-
cryptShared, uri: instance.connectionString
43-
};
41+
if (instance.id === 'encryption-test-cluster') {
42+
return {
43+
cryptShared, uri: instance.connectionString
44+
};
45+
}
4446
}
4547

46-
throw new Error('Unable to location newly configured instance of mongod - should never happen.');
48+
throw new Error('Unable to locate newly configured instance of mongod - should never happen.');
4749
}
4850
}
4951

test/encryptedSchema.test.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
'use strict';
32

43
const assert = require('assert');
@@ -13,7 +12,7 @@ const Schema = mongoose.Schema;
1312
*
1413
* @param {import('../lib').Schema} object
1514
* @param {Array<string> | string} path
16-
* @returns
15+
* @returns { boolean }
1716
*/
1817
function schemaHasEncryptedProperty(schema, path) {
1918
path = [path].flat();
@@ -240,7 +239,7 @@ describe('encrypted schema declaration', function() {
240239
});
241240
});
242241

243-
describe('When a schema is instantiated with a custom schema type plugin', function() {
242+
describe('When a schema is instantiated with a custom schema type plugin that does not have a encryption type', function() {
244243
class Int8 extends mongoose.SchemaType {
245244
constructor(key, options) {
246245
super(key, options, 'Int8');
@@ -266,6 +265,33 @@ describe('encrypted schema declaration', function() {
266265
});
267266
});
268267

268+
describe.only('When a schema is instantiated with a custom schema type plugin that does have a encryption type', function() {
269+
class Int8 extends mongoose.SchemaType {
270+
constructor(key, options) {
271+
super(key, options, 'Int8');
272+
}
273+
274+
autoEncryptionType() {
275+
return 'int';
276+
}
277+
}
278+
279+
beforeEach(function() {
280+
// Don't forget to add `Int8` to the type registry
281+
mongoose.Schema.Types.Int8 = Int8;
282+
});
283+
afterEach(function() {
284+
delete mongoose.Schema.Types.Int8;
285+
});
286+
287+
it('No error is thrown', function() {
288+
new Schema({
289+
field: {
290+
type: Int8, encrypt: { keyId: KEY_ID, algorithm }
291+
}
292+
}, { encryptionType: 'csfle' });
293+
});
294+
});
269295
});
270296

271297
describe('options.encryptionType', function() {

0 commit comments

Comments
 (0)