mongoose-encryption
Version:
Simple encryption and authentication plugin for Mongoose
1,319 lines (1,100 loc) • 83.8 kB
text/coffeescript
mongoose = require 'mongoose'
bufferEqual = require 'buffer-equal-constant-time'
sinon = require 'sinon'
chai = require 'chai'
assert = chai.assert
mongoose.connect 'mongodb://localhost/mongoose-encryption-test'
encryptionKey = 'CwBDwGUwoM5YzBmzwWPSI+KjBKvWHaablbrEiDYh43Q='
signingKey = 'dLBm74RU4NW3e2i3QSifZDNXIXBd54yr7mZp0LKugVUa1X1UP9qoxoa3xfA7Ea4kdVL+JsPg9boGfREbPCb+kw=='
secret = 'correct horse battery staple CtYC/wFXnLQ1Dq8lYZSbnDuz8fTYMALPfgCqdgtpcrc'
encrypt = require '../index.js'
BasicEncryptedModel = null
BasicEncryptedModelSchema = mongoose.Schema
text: type: String
bool: type: Boolean
num: type: Number
date: type: Date
id2: type: mongoose.Schema.Types.ObjectId
arr: [ type: String ]
mix: type: mongoose.Schema.Types.Mixed
buf: type: Buffer
idx: type: String, index: true
BasicEncryptedModelSchema.plugin encrypt, secret: secret
BasicEncryptedModel = mongoose.model 'Simple', BasicEncryptedModelSchema
describe 'encrypt plugin', ->
it 'should add field _ct of type Buffer to the schema', ->
encryptedSchema = mongoose.Schema({}).plugin(encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'test')
assert.property encryptedSchema.paths, '_ct'
assert.propertyVal encryptedSchema.paths._ct, 'instance', 'Buffer'
it 'should add field _ac of type Buffer to the schema', ->
encryptedSchema = mongoose.Schema({}).plugin(encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'test')
assert.property encryptedSchema.paths, '_ac'
assert.propertyVal encryptedSchema.paths._ac, 'instance', 'Buffer'
it 'should expose an encrypt method on documents', ->
EncryptFnTestModel = mongoose.model 'EncryptFnTest', mongoose.Schema({}).plugin(encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'test')
assert.isFunction (new EncryptFnTestModel).encrypt
it 'should expose a decrypt method on documents', ->
DecryptFnTestModel = mongoose.model 'DecryptFnTest', mongoose.Schema({}).plugin(encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'test')
assert.isFunction (new DecryptFnTestModel).decrypt
it 'should expose a decryptSync method on documents', ->
DecryptSyncFnTestModel = mongoose.model 'DecryptSyncFnTest', mongoose.Schema({}).plugin(encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'test')
assert.isFunction (new DecryptSyncFnTestModel).decryptSync
it 'should expose a sign method on documents', ->
SignFnTestModel = mongoose.model 'SignFnTest', mongoose.Schema({}).plugin(encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'test')
assert.isFunction (new SignFnTestModel).sign
it 'should expose a authenticateSync method on documents', ->
AuthenticateSyncFnTestModel = mongoose.model 'AuthenticateSyncFnTest', mongoose.Schema({}).plugin(encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'test')
assert.isFunction (new AuthenticateSyncFnTestModel).authenticateSync
it 'should throw an error if installed twice on the same schema', ->
EncryptedSchema = mongoose.Schema
text: type: String
EncryptedSchema.plugin encrypt, secret: secret
assert.throw -> EncryptedSchema.plugin encrypt, secret: secret
describe 'new EncryptedModel', ->
it 'should remain unaltered', (done) ->
simpleTestDoc1 = new BasicEncryptedModel
text: 'Unencrypted text'
bool: true
num: 42
date: new Date '2014-05-19T16:39:07.536Z'
id2: '5303e65d34e1e80d7a7ce212'
arr: ['alpha', 'bravo']
mix: { str: 'A string', bool: false }
buf: new Buffer 'abcdefg'
assert.propertyVal simpleTestDoc1, 'text', 'Unencrypted text'
assert.propertyVal simpleTestDoc1, 'bool', true
assert.propertyVal simpleTestDoc1, 'num', 42
assert.property simpleTestDoc1, 'date'
assert.equal simpleTestDoc1.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
assert.equal simpleTestDoc1.id2.toString(), '5303e65d34e1e80d7a7ce212'
assert.lengthOf simpleTestDoc1.arr, 2
assert.equal simpleTestDoc1.arr[0], 'alpha'
assert.equal simpleTestDoc1.arr[1], 'bravo'
assert.property simpleTestDoc1, 'mix'
assert.deepEqual simpleTestDoc1.mix, { str: 'A string', bool: false }
assert.property simpleTestDoc1, 'buf'
assert.equal simpleTestDoc1.buf.toString(), 'abcdefg'
assert.property simpleTestDoc1, '_id'
assert.notProperty simpleTestDoc1, '_ct'
done()
describe 'document.save()', ->
before ->
@sandbox = sinon.sandbox.create()
@sandbox.spy BasicEncryptedModel.prototype, 'sign'
@sandbox.spy BasicEncryptedModel.prototype, 'encrypt'
@sandbox.spy BasicEncryptedModel.prototype, 'decryptSync'
after ->
@sandbox.restore()
beforeEach (done) ->
BasicEncryptedModel.prototype.sign.reset()
BasicEncryptedModel.prototype.encrypt.reset()
BasicEncryptedModel.prototype.decryptSync.reset()
@simpleTestDoc2 = new BasicEncryptedModel
text: 'Unencrypted text'
bool: true
num: 42
date: new Date '2014-05-19T16:39:07.536Z'
id2: '5303e65d34e1e80d7a7ce212'
arr: ['alpha', 'bravo']
mix: { str: 'A string', bool: false }
buf: new Buffer 'abcdefg'
@simpleTestDoc2.save (err) =>
assert.equal err, null
done()
afterEach (done) ->
@simpleTestDoc2.remove (err) ->
assert.equal err, null
done()
it 'saves encrypted fields', (done) ->
BasicEncryptedModel.find
_id: @simpleTestDoc2._id
_ct: $exists: true
text: $exists: false
bool: $exists: false
num: $exists: false
date: $exists: false
id2: $exists: false
arr: $exists: false
mix: $exists: false
buf: $exists: false
, (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
done()
return
it 'returns decrypted data after save', (done) ->
@simpleTestDoc2.save (err, doc) ->
return done(err) if err
try
assert.equal doc._ct, undefined
assert.equal doc._ac, undefined
assert.equal doc.text, 'Unencrypted text'
assert.equal doc.bool, true
assert.equal doc.num, 42
assert.deepEqual doc.date, new Date('2014-05-19T16:39:07.536Z')
assert.equal doc.id2, '5303e65d34e1e80d7a7ce212'
assert.equal doc.arr.toString(), ['alpha', 'bravo'].toString()
assert.deepEqual doc.mix, { str: 'A string', bool: false }
assert.deepEqual doc.buf, new Buffer 'abcdefg'
done()
catch err
done err
it 'should have called encryptSync then authenticateSync then decryptSync', ->
assert.equal @simpleTestDoc2.sign.callCount, 1
assert.equal @simpleTestDoc2.encrypt.callCount, 1
assert.equal @simpleTestDoc2.decryptSync.callCount, 1
assert @simpleTestDoc2.encrypt.calledBefore @simpleTestDoc2.decryptSync
assert @simpleTestDoc2.encrypt.calledBefore @simpleTestDoc2.sign, 'encrypted before signed'
assert @simpleTestDoc2.sign.calledBefore @simpleTestDoc2.decryptSync, 'signed before decrypted'
describe 'document.save() on encrypted document which contains nesting', ->
before ->
@schemaWithNest = mongoose.Schema
nest:
birdColor: type: String
areBirdsPretty: type: Boolean
@schemaWithNest.plugin encrypt, secret: secret
@ModelWithNest = mongoose.model 'SimpleNest', @schemaWithNest
beforeEach (done) ->
@nestTestDoc = new @ModelWithNest
nest:
birdColor: 'blue'
areBirdsPretty: true
@nestTestDoc.save (err) ->
assert.equal err, null
done()
afterEach (done) ->
@nestTestDoc.remove (err) ->
assert.equal err, null
done()
it 'encrypts nested fields', (done) ->
@ModelWithNest.find(
_id: @nestTestDoc._id
_ct: $exists: true
nest: $exists: false
).lean().exec (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
done()
it 'saves encrypted fields', (done) ->
@ModelWithNest.find
_id: @nestTestDoc._id
_ct: $exists: true
, (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
assert.isObject docs[0].nest
assert.propertyVal docs[0].nest, 'birdColor', 'blue'
assert.propertyVal docs[0].nest, 'areBirdsPretty', true
done()
return
describe 'document.save() on encrypted nested document', ->
before ->
@schema = mongoose.Schema
birdColor: type: String
areBirdsPretty: type: Boolean
@schema.plugin encrypt, secret: secret, collectionId: 'schema', encryptedFields: ['birdColor']
@schemaWithNest = mongoose.Schema
nest: @schema
@ModelWithNest = mongoose.model 'SimpleNestedBird', @schemaWithNest
beforeEach (done) ->
@nestTestDoc = new @ModelWithNest
nest:
birdColor: 'blue'
areBirdsPretty: true
@nestTestDoc.save (err, doc) ->
assert.equal err, null
done()
afterEach (done) ->
@nestTestDoc.remove (err) ->
assert.equal err, null
done()
it 'encrypts nested fields', (done) ->
@ModelWithNest.find(
_id: @nestTestDoc._id
'nest._ct': $exists: true
'nest.birdColor': $exists: false
).lean().exec (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
done()
it 'saves encrypted fields', (done) ->
@ModelWithNest.find
_id: @nestTestDoc._id
'nest._ct': $exists: true
, (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
assert.isObject docs[0].nest
assert.propertyVal docs[0].nest, 'birdColor', 'blue'
assert.propertyVal docs[0].nest, 'areBirdsPretty', true
done()
return
describe 'document.save() when only certain fields are encrypted', ->
before ->
PartiallyEncryptedModelSchema = mongoose.Schema
encryptedText: type: String
unencryptedText: type: String
PartiallyEncryptedModelSchema.plugin encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'PartiallyEncrypted', encryptedFields: ['encryptedText']
@PartiallyEncryptedModel = mongoose.model 'PartiallyEncrypted', PartiallyEncryptedModelSchema
beforeEach (done) ->
@partiallyEncryptedDoc = new @PartiallyEncryptedModel
encryptedText: 'Encrypted Text'
unencryptedText: 'Unencrypted Text'
@partiallyEncryptedDoc.save (err) ->
assert.equal err, null
done()
afterEach (done) ->
@partiallyEncryptedDoc.remove (err) ->
assert.equal err, null
done()
it 'should have decrypted fields', ->
assert.equal @partiallyEncryptedDoc.encryptedText, 'Encrypted Text'
assert.propertyVal @partiallyEncryptedDoc, 'unencryptedText', 'Unencrypted Text'
it 'should have encrypted fields undefined when encrypt is called', (done) ->
@partiallyEncryptedDoc.encrypt =>
assert.equal @partiallyEncryptedDoc.encryptedText, undefined
assert.propertyVal @partiallyEncryptedDoc, 'unencryptedText', 'Unencrypted Text'
done()
it 'should have a field _ct containing a mongoose Buffer object which appears encrypted when encrypted', (done) ->
@partiallyEncryptedDoc.encrypt =>
assert.isObject @partiallyEncryptedDoc._ct
assert.property @partiallyEncryptedDoc.toObject()._ct, 'buffer'
assert.instanceOf @partiallyEncryptedDoc.toObject()._ct.buffer, Buffer
assert.isString @partiallyEncryptedDoc.toObject()._ct.toString(), 'ciphertext can be converted to a string'
assert.throw -> JSON.parse @partiallyEncryptedDoc.toObject()._ct.toString(), 'ciphertext is not parsable json'
done()
it 'should not overwrite _ct or _ac when saved after a find that didnt retrieve _ct or _ac', (done) ->
@PartiallyEncryptedModel.findById(@partiallyEncryptedDoc).select('unencryptedText').exec (err, doc) =>
assert.equal err, null
assert.equal doc._ct, undefined
assert.equal doc._ac, undefined
assert.propertyVal doc, 'unencryptedText', 'Unencrypted Text', 'selected unencrypted fields should be found'
doc.save (err) =>
assert.equal err, null
@PartiallyEncryptedModel.findById(@partiallyEncryptedDoc).select('unencryptedText _ct _ac').exec (err, finalDoc) ->
assert.equal err, null
assert.equal finalDoc._ct, undefined
assert.propertyVal finalDoc, 'unencryptedText', 'Unencrypted Text', 'selected unencrypted fields should still be found after the select -> save'
assert.propertyVal finalDoc, 'encryptedText', 'Encrypted Text', 'encrypted fields werent overwritten during the select -> save'
done()
describe 'EncryptedModel.create()', ->
beforeEach ->
@docContents =
text: 'Unencrypted text'
bool: true
num: 42
date: new Date '2014-05-19T16:39:07.536Z'
id2: '5303e65d34e1e80d7a7ce212'
arr: ['alpha', 'bravo']
mix: { str: 'A string', bool: false }
buf: new Buffer 'abcdefg'
afterEach (done) ->
BasicEncryptedModel.remove (err) ->
assert.equal err, null
done()
return
it 'when doc created, it should pass an unencrypted version to the callback', (done) ->
BasicEncryptedModel.create @docContents, (err, doc) ->
assert.equal err, null
assert.propertyVal doc, 'text', 'Unencrypted text'
assert.propertyVal doc, 'bool', true
assert.propertyVal doc, 'num', 42
assert.property doc, 'date'
assert.equal doc.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
assert.equal doc.id2.toString(), '5303e65d34e1e80d7a7ce212'
assert.lengthOf doc.arr, 2
assert.equal doc.arr[0], 'alpha'
assert.equal doc.arr[1], 'bravo'
assert.property doc, 'mix'
assert.deepEqual doc.mix, { str: 'A string', bool: false }
assert.property doc, 'buf'
assert.equal doc.buf.toString(), 'abcdefg'
assert.property doc, '_id'
assert.notProperty doc, '_ct'
done()
it 'after doc created, should be encrypted in db', (done) ->
BasicEncryptedModel.create @docContents, (err, doc) ->
assert.equal err, null
assert.ok doc._id
BasicEncryptedModel.find
_id: doc._id
_ct: $exists: true
text: $exists: false
bool: $exists: false
num: $exists: false
date: $exists: false
id2: $exists: false
arr: $exists: false
mix: $exists: false
buf: $exists: false
, (err, docs) ->
assert.lengthOf docs, 1
done err
describe 'EncryptedModel.find()', ->
simpleTestDoc3 = null
before (done) ->
@sandbox = sinon.sandbox.create()
@sandbox.spy BasicEncryptedModel.prototype, 'authenticateSync'
@sandbox.spy BasicEncryptedModel.prototype, 'decryptSync'
simpleTestDoc3 = new BasicEncryptedModel
text: 'Unencrypted text'
bool: true
num: 42
date: new Date '2014-05-19T16:39:07.536Z'
id2: '5303e65d34e1e80d7a7ce212'
arr: ['alpha', 'bravo']
mix: { str: 'A string', bool: false }
buf: new Buffer 'abcdefg'
simpleTestDoc3.save (err) ->
assert.equal err, null
done()
beforeEach ->
BasicEncryptedModel.prototype.authenticateSync.reset()
BasicEncryptedModel.prototype.decryptSync.reset()
after (done) ->
@sandbox.restore()
simpleTestDoc3.remove (err) ->
assert.equal err, null
done()
it 'when doc found, should pass an unencrypted version to the callback', (done) ->
BasicEncryptedModel.findById simpleTestDoc3._id, (err, doc) ->
assert.equal err, null
assert.propertyVal doc, 'text', 'Unencrypted text'
assert.propertyVal doc, 'bool', true
assert.propertyVal doc, 'num', 42
assert.property doc, 'date'
assert.equal doc.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
assert.equal doc.id2.toString(), '5303e65d34e1e80d7a7ce212'
assert.lengthOf doc.arr, 2
assert.equal doc.arr[0], 'alpha'
assert.equal doc.arr[1], 'bravo'
assert.property doc, 'mix'
assert.deepEqual doc.mix, { str: 'A string', bool: false }
assert.property doc, 'buf'
assert.equal doc.buf.toString(), 'abcdefg'
assert.property doc, '_id'
assert.notProperty doc, '_ct'
done()
return
it 'when doc not found by id, should pass null to the callback', (done) ->
BasicEncryptedModel.findById '534ec48d60069bc13338b354', (err, doc) ->
assert.equal err, null
assert.equal doc, null
done()
return
it 'when doc not found by query, should pass [] to the callback', (done) ->
BasicEncryptedModel.find text: 'banana', (err, doc) ->
assert.equal err, null
assert.isArray doc
assert.lengthOf doc, 0
done()
return
it 'should have called authenticateSync then decryptSync', (done) ->
BasicEncryptedModel.findById simpleTestDoc3._id, (err, doc) ->
assert.equal err, null
assert.ok doc
assert.equal doc.authenticateSync.callCount, 1
assert.equal doc.decryptSync.callCount, 1
assert doc.authenticateSync.calledBefore doc.decryptSync, 'authenticated before decrypted'
done()
return
it 'if all authenticated fields selected, should not throw an error', (done) ->
BasicEncryptedModel.findById(simpleTestDoc3._id).select('_ct _ac').exec (err, doc) ->
assert.equal err, null
assert.propertyVal doc, 'text', 'Unencrypted text'
assert.propertyVal doc, 'bool', true
assert.propertyVal doc, 'num', 42
done()
return
it 'if only some authenticated fields selected, should throw an error', (done) ->
BasicEncryptedModel.findById(simpleTestDoc3._id).select('_ct').exec (err, doc) ->
assert.ok err
BasicEncryptedModel.findById(simpleTestDoc3._id).select('_ac').exec (err, doc) ->
assert.ok err
done()
return
describe 'EncryptedModel.find() lean option', ->
simpleTestDoc4 = null
before (done) ->
simpleTestDoc4 = new BasicEncryptedModel
text: 'Unencrypted text'
bool: true
num: 42
date: new Date '2014-05-19T16:39:07.536Z'
id2: '5303e65d34e1e80d7a7ce212'
arr: ['alpha', 'bravo']
mix: { str: 'A string', bool: false }
buf: new Buffer 'abcdefg'
simpleTestDoc4.save (err) ->
assert.equal err, null
done()
after (done) ->
simpleTestDoc4.remove (err) ->
assert.equal err, null
done()
it 'should have encrypted fields undefined on saved document', (done) ->
BasicEncryptedModel.findById(simpleTestDoc4._id).lean().exec (err, doc) ->
assert.equal doc.text, undefined
assert.equal doc.bool, undefined
assert.equal doc.num, undefined
assert.equal doc.date, undefined
assert.equal doc.id2, undefined
assert.equal doc.arr, undefined
assert.equal doc.mix, undefined
assert.equal doc.buf, undefined
done()
it 'should have a field _ct containing a mongoose Buffer object which appears encrypted', (done) ->
BasicEncryptedModel.findById(simpleTestDoc4._id).lean().exec (err, doc) ->
assert.isObject doc._ct
assert.property doc._ct, 'buffer'
assert.instanceOf doc._ct.buffer, Buffer
assert.isString doc._ct.toString(), 'ciphertext can be converted to a string'
assert.throw -> JSON.parse doc._ct.toString(), 'ciphertext is not parsable json'
done()
describe 'document.encrypt()', ->
simpleTestDoc5 = null
beforeEach (done) ->
simpleTestDoc5 = new BasicEncryptedModel
text: 'Unencrypted text'
bool: true
num: 42
date: new Date '2014-05-19T16:39:07.536Z'
id2: '5303e65d34e1e80d7a7ce212'
arr: ['alpha', 'bravo']
mix: { str: 'A string', bool: false }
buf: new Buffer 'abcdefg'
idx: 'Indexed'
simpleTestDoc5.encrypt (err) ->
assert.equal err, null
done()
it 'should have encrypted fields undefined', (done) ->
assert.equal simpleTestDoc5.text, undefined
assert.equal simpleTestDoc5.bool, undefined
assert.equal simpleTestDoc5.num, undefined
assert.equal simpleTestDoc5.date, undefined
assert.equal simpleTestDoc5.id2, undefined
assert.equal simpleTestDoc5.arr, undefined
assert.equal simpleTestDoc5.mix, undefined
assert.equal simpleTestDoc5.buf, undefined
done()
it 'should not encrypt indexed fields by default', (done) ->
assert.propertyVal simpleTestDoc5, 'idx', 'Indexed'
done()
it 'should have a field _ct containing a mongoose Buffer object which appears encrypted', (done) ->
assert.isObject simpleTestDoc5._ct
assert.property simpleTestDoc5.toObject()._ct, 'buffer'
assert.instanceOf simpleTestDoc5.toObject()._ct.buffer, Buffer
assert.isString simpleTestDoc5.toObject()._ct.toString(), 'ciphertext can be converted to a string'
assert.throw -> JSON.parse simpleTestDoc5.toObject()._ct.toString(), 'ciphertext is not parsable json'
done()
it 'should have non-ascii characters in ciphertext as a result of encryption even if all input is ascii', (done) ->
allAsciiDoc = new BasicEncryptedModel
text: 'Unencrypted text'
allAsciiDoc.encrypt (err) ->
assert.equal err, null
assert.notMatch allAsciiDoc.toObject()._ct.toString(), /^[\x00-\x7F]*$/
done()
it 'should pass an error when called on a document which is already encrypted', (done) ->
simpleTestDoc5.encrypt (err) ->
assert.ok err
done()
describe 'document.decrypt()', ->
beforeEach (done) ->
@encryptedSimpleTestDoc = new BasicEncryptedModel
_id: '584b1e7de752fcf3be8cd086'
idx: 'Indexed'
_ct: new Buffer("610bbddbf35455e9a4fcf2428bb6cd68f39fdaece7e851cb213b1be81b10559d1af6d7c205752d2a6620100871d0e" +
"95d3609d4ee81795dcc7ef5130b80f117eb12f557a08d4837609f37d24af8d64f8b5072747e1a9e4585fc07d76720" +
"5e8289235019f818ad7ed9dbb90844d6a42189ab5a8cdc303e60256dbc5daa76386422de8cf1af40ea1c07b7720e5" +
"3787515a959537f4dffc663c69d29e614621bc7a345ab31f9b8931277d7577962e9558119b9d5d7db0a3b1c298afd" +
"eabe11581684b62ffaa58a9877d7ceeeb2ea158df3db7881bfedb40ed4d4de7a6465cf1e1148582714279bd0e0cbf" +
"f145e0bddc1ff3f5e2e6cc8b39f9640e433e4c4140e2095e6", 'hex');
@simpleTestDoc6 = new BasicEncryptedModel
text: 'Unencrypted text'
bool: true
num: 42
date: new Date '2014-05-19T16:39:07.536Z'
id2: '5303e65d34e1e80d7a7ce212'
arr: ['alpha', 'bravo']
mix: { str: 'A string', bool: false }
buf: new Buffer 'abcdefg'
idx: 'Indexed'
@simpleTestDoc6.encrypt (err) ->
assert.equal err, null
done()
it 'should return an unencrypted version', (done) ->
@encryptedSimpleTestDoc.decrypt (err) =>
assert.equal err, null
assert.propertyVal @encryptedSimpleTestDoc, 'text', 'Unencrypted text'
assert.propertyVal @encryptedSimpleTestDoc, 'bool', true
assert.propertyVal @encryptedSimpleTestDoc, 'num', 42
assert.property @encryptedSimpleTestDoc, 'date'
assert.equal @encryptedSimpleTestDoc.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
assert.equal @encryptedSimpleTestDoc.id2.toString(), '5303e65d34e1e80d7a7ce212'
assert.lengthOf @encryptedSimpleTestDoc.arr, 2
assert.equal @encryptedSimpleTestDoc.arr[0], 'alpha'
assert.equal @encryptedSimpleTestDoc.arr[1], 'bravo'
assert.property @encryptedSimpleTestDoc, 'mix'
assert.deepEqual @encryptedSimpleTestDoc.mix, { str: 'A string', bool: false }
assert.property @encryptedSimpleTestDoc, 'buf'
assert.equal @encryptedSimpleTestDoc.buf.toString(), 'abcdefg'
assert.propertyVal @encryptedSimpleTestDoc, 'idx', 'Indexed'
assert.property @encryptedSimpleTestDoc, '_id'
assert.notProperty @encryptedSimpleTestDoc, '_ct'
done()
it 'should return an unencrypted version when run after #encrypt', (done) ->
@simpleTestDoc6.decrypt (err) =>
assert.equal err, null
assert.propertyVal @simpleTestDoc6, 'text', 'Unencrypted text'
assert.propertyVal @simpleTestDoc6, 'bool', true
assert.propertyVal @simpleTestDoc6, 'num', 42
assert.property @simpleTestDoc6, 'date'
assert.equal @simpleTestDoc6.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
assert.equal @simpleTestDoc6.id2.toString(), '5303e65d34e1e80d7a7ce212'
assert.lengthOf @simpleTestDoc6.arr, 2
assert.equal @simpleTestDoc6.arr[0], 'alpha'
assert.equal @simpleTestDoc6.arr[1], 'bravo'
assert.property @simpleTestDoc6, 'mix'
assert.deepEqual @simpleTestDoc6.mix, { str: 'A string', bool: false }
assert.property @simpleTestDoc6, 'buf'
assert.equal @simpleTestDoc6.buf.toString(), 'abcdefg'
assert.propertyVal @simpleTestDoc6, 'idx', 'Indexed'
assert.property @simpleTestDoc6, '_id'
assert.notProperty @simpleTestDoc6, '_ct'
done()
it 'should return an unencrypted version even if document already decrypted', (done) ->
@encryptedSimpleTestDoc.decrypt (err) =>
assert.equal err, null
@encryptedSimpleTestDoc.decrypt (err) =>
assert.equal err, null
assert.propertyVal @encryptedSimpleTestDoc, 'text', 'Unencrypted text'
assert.propertyVal @encryptedSimpleTestDoc, 'bool', true
assert.propertyVal @encryptedSimpleTestDoc, 'num', 42
assert.property @encryptedSimpleTestDoc, 'date'
assert.equal @encryptedSimpleTestDoc.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
assert.equal @encryptedSimpleTestDoc.id2.toString(), '5303e65d34e1e80d7a7ce212'
assert.lengthOf @encryptedSimpleTestDoc.arr, 2
assert.equal @encryptedSimpleTestDoc.arr[0], 'alpha'
assert.equal @encryptedSimpleTestDoc.arr[1], 'bravo'
assert.property @encryptedSimpleTestDoc, 'mix'
assert.deepEqual @encryptedSimpleTestDoc.mix, { str: 'A string', bool: false }
assert.property @encryptedSimpleTestDoc, 'buf'
assert.equal @encryptedSimpleTestDoc.buf.toString(), 'abcdefg'
assert.propertyVal @encryptedSimpleTestDoc, 'idx', 'Indexed'
assert.property @encryptedSimpleTestDoc, '_id'
assert.notProperty @encryptedSimpleTestDoc, '_ct'
done()
describe 'document.decryptSync()', ->
simpleTestDoc7 = null
before (done) ->
simpleTestDoc7 = new BasicEncryptedModel
text: 'Unencrypted text'
bool: true
num: 42
date: new Date '2014-05-19T16:39:07.536Z'
id2: '5303e65d34e1e80d7a7ce212'
arr: ['alpha', 'bravo']
mix: { str: 'A string', bool: false }
buf: new Buffer 'abcdefg'
idx: 'Indexed'
simpleTestDoc7.encrypt (err) ->
assert.equal err, null
done()
after (done) ->
simpleTestDoc7.remove (err) ->
assert.equal err, null
done()
it 'should return an unencrypted version', (done) ->
simpleTestDoc7.decryptSync()
assert.propertyVal simpleTestDoc7, 'text', 'Unencrypted text'
assert.propertyVal simpleTestDoc7, 'bool', true
assert.propertyVal simpleTestDoc7, 'num', 42
assert.property simpleTestDoc7, 'date'
assert.equal simpleTestDoc7.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
assert.equal simpleTestDoc7.id2.toString(), '5303e65d34e1e80d7a7ce212'
assert.lengthOf simpleTestDoc7.arr, 2
assert.equal simpleTestDoc7.arr[0], 'alpha'
assert.equal simpleTestDoc7.arr[1], 'bravo'
assert.property simpleTestDoc7, 'mix'
assert.deepEqual simpleTestDoc7.mix, { str: 'A string', bool: false }
assert.property simpleTestDoc7, 'buf'
assert.equal simpleTestDoc7.buf.toString(), 'abcdefg'
assert.propertyVal simpleTestDoc7, 'idx', 'Indexed'
assert.property simpleTestDoc7, '_id'
assert.notProperty simpleTestDoc7, '_ct'
done()
it 'should return an unencrypted version even if document already decrypted', (done) ->
simpleTestDoc7.decryptSync()
assert.propertyVal simpleTestDoc7, 'text', 'Unencrypted text'
assert.propertyVal simpleTestDoc7, 'bool', true
assert.propertyVal simpleTestDoc7, 'num', 42
assert.property simpleTestDoc7, 'date'
assert.equal simpleTestDoc7.date.toString(), new Date("2014-05-19T16:39:07.536Z").toString()
assert.equal simpleTestDoc7.id2.toString(), '5303e65d34e1e80d7a7ce212'
assert.lengthOf simpleTestDoc7.arr, 2
assert.equal simpleTestDoc7.arr[0], 'alpha'
assert.equal simpleTestDoc7.arr[1], 'bravo'
assert.property simpleTestDoc7, 'mix'
assert.deepEqual simpleTestDoc7.mix, { str: 'A string', bool: false }
assert.property simpleTestDoc7, 'buf'
assert.equal simpleTestDoc7.buf.toString(), 'abcdefg'
assert.propertyVal simpleTestDoc7, 'idx', 'Indexed'
assert.property simpleTestDoc7, '_id'
assert.notProperty simpleTestDoc7, '_ct'
done()
describe '"encryptedFields" option', ->
it 'should encrypt fields iff they are in the passed in "encryptedFields" array even if those fields are indexed', (done) ->
EncryptedFieldsModelSchema = mongoose.Schema
text: type: String, index: true
bool: type: Boolean
num: type: Number
EncryptedFieldsModelSchema.plugin encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'EncryptedFields', encryptedFields: ['text', 'bool']
FieldsEncryptedModel = mongoose.model 'Fields', EncryptedFieldsModelSchema
fieldsEncryptedDoc = new FieldsEncryptedModel
text: 'Unencrypted text'
bool: false
num: 43
fieldsEncryptedDoc.encrypt (err) ->
assert.equal err, null
assert.equal fieldsEncryptedDoc.text, undefined
assert.equal fieldsEncryptedDoc.bool, undefined
assert.propertyVal fieldsEncryptedDoc, 'num', 43
fieldsEncryptedDoc.decrypt (err) ->
assert.equal err, null
assert.equal fieldsEncryptedDoc.text, 'Unencrypted text'
assert.equal fieldsEncryptedDoc.bool, false
assert.propertyVal fieldsEncryptedDoc, 'num', 43
done()
it 'should override other options', (done) ->
EncryptedFieldsOverrideModelSchema = mongoose.Schema
text: type: String, index: true
bool: type: Boolean
num: type: Number
EncryptedFieldsOverrideModelSchema.plugin encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'EncryptedFieldsOverride', encryptedFields: ['text', 'bool'], excludeFromEncryption: ['bool']
FieldsOverrideEncryptedModel = mongoose.model 'FieldsOverride', EncryptedFieldsOverrideModelSchema
fieldsEncryptedDoc = new FieldsOverrideEncryptedModel
text: 'Unencrypted text'
bool: false
num: 43
fieldsEncryptedDoc.encrypt (err) ->
assert.equal err, null
assert.equal fieldsEncryptedDoc.text, undefined
assert.equal fieldsEncryptedDoc.bool, undefined
assert.propertyVal fieldsEncryptedDoc, 'num', 43
fieldsEncryptedDoc.decrypt (err) ->
assert.equal err, null
assert.equal fieldsEncryptedDoc.text, 'Unencrypted text'
assert.equal fieldsEncryptedDoc.bool, false
assert.propertyVal fieldsEncryptedDoc, 'num', 43
done()
describe '"excludeFromEncryption" option', ->
it 'should encrypt all non-indexed fields except those in the passed-in "excludeFromEncryption" array', (done) ->
ExcludeEncryptedModelSchema = mongoose.Schema
text: type: String
bool: type: Boolean
num: type: Number
idx: type: String, index: true
ExcludeEncryptedModelSchema.plugin encrypt, encryptionKey: encryptionKey, signingKey: signingKey, collectionId: 'ExcludeEncrypted', excludeFromEncryption: ['num']
ExcludeEncryptedModel = mongoose.model 'Exclude', ExcludeEncryptedModelSchema
excludeEncryptedDoc = new ExcludeEncryptedModel
text: 'Unencrypted text'
bool: false
num: 43
idx: 'Indexed'
excludeEncryptedDoc.encrypt (err) ->
assert.equal err, null
assert.equal excludeEncryptedDoc.text, undefined
assert.equal excludeEncryptedDoc.bool, undefined
assert.propertyVal excludeEncryptedDoc, 'num', 43
assert.propertyVal excludeEncryptedDoc, 'idx', 'Indexed'
excludeEncryptedDoc.decrypt (err) ->
assert.equal err, null
assert.equal excludeEncryptedDoc.text, 'Unencrypted text'
assert.equal excludeEncryptedDoc.bool, false
assert.propertyVal excludeEncryptedDoc, 'num', 43
assert.propertyVal excludeEncryptedDoc, 'idx', 'Indexed'
done()
describe '"decryptPostSave" option', ->
before ->
HighPerformanceModelSchema = mongoose.Schema
text: type: String
HighPerformanceModelSchema.plugin encrypt, secret: secret, decryptPostSave: false
@HighPerformanceModel = mongoose.model 'HighPerformance', HighPerformanceModelSchema
beforeEach (done) ->
@doc = new @HighPerformanceModel
text: 'Unencrypted text'
done()
afterEach (done) ->
@HighPerformanceModel.remove (err) ->
assert.equal err, null
done()
return
it 'saves encrypted fields correctly', (done) ->
@doc.save (err) =>
assert.equal err, null
@HighPerformanceModel.find
_id: @doc._id
_ct: $exists: true
text: $exists: false
, (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
assert.propertyVal docs[0], 'text', 'Unencrypted text'
done()
it 'returns encrypted data after save', (done) ->
@doc.save (err, savedDoc) ->
assert.equal err, null
assert.property savedDoc, '_ct', 'Document remains encrypted after save'
assert.notProperty savedDoc, 'text'
savedDoc.decrypt (err) ->
assert.equal err, null
assert.notProperty savedDoc, '_ct'
assert.propertyVal savedDoc, 'text', 'Unencrypted text', 'Document can still be unencrypted'
done()
describe 'Array EmbeddedDocument', ->
describe 'when only child is encrypted', ->
describe 'and parent does not have encryptedChildren plugin', ->
before ->
ChildModelSchema = mongoose.Schema
text: type: String
ChildModelSchema.plugin encrypt, encryptionKey: encryptionKey, signingKey: signingKey
ParentModelSchema = mongoose.Schema
text: type: String
children: [ChildModelSchema]
@ParentModel = mongoose.model 'Parent', ParentModelSchema
@ChildModel = mongoose.model 'Child', ChildModelSchema
beforeEach (done) ->
@parentDoc = new @ParentModel
text: 'Unencrypted text'
childDoc = new @ChildModel
text: 'Child unencrypted text'
childDoc2 = new @ChildModel
text: 'Second unencrypted text'
@parentDoc.children.addToSet childDoc
@parentDoc.children.addToSet childDoc2
@parentDoc.save done
after (done) ->
@parentDoc.remove done
describe 'document.save()', ->
it 'should not have decrypted fields', ->
assert.equal @parentDoc.children[0].text, undefined
it 'should persist children as encrypted', (done) ->
@ParentModel.find
_id: @parentDoc._id
'children._ct': $exists: true
'children.text': $exists: false
, (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
assert.propertyVal docs[0].children[0], 'text', 'Child unencrypted text'
done()
return
describe 'document.find()', ->
it 'when parent doc found, should pass an unencrypted version of the embedded document to the callback', (done) ->
@ParentModel.findById @parentDoc._id, (err, doc) ->
assert.equal err, null
assert.propertyVal doc, 'text', 'Unencrypted text'
assert.isArray doc.children
assert.isObject doc.children[0]
assert.property doc.children[0], 'text', 'Child unencrypted text'
assert.property doc.children[0], '_id'
assert.notProperty doc.children[0], '_ct'
done()
return
describe 'tampering with child documents by swapping their ciphertext', ->
it 'should not cause an error because embedded documents are not self-authenticated', (done) ->
@ParentModel.findById(@parentDoc._id).lean().exec (err, doc) =>
assert.equal err, null
assert.isArray doc.children
childDoc1CipherText = doc.children[0]._ct
childDoc2CipherText = doc.children[1]._ct
@ParentModel.update { _id: @parentDoc._id }
, { $set : {'children.0._ct': childDoc2CipherText, 'children.1._ct': childDoc1CipherText } }
, (err) =>
assert.equal err, null
@ParentModel.findById @parentDoc._id, (err, doc) ->
assert.equal err, null
assert.isArray doc.children
assert.property doc.children[0], 'text', 'Second unencrypted text', 'Ciphertext was swapped'
assert.property doc.children[1], 'text', 'Child unencrypted text', 'Ciphertext was swapped'
done()
return
describe 'and parent has encryptedChildren plugin', ->
before ->
ChildModelSchema = mongoose.Schema
text: type: String
ChildModelSchema.plugin encrypt, encryptionKey: encryptionKey, signingKey: signingKey
ParentModelSchema = mongoose.Schema
text: type: String
children: [ChildModelSchema]
ParentModelSchema.plugin encrypt.encryptedChildren
@ParentModel = mongoose.model 'ParentEC', ParentModelSchema
@ChildModel = mongoose.model 'ChildOfECP', ChildModelSchema
beforeEach (done) ->
@parentDoc = new @ParentModel
text: 'Unencrypted text'
childDoc = new @ChildModel
text: 'Child unencrypted text'
childDoc2 = new @ChildModel
text: 'Second unencrypted text'
@parentDoc.children.addToSet childDoc
@parentDoc.children.addToSet childDoc2
@parentDoc.save done
after (done) ->
@parentDoc.remove done
describe 'document.save()', ->
it 'should have decrypted fields', ->
assert.equal @parentDoc.children[0].text, 'Child unencrypted text'
it 'should persist children as encrypted', (done) ->
@ParentModel.find
_id: @parentDoc._id
'children._ct': $exists: true
'children.text': $exists: false
, (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
assert.propertyVal docs[0].children[0], 'text', 'Child unencrypted text'
done()
return
describe 'document.find()', ->
it 'when parent doc found, should pass an unencrypted version of the embedded document to the callback', (done) ->
@ParentModel.findById @parentDoc._id, (err, doc) ->
assert.equal err, null
assert.propertyVal doc, 'text', 'Unencrypted text'
assert.isArray doc.children
assert.isObject doc.children[0]
assert.property doc.children[0], 'text', 'Child unencrypted text'
assert.property doc.children[0], '_id'
assert.notProperty doc.children[0], '_ct'
done()
return
describe 'tampering with child documents by swapping their ciphertext', ->
it 'should not cause an error because embedded documents are not self-authenticated', (done) ->
@ParentModel.findById(@parentDoc._id).lean().exec (err, doc) =>
assert.equal err, null
assert.isArray doc.children
childDoc1CipherText = doc.children[0]._ct
childDoc2CipherText = doc.children[1]._ct
@ParentModel.update { _id: @parentDoc._id }
, { $set : {'children.0._ct': childDoc2CipherText, 'children.1._ct': childDoc1CipherText } }
, (err) =>
assert.equal err, null
@ParentModel.findById @parentDoc._id, (err, doc) ->
assert.equal err, null
assert.isArray doc.children
assert.property doc.children[0], 'text', 'Second unencrypted text', 'Ciphertext was swapped'
assert.property doc.children[1], 'text', 'Child unencrypted text', 'Ciphertext was swapped'
done()
describe 'when child is encrypted and authenticated', ->
before ->
ChildModelSchema = mongoose.Schema
text: type: String
ChildModelSchema.plugin encrypt,
encryptionKey: encryptionKey
signingKey: signingKey
ParentModelSchema = mongoose.Schema
text: type: String
children: [ChildModelSchema]
ParentModelSchema.plugin encrypt,
encryptionKey: encryptionKey
signingKey: signingKey
encryptedFields: []
additionalAuthenticatedFields: ['children']
@ParentModel = mongoose.model 'ParentWithAuth', ParentModelSchema
@ChildModel = mongoose.model 'ChildWithAuth', ChildModelSchema
beforeEach (done) ->
@parentDoc = new @ParentModel
text: 'Unencrypted text'
childDoc = new @ChildModel
text: 'Child unencrypted text'
childDoc2 = new @ChildModel
text: 'Second unencrypted text'
@parentDoc.children.addToSet childDoc
@parentDoc.children.addToSet childDoc2
@parentDoc.save done
after (done) ->
@parentDoc.remove done
it 'should persist children as encrypted after removing a child', (done) ->
@ParentModel.findById @parentDoc._id, (err, doc) =>
return done(err) if err
assert.ok doc, 'should have found doc with encrypted children'
doc.children.id(doc.children[1]._id).remove()
doc.save (err) =>
return done(err) if err
@ParentModel.find
_id: @parentDoc._id
'children._ct': $exists: true
'children.text': $exists: false
, (err, docs) ->
return done(err) if err
assert.ok doc, 'should have found doc with encrypted children'
assert.equal doc.children.length, 1
done()
return
it 'should persist children as encrypted after adding a child', (done) ->
@ParentModel.findById @parentDoc._id, (err, doc) =>
return done(err) if err
assert.ok doc, 'should have found doc with encrypted children'
doc.children.addToSet text: 'new child'
doc.save (err) =>
return done(err) if err
@ParentModel.findById @parentDoc._id
.exec (err, doc) =>
return done(err) if err
assert.ok doc, 'should have found doc with encrypted children'
assert.equal doc.children.length, 3
done()
return
describe 'when child and parent are encrypted', ->
before ->
ChildModelSchema = mongoose.Schema
text: type: String
ChildModelSchema.plugin encrypt, encryptionKey: encryptionKey, signingKey: signingKey
ParentModelSchema = mongoose.Schema
text: type: String
children: [ChildModelSchema]
ParentModelSchema.plugin encrypt,
encryptionKey: encryptionKey
signingKey: signingKey
encryptedFields: ['text']
additionalAuthenticatedFields: ['children']
@ParentModel = mongoose.model 'ParentBoth', ParentModelSchema
@ChildModel = mongoose.model 'ChildBoth', ChildModelSchema
beforeEach (done) ->
@parentDoc = new @ParentModel
text: 'Unencrypted text'
childDoc = new @ChildModel
text: 'Child unencrypted text'
childDoc2 = new @ChildModel
text: 'Second unencrypted text'
@parentDoc.children.addToSet childDoc
@parentDoc.children.addToSet childDoc2
@parentDoc.save done
after (done) ->
@parentDoc.remove done
describe 'document.save()', ->
it 'should have decrypted fields on parent', ->
assert.equal @parentDoc.text, 'Unencrypted text'
it 'should have decrypted fields', ->
assert.equal @parentDoc.children[0].text, 'Child unencrypted text'
it 'should persist children as encrypted', (done) ->
@ParentModel.find
_id: @parentDoc._id
'children._ct': $exists: true
'children.text': $exists: false
, (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
assert.propertyVal docs[0].children[0], 'text', 'Child unencrypted text'
done()
return
describe 'document.find()', ->
it 'when parent doc found, should pass an unencrypted version of the embedded document to the callback', (done) ->
@ParentModel.findById @parentDoc._id
, (err, doc) ->
assert.equal err, null
assert.propertyVal doc, 'text', 'Unencrypted text'
assert.isArray doc.children
assert.isObject doc.children[0]
assert.property doc.children[0], 'text', 'Child unencrypted text'
assert.property doc.children[0], '_id'
assert.notProperty doc.children[0], '_ct'
done()
return
describe 'when child field is in additionalAuthenticatedFields on parent and child documents are tampered with by swapping their ciphertext', ->
it 'should pass an error', (done) ->
@ParentModel.findById(@parentDoc._id).lean().exec (err, doc) =>
assert.equal err, null
assert.isArray doc.children
childDoc1CipherText = doc.children[0]._ct
childDoc2CipherText = doc.children[1]._ct
@ParentModel.update { _id: @parentDoc._id }
, { $set : {'children.0._ct': childDoc2CipherText, 'children.1._ct': childDoc1CipherText } }
, (err) =>
assert.equal err, null
@ParentModel.findById @parentDoc._id, (err, doc) ->
assert.ok err, 'There was an error'
assert.propertyVal err, 'message', 'Authentication failed'
done()
describe 'when entire parent is encrypted', ->
before ->
ParentModelSchema = mongoose.Schema
text: type: String
children: [text: type: String]
ParentModelSchema.plugin encrypt, encryptionKey: encryptionKey, signingKey: signingKey
@ParentModel = mongoose.model 'ParentEntire', ParentModelSchema
beforeEach (done) ->
@parentDoc = new @ParentModel
text: 'Unencrypted text'
children: [text: 'Child unencrypted text']
@parentDoc.save done
after (done) ->
@parentDoc.remove done
describe 'document.save()', ->
it 'should have decrypted fields in document passed to call back', ->
assert.equal @parentDoc.text, 'Unencrypted text'
assert.equal @parentDoc.children[0].text, 'Child unencrypted text'
it 'should persist the entire document as encrypted', (done) ->
@ParentModel.find
_id: @parentDoc._id
'_ct': $exists: true
'children': $exists: false
'children.text': $exists: false
, (err, docs) ->
assert.equal err, null
assert.lengthOf docs, 1
assert.propertyVal docs[0], 'text', 'Unencrypted text'
assert.propertyVal docs[0].children[0], 'text', 'Child unencrypted text'
done()
return
describe 'document.find()', ->
it 'when parent doc found, should pass an unencrypted version of the embedded document to the callback', (done) ->
@ParentModel.findById @parentDoc._id, (err, doc) ->
assert.equal err, null
assert.propertyVal doc, 'text', 'Unencrypted text'
assert.isArray doc.children
assert.isObject doc.children[0]
assert.property doc.children[0], 'text', 'Child unencrypted text'
assert.property doc.children[0], '_id'
assert.notProperty doc.children[0], '_ct'
done()
return
describe 'Encrypted embedded document when parent has validation error and doesnt have encryptedChildren plugin', ->
before ->
ChildModelSchema = mongoose.Schema
text: type: String
ChildModelSchema.plugin encrypt,
encryptionKey: encryptionKey, signingKey: signingKey
encryptedFields: ['text']
ParentModelSchema = mongoose.Schema
text: type: String
children: [ChildModelSchema]
ParentModelSchema.pre 'validate', (next) ->
@invalidate 'text', 'invalid', this.text
next()
@ParentModel2 = mongoose.model 'ParentWithoutPlugin', ParentModelSchema
@ChildModel2 = mongoose.model 'ChildAgain', ChildModelSchema
it 'should return unencrypted embedded documents', (done) ->
doc = new @ParentModel2
text: 'here it is'
children: [{text: 'Child unencrypted text'}]
doc.save (err) ->
assert.ok err, 'There should be a validation error'
assert.propertyVal doc, 'text', 'here it is'
assert.isArray doc.children
assert.property doc.children[0], '_id'
assert.notProperty doc.children[0], '_ct'
assert.property doc.children[0], 'text', 'Child unencrypted text'
done()
describe 'Encrypted embedded document when parent has validation error and has encryptedChildren plugin', ->
before ->
ChildModelSchema = mongoose.Schema
text: type: String
ChildModelSchema.plugin encrypt,
encryptionKey: encryptionKey, signingKey: signingKey
encryptedFields: ['text']
@ParentModelSchema = mongoose.Schema
text: type: String
children: [ChildModelSchema]
@ParentModelSchema.pre 'validate', (next) ->
@invalidate 'text',