diff --git a/src/client-side-encryption/auto_encrypter.ts b/src/client-side-encryption/auto_encrypter.ts index 53426609f90..54e4835686a 100644 --- a/src/client-side-encryption/auto_encrypter.ts +++ b/src/client-side-encryption/auto_encrypter.ts @@ -335,10 +335,7 @@ export class AutoEncrypter { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: TS complains as this always returns true on versions where it is present. if (net.getDefaultAutoSelectFamily) { - // AutoEncrypter is made inside of MongoClient constructor while options are being parsed, - // we do not have access to the options that are in progress. - // TODO(NODE-6449): AutoEncrypter does not use client options for autoSelectFamily - Object.assign(clientOptions, autoSelectSocketOptions(this._client.s?.options ?? {})); + Object.assign(clientOptions, autoSelectSocketOptions(this._client.s.options ?? {})); } this._mongocryptdClient = new MongoClient(this._mongocryptdManager.uri, clientOptions); diff --git a/src/connection_string.ts b/src/connection_string.ts index c56cf9bdedd..40e883de4fa 100644 --- a/src/connection_string.ts +++ b/src/connection_string.ts @@ -236,14 +236,8 @@ class CaseInsensitiveMap extends Map { export function parseOptions( uri: string, - mongoClient: MongoClient | MongoClientOptions | undefined = undefined, options: MongoClientOptions = {} ): MongoOptions { - if (mongoClient != null && !(mongoClient instanceof MongoClient)) { - options = mongoClient; - mongoClient = undefined; - } - // validate BSONOptions if (options.useBigInt64 && typeof options.promoteLongs === 'boolean' && !options.promoteLongs) { throw new MongoAPIError('Must request either bigint or Long for int64 deserialization'); @@ -452,10 +446,9 @@ export function parseOptions( validateLoadBalancedOptions(hosts, mongoOptions, isSRV); - if (mongoClient && mongoOptions.autoEncryption) { + if (mongoOptions.autoEncryption) { Encrypter.checkForMongoCrypt(); - mongoOptions.encrypter = new Encrypter(mongoClient, uri, options); - mongoOptions.autoEncrypter = mongoOptions.encrypter.autoEncrypter; + mongoOptions.useAutoEncryption = true; } // Potential SRV Overrides and SRV connection string validations diff --git a/src/mongo_client.ts b/src/mongo_client.ts index d32ad8554af..9204082353f 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -5,7 +5,7 @@ import type { ConnectionOptions as TLSConnectionOptions, TLSSocketOptions } from import { TopologyType } from '.'; import { type BSONSerializeOptions, type Document, resolveBSONOptions } from './bson'; import { ChangeStream, type ChangeStreamDocument, type ChangeStreamOptions } from './change_stream'; -import type { AutoEncrypter, AutoEncryptionOptions } from './client-side-encryption/auto_encrypter'; +import { AutoEncrypter, AutoEncryptionOptions } from './client-side-encryption/auto_encrypter'; import { type AuthMechanismProperties, DEFAULT_ALLOWED_HOSTS, @@ -25,7 +25,7 @@ import { parseOptions, resolveSRVRecord } from './connection_string'; import { MONGO_CLIENT_EVENTS } from './constants'; import { type AbstractCursor } from './cursor/abstract_cursor'; import { Db, type DbOptions } from './db'; -import type { Encrypter } from './encrypter'; +import { Encrypter } from './encrypter'; import { MongoInvalidArgumentError } from './error'; import { MongoClientAuthProviders } from './mongo_client_auth_providers'; import { @@ -419,6 +419,8 @@ export class MongoClient extends TypedEventEmitter implements private connectionLock?: Promise; /** @internal */ private closeLock?: Promise; + /** @internal */ + private encrypter?: Encrypter; /** * The consolidate, parsed, transformed and merged options. @@ -437,7 +439,7 @@ export class MongoClient extends TypedEventEmitter implements super(); this.on('error', noop); - this.options = parseOptions(url, this, options); + this.options = parseOptions(url, options); this.appendMetadata(this.options.driverInfo); @@ -540,7 +542,11 @@ export class MongoClient extends TypedEventEmitter implements /** @internal */ get autoEncrypter(): AutoEncrypter | undefined { - return this.options.autoEncrypter; + if (this.options.autoEncryption && !this.encrypter) { + // Create on first access + this.encrypter = new Encrypter(this, this.s.url, this.options); + } + return this.encrypter?.autoEncrypter; } get readConcern(): ReadConcern | undefined { @@ -686,9 +692,9 @@ export class MongoClient extends TypedEventEmitter implements }; if (this.autoEncrypter) { - await this.autoEncrypter?.init(); + await this.autoEncrypter.init(); await topologyConnect(); - await options.encrypter.connectInternalClient(); + await this.encrypter!.connectInternalClient(); } else { await topologyConnect(); } @@ -795,9 +801,8 @@ export class MongoClient extends TypedEventEmitter implements topology.close(); - const { encrypter } = this.options; - if (encrypter) { - await encrypter.close(this); + if (this.encrypter) { + await this.encrypter.close(this); } async function endSessions( @@ -1051,41 +1056,41 @@ export class MongoClient extends TypedEventEmitter implements */ export interface MongoOptions extends Required< - Pick< - MongoClientOptions, - | 'maxAdaptiveRetries' - | 'enableOverloadRetargeting' - | 'autoEncryption' - | 'connectTimeoutMS' - | 'directConnection' - | 'driverInfo' - | 'forceServerObjectId' - | 'minHeartbeatFrequencyMS' - | 'heartbeatFrequencyMS' - | 'localThresholdMS' - | 'maxConnecting' - | 'maxIdleTimeMS' - | 'maxPoolSize' - | 'minPoolSize' - | 'monitorCommands' - | 'noDelay' - | 'pkFactory' - | 'raw' - | 'replicaSet' - | 'retryReads' - | 'retryWrites' - | 'serverSelectionTimeoutMS' - | 'socketTimeoutMS' - | 'srvMaxHosts' - | 'srvServiceName' - | 'tlsAllowInvalidCertificates' - | 'tlsAllowInvalidHostnames' - | 'tlsInsecure' - | 'waitQueueTimeoutMS' - | 'zlibCompressionLevel' - > - >, - SupportedNodeConnectionOptions { + Pick< + MongoClientOptions, + | 'maxAdaptiveRetries' + | 'enableOverloadRetargeting' + | 'autoEncryption' + | 'connectTimeoutMS' + | 'directConnection' + | 'driverInfo' + | 'forceServerObjectId' + | 'minHeartbeatFrequencyMS' + | 'heartbeatFrequencyMS' + | 'localThresholdMS' + | 'maxConnecting' + | 'maxIdleTimeMS' + | 'maxPoolSize' + | 'minPoolSize' + | 'monitorCommands' + | 'noDelay' + | 'pkFactory' + | 'raw' + | 'replicaSet' + | 'retryReads' + | 'retryWrites' + | 'serverSelectionTimeoutMS' + | 'socketTimeoutMS' + | 'srvMaxHosts' + | 'srvServiceName' + | 'tlsAllowInvalidCertificates' + | 'tlsAllowInvalidHostnames' + | 'tlsInsecure' + | 'waitQueueTimeoutMS' + | 'zlibCompressionLevel' + > + >, + SupportedNodeConnectionOptions { appName?: string; hosts: HostAddress[]; srvHost?: string; @@ -1101,8 +1106,6 @@ export interface MongoOptions /** @internal */ metadata: Promise; /** @internal */ - autoEncrypter?: AutoEncrypter; - /** @internal */ tokenCache?: TokenCache; proxyHost?: string; proxyPort?: number; @@ -1114,11 +1117,11 @@ export interface MongoOptions /** @internal */ authProviders: MongoClientAuthProviders; /** @internal */ - encrypter: Encrypter; - /** @internal */ userSpecifiedAuthSource: boolean; /** @internal */ userSpecifiedReplicaSet: boolean; + /** @internal */ + useAutoEncryption: boolean; /** * # NOTE ABOUT TLS Options diff --git a/test/unit/client-side-encryption/auto_encrypter.test.ts b/test/unit/client-side-encryption/auto_encrypter.test.ts index 17fe6436b39..338f620eaf5 100644 --- a/test/unit/client-side-encryption/auto_encrypter.test.ts +++ b/test/unit/client-side-encryption/auto_encrypter.test.ts @@ -43,13 +43,14 @@ class MockClient { s: { options: any }; constructor(options?: any) { - this.options = { options: options || {} }; + this.options = { ...options || {} }; this.s = { options: this.options }; } } const originalAccessKeyId = process.env.AWS_ACCESS_KEY_ID; const originalSecretAccessKey = process.env.AWS_SECRET_ACCESS_KEY; +const emptyLogger = () => { }; describe('AutoEncrypter', function () { this.timeout(12000); @@ -95,10 +96,7 @@ describe('AutoEncrypter', function () { const autoEncrypterOptions = { mongocryptdBypassSpawn: true, keyVaultNamespace: 'admin.datakeys', - options: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - logger: () => {} - }, + options: { logger: emptyLogger }, kmsProviders: { aws: { accessKeyId: 'example', secretAccessKey: 'example' }, local: { key: Buffer.alloc(96) } @@ -141,6 +139,53 @@ describe('AutoEncrypter', function () { }); }); }); + + it('should pass settings to the mongocryptd client', function () { + const client = new MockClient() as MongoClient; + const autoEncrypterOptions = { + mongocryptdBypassSpawn: true, + keyVaultNamespace: 'admin.datakeys', + options: { logger: emptyLogger }, + kmsProviders: { + aws: { accessKeyId: 'example', secretAccessKey: 'example' }, + local: { key: Buffer.alloc(96) } + } + }; + const autoEncrypter = new AutoEncrypter(client, autoEncrypterOptions); + expect(autoEncrypter._mongocryptdClient.options.autoSelectFamily).to.be.true; + }); + it('should pass autoSelectFamily settings to the mongocryptd client', function () { + const client = new MockClient({ + autoFamilySelect: true, + }) as MongoClient; + const autoEncrypterOptions = { + mongocryptdBypassSpawn: true, + keyVaultNamespace: 'admin.datakeys', + options: { logger: emptyLogger }, + kmsProviders: { + aws: { accessKeyId: 'example', secretAccessKey: 'example' }, + local: { key: Buffer.alloc(96) } + } + }; + const autoEncrypter = new AutoEncrypter(client, autoEncrypterOptions); + expect(autoEncrypter._mongocryptdClient.options.autoSelectFamily).to.be.true; + }); + it('should pass autoSelectFamilyAttemptTimeout settings to the mongocryptd client', function () { + const client = new MockClient({ + autoSelectFamilyAttemptTimeout: 223456 + }) as MongoClient; + const autoEncrypterOptions = { + mongocryptdBypassSpawn: true, + keyVaultNamespace: 'admin.datakeys', + options: { logger: emptyLogger }, + kmsProviders: { + aws: { accessKeyId: 'example', secretAccessKey: 'example' }, + local: { key: Buffer.alloc(96) } + } + }; + const autoEncrypter = new AutoEncrypter(client, autoEncrypterOptions); + expect(autoEncrypter._mongocryptdClient.options.autoSelectFamilyAttemptTimeout).to.equal(223456); + }); }); it('should support `bypassAutoEncryption`', async function () { @@ -149,10 +194,7 @@ describe('AutoEncrypter', function () { bypassAutoEncryption: true, mongocryptdBypassSpawn: true, keyVaultNamespace: 'admin.datakeys', - options: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - logger: () => {} - }, + options: { logger: emptyLogger }, kmsProviders: { aws: { accessKeyId: 'example', secretAccessKey: 'example' }, local: { key: Buffer.alloc(96) } @@ -169,10 +211,7 @@ describe('AutoEncrypter', function () { const client = new MockClient() as MongoClient; const mc = new AutoEncrypter(client, { keyVaultNamespace: 'admin.datakeys', - options: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - logger: () => {} - }, + options: { logger: emptyLogger }, kmsProviders: { aws: { accessKeyId: 'example', secretAccessKey: 'example' }, local: { key: Buffer.alloc(96) } @@ -192,10 +231,7 @@ describe('AutoEncrypter', function () { const client = new MockClient(); const mc = new AutoEncrypter(client, { keyVaultNamespace: 'admin.datakeys', - options: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - logger: () => {} - }, + options: { logger: emptyLogger }, kmsProviders: { aws: { accessKeyId: 'example', secretAccessKey: 'example' }, local: { key: Buffer.alloc(96) } @@ -252,10 +288,7 @@ describe('AutoEncrypter', function () { const client = new MockClient(); const mc = new AutoEncrypter(client, { keyVaultNamespace: 'admin.datakeys', - options: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - logger: () => {} - }, + options: { logger: emptyLogger }, kmsProviders: { aws: {} } @@ -291,10 +324,7 @@ describe('AutoEncrypter', function () { const client = new MockClient(); const mc = new AutoEncrypter(client, { keyVaultNamespace: 'admin.datakeys', - options: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - logger: () => {} - }, + options: { logger: emptyLogger }, kmsProviders: { aws: {} } @@ -310,10 +340,7 @@ describe('AutoEncrypter', function () { const client = new MockClient(); const mc = new AutoEncrypter(client, { keyVaultNamespace: 'admin.datakeys', - options: { - // eslint-disable-next-line @typescript-eslint/no-empty-function - logger: () => {} - }, + options: { logger: emptyLogger }, kmsProviders: { aws: { accessKeyId: 'example', secretAccessKey: 'example' }, local: { key: Buffer.alloc(96) }