'use strict'; const MongoClient = require('./mongo_client'); const BSON = require('./core/connection/utils').retrieveBSON(); const MongoError = require('./core/error').MongoError; let mongodbClientEncryption = undefined; try { // Ensure you always wrap an optional require in the try block NODE-3199 mongodbClientEncryption = require('mongodb-client-encryption'); } catch (err) { throw new MongoError( 'Auto-encryption requested, but the module is not installed. ' + 'Please add `mongodb-client-encryption` as a dependency of your project' ); } if ( mongodbClientEncryption === undefined || typeof mongodbClientEncryption.extension !== 'function' ) { throw new MongoError( 'loaded version of `mongodb-client-encryption` does not have property `extension`. ' + 'Please make sure you are loading the correct version of `mongodb-client-encryption`' ); } const AutoEncrypter = mongodbClientEncryption.extension(require('../index')).AutoEncrypter; const kInternalClient = Symbol('internalClient'); class Encrypter { /** * @param {MongoClient} client * @param {{autoEncryption: import('./mongo_client').AutoEncryptionOptions, bson: object}} options */ constructor(client, options) { this.bypassAutoEncryption = !!options.autoEncryption.bypassAutoEncryption; this.needsConnecting = false; if (options.maxPoolSize === 0 && options.autoEncryption.keyVaultClient == null) { options.autoEncryption.keyVaultClient = client; } else if (options.autoEncryption.keyVaultClient == null) { options.autoEncryption.keyVaultClient = this.getInternalClient(client); } if (this.bypassAutoEncryption) { options.autoEncryption.metadataClient = undefined; } else if (options.maxPoolSize === 0) { options.autoEncryption.metadataClient = client; } else { options.autoEncryption.metadataClient = this.getInternalClient(client); } options.autoEncryption.bson = Encrypter.makeBSON(options); this.autoEncrypter = new AutoEncrypter(client, options.autoEncryption); } getInternalClient(client) { if (!this[kInternalClient]) { const clonedOptions = {}; for (const key of Object.keys(client.s.options)) { if ( ['autoEncryption', 'minPoolSize', 'servers', 'caseTranslate', 'dbName'].indexOf(key) !== -1 ) continue; clonedOptions[key] = client.s.options[key]; } clonedOptions.minPoolSize = 0; const allEvents = [ // APM 'commandStarted', 'commandSucceeded', 'commandFailed', // SDAM 'serverOpening', 'serverClosed', 'serverDescriptionChanged', 'serverHeartbeatStarted', 'serverHeartbeatSucceeded', 'serverHeartbeatFailed', 'topologyOpening', 'topologyClosed', 'topologyDescriptionChanged', // Legacy 'joined', 'left', 'ping', 'ha', // CMAP 'connectionPoolCreated', 'connectionPoolClosed', 'connectionCreated', 'connectionReady', 'connectionClosed', 'connectionCheckOutStarted', 'connectionCheckOutFailed', 'connectionCheckedOut', 'connectionCheckedIn', 'connectionPoolCleared' ]; this[kInternalClient] = new MongoClient(client.s.url, clonedOptions); for (const eventName of allEvents) { for (const listener of client.listeners(eventName)) { this[kInternalClient].on(eventName, listener); } } client.on('newListener', (eventName, listener) => { this[kInternalClient].on(eventName, listener); }); this.needsConnecting = true; } return this[kInternalClient]; } connectInternalClient(callback) { if (this.needsConnecting) { this.needsConnecting = false; return this[kInternalClient].connect(callback); } return callback(); } close(client, force, callback) { this.autoEncrypter.teardown(e => { if (this[kInternalClient] && client !== this[kInternalClient]) { return this[kInternalClient].close(force, callback); } callback(e); }); } static makeBSON(options) { return ( (options || {}).bson || new BSON([ BSON.Binary, BSON.Code, BSON.DBRef, BSON.Decimal128, BSON.Double, BSON.Int32, BSON.Long, BSON.Map, BSON.MaxKey, BSON.MinKey, BSON.ObjectId, BSON.BSONRegExp, BSON.Symbol, BSON.Timestamp ]) ); } } module.exports = { Encrypter };