Skip to content

Commit

Permalink
Added encryptNull configuration option
Browse files Browse the repository at this point in the history
- encryptNull: when true (default) preserves existing behavior. when false,  modifies behavior in such a way that null values are not encrypted.

Co-authored-by: Adi Levy <alevy@attentigroup.com>
Co-authored-by: Victor Parmar <vic@smalldata.tech>
  • Loading branch information
3 people committed Jul 19, 2023
1 parent f9d79cd commit 9f11745
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 2 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ const results = await Message.find({ name: messageToSearchWith.name });
- `secret` (required): a string cipher (or a synchronous factory function which returns a string cipher) which is used to encrypt the data (don't lose this!)
- `useAes256Ctr` (optional, default `false`): a boolean indicating whether the older `aes-256-ctr` algorithm should be used. Note that this is strictly a backwards compatibility feature and for new installations it is recommended to leave this at default.
- `saltGenerator` (optional, default `const defaultSaltGenerator = secret => crypto.randomBytes(16);`): a function that should return either a `utf-8` encoded string that is 16 characters in length or a `Buffer` of length 16. This function is also passed the secret as shown in the default function example.
- `encryptNull` (optional, default `true`): An option to enable or disable encryption of null values.
- `notifyDecryptFails` (optional, default `true`): An option to enable or disable an exception on decryption failures. When disabled, exceptions will be inhibited, and an empty field will be returned.

### Static methods
Expand Down
10 changes: 8 additions & 2 deletions lib/mongoose-field-encryption.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use strict";

const crypto = require("crypto");

const algorithm = "aes-256-cbc";
const deprecatedAlgorithm = "aes-256-ctr";
const encryptedFieldNamePrefix = "__enc_";
Expand Down Expand Up @@ -78,7 +77,6 @@ const decrypt = function (encryptedHex, secret, decryptOptions = {}) {
throw err;
}
}

return "";
};

Expand All @@ -98,6 +96,10 @@ const fieldEncryption = function (schema, options) {
const encryptionStrategy = useAes256Ctr ? encryptAes256Ctr : encrypt;
const saltGenerator = options.saltGenerator ? options.saltGenerator : defaultSaltGenerator;

// Added option for a user to skip null values encryption.
// The default is true, for the sake of backward compitability, where user already encrypts null.
const encryptNull = options.encryptNull !== undefined ? options.encryptNull : true;

// Added option for a user to get an exception if decrypt fails.
// Maintained default behaviour that mongoose-field-encryption notifies decrypt failures.
const notifyDecryptFails = options.notifyDecryptFails !== undefined ? options.notifyDecryptFails : true;
Expand Down Expand Up @@ -146,6 +148,10 @@ const fieldEncryption = function (schema, options) {
const fieldValue = obj[field];

if (!obj[encryptedFieldName] && typeof fieldValue !== "undefined") {
if (fieldValue === null && encryptNull === false) {
// protect null value field, and do not try to encrypt it
continue;
}
if (typeof fieldValue === "string") {
// handle strings separately to maintain searchability
const value = encryptionStrategy(fieldValue, secret, saltGenerator);
Expand Down
70 changes: 70 additions & 0 deletions test/test-encryption-options.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,74 @@ describe("Test fieldEncryption options behaviour", function () {

done(new Error("should have thrown an exception"));
});

it("Demonstrate encryptNull: false option", function (done) {
const FieldEncryptionSchema = new mongoose.Schema({
noEncrypt: { type: String },
toEncrypt1: { type: String },
toEncrypt2: { type: String },
});

FieldEncryptionSchema.plugin(fieldEncryptionPlugin, {
fields: ["toEncrypt1", "toEncrypt2"],
secret: "letsdothis",
encryptNull: false,
});

const FieldEncryptionOptionsTest3 = mongoose.model("FieldEncryptionOptionsTest3", FieldEncryptionSchema);
const sut = new FieldEncryptionOptionsTest3({
noEncrypt: "clear",
toEncrypt1: "some stuff",
toEncrypt2: null
});

// when
sut.encryptFieldsSync();

// then
expect(sut.noEncrypt).to.equal("clear");
expect(sut.__enc_noEncrypt).to.be.undefined;

expect(sut.__enc_toEncrypt1).to.be.true;
expect(sut.toEncrypt1).to.not.eql("some stuff");

expect(sut.__enc_toEncrypt2).to.be.undefined;
expect(sut.toEncrypt2).to.eql(null);
done();
});

it("Demonstrate encryptNull: true option (Default behaviour)", function (done) {
const FieldEncryptionSchema = new mongoose.Schema({
noEncrypt: { type: String },
toEncrypt1: { type: String },
toEncrypt2: { type: String },
});

FieldEncryptionSchema.plugin(fieldEncryptionPlugin, {
fields: ["toEncrypt1", "toEncrypt2"],
secret: "letsdothis",
});

const FieldEncryptionOptionsTest4 = mongoose.model("FieldEncryptionOptionsTest4", FieldEncryptionSchema);
const sut = new FieldEncryptionOptionsTest4({
noEncrypt: "clear",
toEncrypt1: "some stuff",
toEncrypt2: null
});

// when
sut.encryptFieldsSync();

// then
expect(sut.noEncrypt).to.equal("clear");
expect(sut.__enc_noEncrypt).to.be.undefined;

expect(sut.__enc_toEncrypt1).to.be.true;
expect(sut.toEncrypt1).to.not.eql("some stuff");

expect(sut.__enc_toEncrypt2).to.be.true;
expect(sut.toEncrypt2).to.be.undefined;
expect(sut.__enc_toEncrypt2_d).to.exist;
done();
});
});

0 comments on commit 9f11745

Please sign in to comment.