diff --git a/src/cmap/auth/scram.ts b/src/cmap/auth/scram.ts index 7a339151fa..d17abbac40 100644 --- a/src/cmap/auth/scram.ts +++ b/src/cmap/auth/scram.ts @@ -30,7 +30,10 @@ class ScramSHA extends AuthProvider { if (!credentials) { return callback(new MongoMissingCredentialsError('AuthContext must provide credentials.')); } - if (cryptoMethod === 'sha256' && saslprep == null) { + if ( + cryptoMethod === 'sha256' && + ('kModuleError' in saslprep || typeof saslprep !== 'function') + ) { emitWarning('Warning: no saslprep library specified. Passwords will not be sanitized'); } @@ -152,7 +155,8 @@ function continueScramConversation( let processedPassword; if (cryptoMethod === 'sha256') { - processedPassword = 'kModuleError' in saslprep ? password : saslprep(password); + processedPassword = + 'kModuleError' in saslprep || typeof saslprep !== 'function' ? password : saslprep(password); } else { try { processedPassword = passwordDigest(username, password); diff --git a/test/integration/auth/scram_sha_256.test.js b/test/integration/auth/scram_sha_256.test.js deleted file mode 100644 index 6718550e21..0000000000 --- a/test/integration/auth/scram_sha_256.test.js +++ /dev/null @@ -1,122 +0,0 @@ -'use strict'; - -const sinon = require('sinon'); -const { expect } = require('chai'); -const { Connection } = require('../../../src/cmap/connection'); -const { ScramSHA256 } = require('../../../src/cmap/auth/scram'); -const { setupDatabase } = require('../shared'); -const { LEGACY_HELLO_COMMAND } = require('../../../src/constants'); - -// TODO(NODE-4338): withClient usage prevented these tests from running -// the import has been removed since the function is being deleted, this is here to keep modifications minimal -// so that the implementer of the fix for these tests can try to reference the original intent -const withClient = () => null; - -describe('SCRAM_SHA_256', function () { - beforeEach(function () { - this.currentTest.skipReason = 'TODO(NODE-4338): correct withClient usage'; - this.currentTest.skip(); - }); - - const test = {}; - - // Note: this setup was adapted from the prose test setup - const userMap = { - both: { - description: 'user with both credentials', - username: 'both', - password: 'both', - mechanisms: ['SCRAM-SHA-1', 'SCRAM-SHA-256'] - } - }; - - const users = Object.keys(userMap).map(name => userMap[name]); - - afterEach(() => test.sandbox.restore()); - - before(function () { - test.sandbox = sinon.createSandbox(); - return setupDatabase(this.configuration); - }); - - before(function () { - return withClient(this.configuration.newClient(), client => { - test.oldDbName = this.configuration.db; - this.configuration.db = 'admin'; - const db = client.db(this.configuration.db); - - const createUserCommands = users.map(user => ({ - createUser: user.username, - pwd: user.password, - roles: ['root'], - mechanisms: user.mechanisms - })); - - return Promise.all(createUserCommands.map(cmd => db.command(cmd))); - }); - }); - - after(function () { - return withClient(this.configuration.newClient(), client => { - const db = client.db(this.configuration.db); - this.configuration.db = test.oldDbName; - - return Promise.all(users.map(user => db.removeUser(user.username))); - }); - }); - - it('should shorten SCRAM conversations if the server supports it', { - metadata: { requires: { mongodb: '>=4.4', topology: ['single'] } }, - test: function () { - const options = { - auth: { - username: userMap.both.username, - password: userMap.both.password - }, - authSource: this.configuration.db - }; - - let runCommandSpy; - test.sandbox.stub(ScramSHA256.prototype, 'auth').callsFake(function (authContext, callback) { - const connection = authContext.connection; - const auth = ScramSHA256.prototype.auth.wrappedMethod; - runCommandSpy = test.sandbox.spy(connection, 'command'); - function _callback(err, res) { - runCommandSpy.restore(); - callback(err, res); - } - - auth.apply(this, [authContext, _callback]); - }); - - return withClient(this.configuration.newClient({}, options), () => { - expect(runCommandSpy.callCount).to.equal(1); - }); - } - }); - - it('should send speculativeAuthenticate on initial handshake on MongoDB 4.4+', { - metadata: { requires: { mongodb: '>=4.4', topology: ['single'] } }, - test: function () { - const options = { - auth: { - username: userMap.both.username, - password: userMap.both.password - }, - authSource: this.configuration.db - }; - - const commandSpy = test.sandbox.spy(Connection.prototype, 'command'); - return withClient(this.configuration.newClient({}, options), () => { - const calls = commandSpy - .getCalls() - .filter(c => c.thisValue.id !== '') // ignore all monitor connections - .filter(c => c.args[1][LEGACY_HELLO_COMMAND]); // only consider handshakes - - expect(calls).to.have.length(1); - const handshakeDoc = calls[0].args[1]; - expect(handshakeDoc).to.have.property('speculativeAuthenticate'); - }); - } - }); -}); diff --git a/test/integration/auth/scram_sha_256.test.ts b/test/integration/auth/scram_sha_256.test.ts new file mode 100644 index 0000000000..2940ada09b --- /dev/null +++ b/test/integration/auth/scram_sha_256.test.ts @@ -0,0 +1,64 @@ +import { expect } from 'chai'; +import * as sinon from 'sinon'; + +// eslint-disable-next-line @typescript-eslint/no-restricted-imports +import * as deps from '../../../src/deps'; +import { type MongoClient } from '../../mongodb'; + +describe('SCRAM_SHA_256', function () { + beforeEach(function () { + if (!this.configuration.parameters.authenticationMechanisms.includes('SCRAM-SHA-256')) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.currentTest!.skipReason = 'Test requires that SCRAM-SHA-256 be enabled on the server.'; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + this.currentTest!.skip(); + } + }); + + context('when saslprep is not a function', () => { + let client: MongoClient; + + beforeEach(function () { + sinon.stub(deps, 'saslprep').value({}); + client = this.configuration.newClient({ authMechanism: 'SCRAM-SHA-256' }); + }); + + afterEach(() => { + sinon.restore(); + return client.close(); + }); + + it('does not throw an error', { requires: { auth: 'enabled' } }, async function () { + await client.connect(); + }); + + it('emits a warning', { requires: { auth: 'enabled' } }, async function () { + const warnings: Array = []; + process.once('warning', w => warnings.push(w)); + await client.connect(); + expect(warnings).to.have.lengthOf(1); + expect(warnings[0]).to.have.property( + 'message', + 'Warning: no saslprep library specified. Passwords will not be sanitized' + ); + }); + }); + + context('when saslprep is a function', () => { + let client: MongoClient; + + beforeEach(function () { + client = this.configuration.newClient({ authMechanism: 'SCRAM-SHA-256' }); + }); + + afterEach(() => client.close()); + + it('calls saslprep', { requires: { auth: 'enabled' } }, async function () { + const spy = sinon.spy(deps, 'saslprep'); + + await client.connect(); + + expect(spy).to.have.been.called; + }); + }); +});