Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions src/client-side-encryption/auto_encrypter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
11 changes: 2 additions & 9 deletions src/connection_string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,14 +236,8 @@ class CaseInsensitiveMap<Value = any> extends Map<string, Value> {

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');
Expand Down Expand Up @@ -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
Expand Down
99 changes: 51 additions & 48 deletions src/mongo_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -419,6 +419,8 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
private connectionLock?: Promise<this>;
/** @internal */
private closeLock?: Promise<void>;
/** @internal */
private encrypter?: Encrypter;

/**
* The consolidate, parsed, transformed and merged options.
Expand All @@ -437,7 +439,7 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements
super();
this.on('error', noop);

this.options = parseOptions(url, this, options);
this.options = parseOptions(url, options);

this.appendMetadata(this.options.driverInfo);

Expand Down Expand Up @@ -540,7 +542,11 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> 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 {
Expand Down Expand Up @@ -686,9 +692,9 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> 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();
}
Expand Down Expand Up @@ -795,9 +801,8 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> implements

topology.close();

const { encrypter } = this.options;
if (encrypter) {
await encrypter.close(this);
if (this.encrypter) {
await this.encrypter.close(this);
}

async function endSessions(
Expand Down Expand Up @@ -1051,41 +1056,41 @@ export class MongoClient extends TypedEventEmitter<MongoClientEvents> 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;
Expand All @@ -1101,8 +1106,6 @@ export interface MongoOptions
/** @internal */
metadata: Promise<ClientMetadata>;
/** @internal */
autoEncrypter?: AutoEncrypter;
/** @internal */
tokenCache?: TokenCache;
proxyHost?: string;
proxyPort?: number;
Expand All @@ -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
Expand Down
85 changes: 56 additions & 29 deletions test/unit/client-side-encryption/auto_encrypter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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) }
Expand Down Expand Up @@ -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 () {
Expand All @@ -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) }
Expand All @@ -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) }
Expand All @@ -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) }
Expand Down Expand Up @@ -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: {}
}
Expand Down Expand Up @@ -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: {}
}
Expand All @@ -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) }
Expand Down
Loading