From 60bfc483dba568c1a0f62a2a78d7ce50ed03082c Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 28 Feb 2024 18:54:17 +0100 Subject: [PATCH] feat(NODE-5940): cache the AWS credentials provider in the MONGODB-AWS auth logic (#4000) --- src/cmap/auth/auth_provider.ts | 4 + src/cmap/auth/mongodb_aws.ts | 123 +++++++++++----------- src/cmap/auth/scram.ts | 7 +- src/cmap/connect.ts | 33 ++---- src/cmap/connection.ts | 3 + src/cmap/connection_pool.ts | 9 +- src/index.ts | 4 +- src/mongo_client.ts | 6 +- src/mongo_client_auth_providers.ts | 52 +++++++++ src/utils.ts | 3 + test/integration/auth/mongodb_aws.test.ts | 35 +++++- test/unit/cmap/connect.test.ts | 22 ++-- test/unit/cmap/connection.test.ts | 7 +- test/unit/cmap/connection_pool.test.js | 4 + test/unit/index.test.ts | 1 + test/unit/mongo_client.test.js | 22 +++- 16 files changed, 229 insertions(+), 106 deletions(-) create mode 100644 src/mongo_client_auth_providers.ts diff --git a/src/cmap/auth/auth_provider.ts b/src/cmap/auth/auth_provider.ts index 37a47889b9..e40c791ea5 100644 --- a/src/cmap/auth/auth_provider.ts +++ b/src/cmap/auth/auth_provider.ts @@ -34,6 +34,10 @@ export class AuthContext { } } +/** + * Provider used during authentication. + * @internal + */ export abstract class AuthProvider { /** * Prepare the handshake document before the initial handshake. diff --git a/src/cmap/auth/mongodb_aws.ts b/src/cmap/auth/mongodb_aws.ts index cfaf8e6f9b..0e430dd1ac 100644 --- a/src/cmap/auth/mongodb_aws.ts +++ b/src/cmap/auth/mongodb_aws.ts @@ -1,17 +1,15 @@ -import * as crypto from 'crypto'; import * as process from 'process'; -import { promisify } from 'util'; import type { Binary, BSONSerializeOptions } from '../../bson'; import * as BSON from '../../bson'; -import { aws4, getAwsCredentialProvider } from '../../deps'; +import { aws4, type AWSCredentials, getAwsCredentialProvider } from '../../deps'; import { MongoAWSError, MongoCompatibilityError, MongoMissingCredentialsError, MongoRuntimeError } from '../../error'; -import { ByteUtils, maxWireVersion, ns, request } from '../../utils'; +import { ByteUtils, maxWireVersion, ns, randomBytes, request } from '../../utils'; import { type AuthContext, AuthProvider } from './auth_provider'; import { MongoCredentials } from './mongo_credentials'; import { AuthMechanism } from './providers'; @@ -57,12 +55,40 @@ interface AWSSaslContinuePayload { } export class MongoDBAWS extends AuthProvider { - static credentialProvider: ReturnType | null = null; - randomBytesAsync: (size: number) => Promise; + static credentialProvider: ReturnType; + provider?: () => Promise; constructor() { super(); - this.randomBytesAsync = promisify(crypto.randomBytes); + MongoDBAWS.credentialProvider ??= getAwsCredentialProvider(); + + let { AWS_STS_REGIONAL_ENDPOINTS = '', AWS_REGION = '' } = process.env; + AWS_STS_REGIONAL_ENDPOINTS = AWS_STS_REGIONAL_ENDPOINTS.toLowerCase(); + AWS_REGION = AWS_REGION.toLowerCase(); + + /** The option setting should work only for users who have explicit settings in their environment, the driver should not encode "defaults" */ + const awsRegionSettingsExist = + AWS_REGION.length !== 0 && AWS_STS_REGIONAL_ENDPOINTS.length !== 0; + + /** + * If AWS_STS_REGIONAL_ENDPOINTS is set to regional, users are opting into the new behavior of respecting the region settings + * + * If AWS_STS_REGIONAL_ENDPOINTS is set to legacy, then "old" regions need to keep using the global setting. + * Technically the SDK gets this wrong, it reaches out to 'sts.us-east-1.amazonaws.com' when it should be 'sts.amazonaws.com'. + * That is not our bug to fix here. We leave that up to the SDK. + */ + const useRegionalSts = + AWS_STS_REGIONAL_ENDPOINTS === 'regional' || + (AWS_STS_REGIONAL_ENDPOINTS === 'legacy' && !LEGACY_REGIONS.has(AWS_REGION)); + + if ('fromNodeProviderChain' in MongoDBAWS.credentialProvider) { + this.provider = + awsRegionSettingsExist && useRegionalSts + ? MongoDBAWS.credentialProvider.fromNodeProviderChain({ + clientConfig: { region: AWS_REGION } + }) + : MongoDBAWS.credentialProvider.fromNodeProviderChain(); + } } override async auth(authContext: AuthContext): Promise { @@ -83,7 +109,7 @@ export class MongoDBAWS extends AuthProvider { } if (!authContext.credentials.username) { - authContext.credentials = await makeTempCredentials(authContext.credentials); + authContext.credentials = await makeTempCredentials(authContext.credentials, this.provider); } const { credentials } = authContext; @@ -101,7 +127,7 @@ export class MongoDBAWS extends AuthProvider { : undefined; const db = credentials.source; - const nonce = await this.randomBytesAsync(32); + const nonce = await randomBytes(32); const saslStart = { saslStart: 1, @@ -181,7 +207,10 @@ interface AWSTempCredentials { Expiration?: Date; } -async function makeTempCredentials(credentials: MongoCredentials): Promise { +async function makeTempCredentials( + credentials: MongoCredentials, + provider?: () => Promise +): Promise { function makeMongoCredentialsFromAWSTemp(creds: AWSTempCredentials) { if (!creds.AccessKeyId || !creds.SecretAccessKey || !creds.Token) { throw new MongoMissingCredentialsError('Could not obtain temporary MONGODB-AWS credentials'); @@ -198,11 +227,31 @@ async function makeTempCredentials(credentials: MongoCredentials): Promise Promise; constructor(cryptoMethod: CryptoMethod) { super(); this.cryptoMethod = cryptoMethod || 'sha1'; - this.randomBytesAsync = promisify(crypto.randomBytes); } override async prepare( @@ -41,7 +38,7 @@ class ScramSHA extends AuthProvider { emitWarning('Warning: no saslprep library specified. Passwords will not be sanitized'); } - const nonce = await this.randomBytesAsync(24); + const nonce = await randomBytes(24); // store the nonce for later use authContext.nonce = nonce; diff --git a/src/cmap/connect.ts b/src/cmap/connect.ts index 85bb80e263..ff143e2747 100644 --- a/src/cmap/connect.ts +++ b/src/cmap/connect.ts @@ -17,14 +17,8 @@ import { needsRetryableWriteLabel } from '../error'; import { type Callback, HostAddress, ns } from '../utils'; -import { AuthContext, type AuthProvider } from './auth/auth_provider'; -import { GSSAPI } from './auth/gssapi'; -import { MongoCR } from './auth/mongocr'; -import { MongoDBAWS } from './auth/mongodb_aws'; -import { Plain } from './auth/plain'; +import { AuthContext } from './auth/auth_provider'; import { AuthMechanism } from './auth/providers'; -import { ScramSHA1, ScramSHA256 } from './auth/scram'; -import { X509 } from './auth/x509'; import { type CommandOptions, Connection, @@ -39,17 +33,6 @@ import { MIN_SUPPORTED_WIRE_VERSION } from './wire_protocol/constants'; -/** @internal */ -export const AUTH_PROVIDERS = new Map([ - [AuthMechanism.MONGODB_AWS, new MongoDBAWS()], - [AuthMechanism.MONGODB_CR, new MongoCR()], - [AuthMechanism.MONGODB_GSSAPI, new GSSAPI()], - [AuthMechanism.MONGODB_PLAIN, new Plain()], - [AuthMechanism.MONGODB_SCRAM_SHA1, new ScramSHA1()], - [AuthMechanism.MONGODB_SCRAM_SHA256, new ScramSHA256()], - [AuthMechanism.MONGODB_X509, new X509()] -]); - /** @public */ export type Stream = Socket | TLSSocket; @@ -110,7 +93,7 @@ async function performInitialHandshake( if (credentials) { if ( !(credentials.mechanism === AuthMechanism.MONGODB_DEFAULT) && - !AUTH_PROVIDERS.get(credentials.mechanism) + !options.authProviders.getOrCreateProvider(credentials.mechanism) ) { throw new MongoInvalidArgumentError(`AuthMechanism '${credentials.mechanism}' not supported`); } @@ -165,7 +148,7 @@ async function performInitialHandshake( authContext.response = response; const resolvedCredentials = credentials.resolveAuthMechanism(response); - const provider = AUTH_PROVIDERS.get(resolvedCredentials.mechanism); + const provider = options.authProviders.getOrCreateProvider(resolvedCredentials.mechanism); if (!provider) { throw new MongoInvalidArgumentError( `No AuthProvider for ${resolvedCredentials.mechanism} defined.` @@ -186,6 +169,10 @@ async function performInitialHandshake( } } +/** + * HandshakeDocument used during authentication. + * @internal + */ export interface HandshakeDocument extends Document { /** * @deprecated Use hello instead @@ -227,7 +214,9 @@ export async function prepareHandshakeDocument( if (credentials.mechanism === AuthMechanism.MONGODB_DEFAULT && credentials.username) { handshakeDoc.saslSupportedMechs = `${credentials.source}.${credentials.username}`; - const provider = AUTH_PROVIDERS.get(AuthMechanism.MONGODB_SCRAM_SHA256); + const provider = authContext.options.authProviders.getOrCreateProvider( + AuthMechanism.MONGODB_SCRAM_SHA256 + ); if (!provider) { // This auth mechanism is always present. throw new MongoInvalidArgumentError( @@ -236,7 +225,7 @@ export async function prepareHandshakeDocument( } return provider.prepare(handshakeDoc, authContext); } - const provider = AUTH_PROVIDERS.get(credentials.mechanism); + const provider = authContext.options.authProviders.getOrCreateProvider(credentials.mechanism); if (!provider) { throw new MongoInvalidArgumentError(`No AuthProvider for ${credentials.mechanism} defined.`); } diff --git a/src/cmap/connection.ts b/src/cmap/connection.ts index da3c6a4beb..b5aaf3ff8f 100644 --- a/src/cmap/connection.ts +++ b/src/cmap/connection.ts @@ -23,6 +23,7 @@ import { MongoWriteConcernError } from '../error'; import type { ServerApi, SupportedNodeConnectionOptions } from '../mongo_client'; +import { type MongoClientAuthProviders } from '../mongo_client_auth_providers'; import { type CancellationToken, TypedEventEmitter } from '../mongo_types'; import type { ReadPreferenceLike } from '../read_preference'; import { applySession, type ClientSession, updateSessionFromResponse } from '../sessions'; @@ -120,6 +121,8 @@ export interface ConnectionOptions /** @internal */ connectionType?: typeof Connection; credentials?: MongoCredentials; + /** @internal */ + authProviders: MongoClientAuthProviders; connectTimeoutMS?: number; tls: boolean; /** @deprecated - Will not be able to turn off in the future. */ diff --git a/src/cmap/connection_pool.ts b/src/cmap/connection_pool.ts index 4dfa1e3896..f5bc3dd6bf 100644 --- a/src/cmap/connection_pool.ts +++ b/src/cmap/connection_pool.ts @@ -28,7 +28,7 @@ import { import { CancellationToken, TypedEventEmitter } from '../mongo_types'; import type { Server } from '../sdam/server'; import { type Callback, eachAsync, List, makeCounter } from '../utils'; -import { AUTH_PROVIDERS, connect } from './connect'; +import { connect } from './connect'; import { Connection, type ConnectionEvents, type ConnectionOptions } from './connection'; import { ConnectionCheckedInEvent, @@ -620,7 +620,9 @@ export class ConnectionPool extends TypedEventEmitter { ); } const resolvedCredentials = credentials.resolveAuthMechanism(connection.hello || undefined); - const provider = AUTH_PROVIDERS.get(resolvedCredentials.mechanism); + const provider = this[kServer].topology.client.s.authProviders.getOrCreateProvider( + resolvedCredentials.mechanism + ); if (!provider) { return callback( new MongoMissingCredentialsError( @@ -697,7 +699,8 @@ export class ConnectionPool extends TypedEventEmitter { ...this.options, id: this[kConnectionCounter].next().value, generation: this[kGeneration], - cancellationToken: this[kCancellationToken] + cancellationToken: this[kCancellationToken], + authProviders: this[kServer].topology.client.s.authProviders }; this[kPending]++; diff --git a/src/index.ts b/src/index.ts index c4960f6140..93266e26eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -200,7 +200,7 @@ export type { ResumeToken, UpdateDescription } from './change_stream'; -export type { AuthContext } from './cmap/auth/auth_provider'; +export type { AuthContext, AuthProvider } from './cmap/auth/auth_provider'; export type { AuthMechanismProperties, MongoCredentials, @@ -217,6 +217,7 @@ export type { Response, WriteProtocolMessageType } from './cmap/commands'; +export type { HandshakeDocument } from './cmap/connect'; export type { LEGAL_TCP_SOCKET_OPTIONS, LEGAL_TLS_SOCKET_OPTIONS, Stream } from './cmap/connect'; export type { CommandOptions, @@ -304,6 +305,7 @@ export type { SupportedTLSSocketOptions, WithSessionCallback } from './mongo_client'; +export { MongoClientAuthProviders } from './mongo_client_auth_providers'; export type { Log, LogConvertible, diff --git a/src/mongo_client.ts b/src/mongo_client.ts index 270e3fd4cd..6c37f3de29 100644 --- a/src/mongo_client.ts +++ b/src/mongo_client.ts @@ -16,6 +16,7 @@ import { Db, type DbOptions } from './db'; import type { AutoEncrypter, AutoEncryptionOptions } from './deps'; import type { Encrypter } from './encrypter'; import { MongoInvalidArgumentError } from './error'; +import { MongoClientAuthProviders } from './mongo_client_auth_providers'; import { MongoLogger, type MongoLoggerOptions } from './mongo_logger'; import { TypedEventEmitter } from './mongo_types'; import type { ReadConcern, ReadConcernLevel, ReadConcernLike } from './read_concern'; @@ -293,6 +294,7 @@ export interface MongoClientPrivate { bsonOptions: BSONSerializeOptions; namespace: MongoDBNamespace; hasBeenClosed: boolean; + authProviders: MongoClientAuthProviders; /** * We keep a reference to the sessions that are acquired from the pool. * - used to track and close all sessions in client.close() (which is non-standard behavior) @@ -369,6 +371,7 @@ export class MongoClient extends TypedEventEmitter { hasBeenClosed: false, sessionPool: new ServerSessionPool(this), activeSessions: new Set(), + authProviders: new MongoClientAuthProviders(), get options() { return client[kOptions]; @@ -765,7 +768,8 @@ export interface MongoOptions /** @internal */ connectionType?: typeof Connection; - + /** @internal */ + authProviders: MongoClientAuthProviders; /** @internal */ encrypter: Encrypter; /** @internal */ diff --git a/src/mongo_client_auth_providers.ts b/src/mongo_client_auth_providers.ts new file mode 100644 index 0000000000..0e59ca017e --- /dev/null +++ b/src/mongo_client_auth_providers.ts @@ -0,0 +1,52 @@ +import { type AuthProvider } from './cmap/auth/auth_provider'; +import { GSSAPI } from './cmap/auth/gssapi'; +import { MongoCR } from './cmap/auth/mongocr'; +import { MongoDBAWS } from './cmap/auth/mongodb_aws'; +import { Plain } from './cmap/auth/plain'; +import { AuthMechanism } from './cmap/auth/providers'; +import { ScramSHA1, ScramSHA256 } from './cmap/auth/scram'; +import { X509 } from './cmap/auth/x509'; +import { MongoInvalidArgumentError } from './error'; + +/** @internal */ +const AUTH_PROVIDERS = new Map AuthProvider>([ + [AuthMechanism.MONGODB_AWS, () => new MongoDBAWS()], + [AuthMechanism.MONGODB_CR, () => new MongoCR()], + [AuthMechanism.MONGODB_GSSAPI, () => new GSSAPI()], + [AuthMechanism.MONGODB_PLAIN, () => new Plain()], + [AuthMechanism.MONGODB_SCRAM_SHA1, () => new ScramSHA1()], + [AuthMechanism.MONGODB_SCRAM_SHA256, () => new ScramSHA256()], + [AuthMechanism.MONGODB_X509, () => new X509()] +]); + +/** + * Create a set of providers per client + * to avoid sharing the provider's cache between different clients. + * @internal + */ +export class MongoClientAuthProviders { + private existingProviders: Map = new Map(); + + /** + * Get or create an authentication provider based on the provided mechanism. + * We don't want to create all providers at once, as some providers may not be used. + * @param name - The name of the provider to get or create. + * @returns The provider. + * @throws MongoInvalidArgumentError if the mechanism is not supported. + * @internal + */ + getOrCreateProvider(name: AuthMechanism | string): AuthProvider { + const authProvider = this.existingProviders.get(name); + if (authProvider) { + return authProvider; + } + + const provider = AUTH_PROVIDERS.get(name)?.(); + if (!provider) { + throw new MongoInvalidArgumentError(`authMechanism ${name} not supported`); + } + + this.existingProviders.set(name, provider); + return provider; + } +} diff --git a/src/utils.ts b/src/utils.ts index 2948b0d729..559f6a4df3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,6 +3,7 @@ import type { SrvRecord } from 'dns'; import * as http from 'http'; import * as url from 'url'; import { URL } from 'url'; +import { promisify } from 'util'; import { type Document, ObjectId, resolveBSONOptions } from './bson'; import type { Connection } from './cmap/connection'; @@ -1317,3 +1318,5 @@ export async function request( req.end(); }); } + +export const randomBytes = promisify(crypto.randomBytes); diff --git a/test/integration/auth/mongodb_aws.test.ts b/test/integration/auth/mongodb_aws.test.ts index e075b67775..1981324939 100644 --- a/test/integration/auth/mongodb_aws.test.ts +++ b/test/integration/auth/mongodb_aws.test.ts @@ -67,6 +67,20 @@ describe('MONGODB-AWS', function () { .that.equals(''); }); + it('should store a MongoDBAWS provider instance per client', async function () { + client = this.configuration.newClient(process.env.MONGODB_URI); + + await client + .db('aws') + .collection('aws_test') + .estimatedDocumentCount() + .catch(error => error); + + expect(client).to.have.nested.property('s.authProviders'); + const provider = client.s.authProviders.getOrCreateProvider('MONGODB-AWS'); + expect(provider).to.be.instanceOf(MongoDBAWS); + }); + describe('EC2 with missing credentials', () => { let client; @@ -190,6 +204,7 @@ describe('MONGODB-AWS', function () { let storedEnv; let calledArguments; let shouldSkip = false; + let numberOfFromNodeProviderChainCalls; const envCheck = () => { const { AWS_WEB_IDENTITY_TOKEN_FILE = '' } = process.env; @@ -204,8 +219,6 @@ describe('MONGODB-AWS', function () { return this.skip(); } - client = this.configuration.newClient(process.env.MONGODB_URI); - storedEnv = process.env; if (test.env.AWS_STS_REGIONAL_ENDPOINTS === undefined) { delete process.env.AWS_STS_REGIONAL_ENDPOINTS; @@ -218,13 +231,17 @@ describe('MONGODB-AWS', function () { process.env.AWS_REGION = test.env.AWS_REGION; } - calledArguments = []; + numberOfFromNodeProviderChainCalls = 0; + MongoDBAWS.credentialProvider = { fromNodeProviderChain(...args) { calledArguments = args; + numberOfFromNodeProviderChainCalls += 1; return credentialProvider.fromNodeProviderChain(...args); } }; + + client = this.configuration.newClient(process.env.MONGODB_URI); }); afterEach(() => { @@ -253,6 +270,18 @@ describe('MONGODB-AWS', function () { expect(calledArguments).to.deep.equal(test.calledWith); }); + + it('fromNodeProviderChain called once', async function () { + await client.close(); + await client.connect(); + await client + .db('aws') + .collection('aws_test') + .estimatedDocumentCount() + .catch(error => error); + + expect(numberOfFromNodeProviderChainCalls).to.be.eql(1); + }); }); } }); diff --git a/test/unit/cmap/connect.test.ts b/test/unit/cmap/connect.test.ts index c3327a8646..c636c0cd81 100644 --- a/test/unit/cmap/connect.test.ts +++ b/test/unit/cmap/connect.test.ts @@ -10,6 +10,7 @@ import { HostAddress, isHello, LEGACY_HELLO_COMMAND, + MongoClientAuthProviders, MongoCredentials, MongoNetworkError, prepareHandshakeDocument @@ -45,7 +46,8 @@ describe('Connect Tests', function () { source: 'admin', mechanism: 'PLAIN', mechanismProperties: {} - }) + }), + authProviders: new MongoClientAuthProviders() }; }); @@ -207,7 +209,9 @@ describe('Connect Tests', function () { context('prepareHandshakeDocument', () => { context('when serverApi.version is present', () => { - const options = {}; + const options = { + authProviders: new MongoClientAuthProviders() + }; const authContext = { connection: { serverApi: { version: '1' } }, options @@ -222,7 +226,9 @@ describe('Connect Tests', function () { context('when serverApi is not present', () => { const options = {}; const authContext = { - connection: {}, + connection: { + authProviders: new MongoClientAuthProviders() + }, options }; @@ -235,7 +241,9 @@ describe('Connect Tests', function () { context('loadBalanced option', () => { context('when loadBalanced is not set as an option', () => { it('does not set loadBalanced on the handshake document', async () => { - const options = {}; + const options = { + authProviders: new MongoClientAuthProviders() + }; const authContext = { connection: {}, options @@ -248,7 +256,8 @@ describe('Connect Tests', function () { context('when loadBalanced is set to false', () => { it('does not set loadBalanced on the handshake document', async () => { const options = { - loadBalanced: false + loadBalanced: false, + authProviders: new MongoClientAuthProviders() }; const authContext = { connection: {}, @@ -262,7 +271,8 @@ describe('Connect Tests', function () { context('when loadBalanced is set to true', () => { it('does set loadBalanced on the handshake document', async () => { const options = { - loadBalanced: true + loadBalanced: true, + authProviders: new MongoClientAuthProviders() }; const authContext = { connection: {}, diff --git a/test/unit/cmap/connection.test.ts b/test/unit/cmap/connection.test.ts index 42c4dfae9d..25f72e0bd7 100644 --- a/test/unit/cmap/connection.test.ts +++ b/test/unit/cmap/connection.test.ts @@ -15,6 +15,7 @@ import { type HostAddress, isHello, type MessageStream, + MongoClientAuthProviders, MongoNetworkError, MongoNetworkTimeoutError, MongoRuntimeError, @@ -31,7 +32,8 @@ const connectionOptionsDefaults = { monitorCommands: false, tls: false, metadata: undefined, - loadBalanced: false + loadBalanced: false, + authProviders: new MongoClientAuthProviders() }; /** @@ -446,7 +448,8 @@ describe('new Connection()', function () { generation: 1, monitorCommands: false, metadata: {} as ClientMetadata, - loadBalanced: false + loadBalanced: false, + authProviders: new MongoClientAuthProviders() }; let server; let connectOptions; diff --git a/test/unit/cmap/connection_pool.test.js b/test/unit/cmap/connection_pool.test.js index ecfb8fb769..a08c955d14 100644 --- a/test/unit/cmap/connection_pool.test.js +++ b/test/unit/cmap/connection_pool.test.js @@ -10,6 +10,7 @@ const { ns, isHello } = require('../../mongodb'); const { LEGACY_HELLO_COMMAND } = require('../../mongodb'); const { createTimerSandbox } = require('../timer_sandbox'); const { topologyWithPlaceholderClient } = require('../../tools/utils'); +const { MongoClientAuthProviders } = require('../../mongodb'); describe('Connection Pool', function () { let mockMongod; @@ -18,6 +19,9 @@ describe('Connection Pool', function () { client: { mongoLogger: { debug: () => null + }, + s: { + authProviders: new MongoClientAuthProviders() } } } diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index 833082b983..c3cbc5510d 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -69,6 +69,7 @@ const EXPECTED_EXPORTS = [ 'MongoBulkWriteError', 'MongoChangeStreamError', 'MongoClient', + 'MongoClientAuthProviders', 'MongoCompatibilityError', 'MongoCursorExhaustedError', 'MongoCursorInUseError', diff --git a/test/unit/mongo_client.test.js b/test/unit/mongo_client.test.js index 2791f1d8ed..f1b8ed24f1 100644 --- a/test/unit/mongo_client.test.js +++ b/test/unit/mongo_client.test.js @@ -8,12 +8,17 @@ const { ReadConcern } = require('../mongodb'); const { WriteConcern } = require('../mongodb'); const { ReadPreference } = require('../mongodb'); const { MongoCredentials } = require('../mongodb'); -const { MongoClient, MongoParseError, ServerApiVersion } = require('../mongodb'); +const { + MongoClient, + MongoParseError, + ServerApiVersion, + MongoInvalidArgumentError +} = require('../mongodb'); const { MongoLogger } = require('../mongodb'); const sinon = require('sinon'); const { Writable } = require('stream'); -describe('MongoOptions', function () { +describe('MongoClient', function () { it('MongoClient should always freeze public options', function () { const client = new MongoClient('mongodb://localhost:27017'); expect(client.options).to.be.frozen; @@ -876,4 +881,17 @@ describe('MongoOptions', function () { expect(client.options).to.have.property('mongoLoggerOptions').to.equal(expectedLoggingObject); }); }); + + context('getAuthProvider', function () { + it('throws MongoInvalidArgumentError if provided authMechanism is not supported', function () { + const client = new MongoClient('mongodb://localhost:27017'); + try { + client.s.authProviders.getOrCreateProvider('NOT_SUPPORTED'); + } catch (error) { + expect(error).to.be.an.instanceof(MongoInvalidArgumentError); + return; + } + expect.fail('missed exception'); + }); + }); });