Skip to content
This repository has been archived by the owner on Feb 4, 2022. It is now read-only.

Commit

Permalink
refactor(auth): switch to MongoCredentials and cleanup
Browse files Browse the repository at this point in the history
A major refactor to rewrite core to use MongoCredentials objects
instead of individually passing around auth fields. Also removes
significant amounts of duplicate code in auth providers. Also
switches to the async version of crypto.randomBytes to
resolve #307.

Fixes NODE-1436
Fixes NODE-1442
  • Loading branch information
daprahamian authored and mbroadst committed Feb 8, 2019
1 parent 99abe7d commit 0b9243d
Show file tree
Hide file tree
Showing 22 changed files with 981 additions and 1,434 deletions.
1 change: 1 addition & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ module.exports = {
// Raw operations
Query: require('./lib/connection/commands').Query,
// Auth mechanisms
MongoCredentials: require('./lib/auth/mongo_credentials').MongoCredentials,
defaultAuthProviders: require('./lib/auth/defaultAuthProviders').defaultAuthProviders,
MongoCR: require('./lib/auth/mongocr'),
X509: require('./lib/auth/x509'),
Expand Down
160 changes: 160 additions & 0 deletions lib/auth/auth_provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
'use strict';

const MongoError = require('../error').MongoError;

/**
* Creates a new AuthProvider, which dictates how to authenticate for a given
* mechanism.
* @class
*/
class AuthProvider {
constructor(bson) {
this.bson = bson;
this.authStore = [];
}

/**
* Authenticate
* @method
* @param {SendAuthCommand} sendAuthCommand Writes an auth command directly to a specific connection
* @param {Connection[]} connections Connections to authenticate using this authenticator
* @param {MongoCredentials} credentials Authentication credentials
* @param {authResultCallback} callback The callback to return the result from the authentication
*/
auth(sendAuthCommand, connections, credentials, callback) {
// Total connections
let count = connections.length;

if (count === 0) {
callback(null, null);
return;
}

// Valid connections
let numberOfValidConnections = 0;
let errorObject = null;

const execute = connection => {
this._authenticateSingleConnection(sendAuthCommand, connection, credentials, (err, r) => {
// Adjust count
count = count - 1;

// If we have an error
if (err) {
errorObject = err;
} else if (r.result && r.result['$err']) {
errorObject = r.result;
} else if (r.result && r.result['errmsg']) {
errorObject = r.result;
} else {
numberOfValidConnections = numberOfValidConnections + 1;
}

// Still authenticating against other connections.
if (count !== 0) {
return;
}

// We have authenticated all connections
if (numberOfValidConnections > 0) {
// Store the auth details
this.addCredentials(credentials);
// Return correct authentication
callback(null, true);
} else {
if (errorObject == null) {
errorObject = new MongoError(`failed to authenticate using ${credentials.mechanism}`);
}
callback(errorObject, false);
}
});
};

const executeInNextTick = _connection => process.nextTick(() => execute(_connection));

// For each connection we need to authenticate
while (connections.length > 0) {
executeInNextTick(connections.shift());
}
}

/**
* Implementation of a single connection authenticating. Is meant to be overridden.
* Will error if called directly
* @ignore
*/
_authenticateSingleConnection(/*sendAuthCommand, connection, credentials, callback*/) {
throw new Error('_authenticateSingleConnection must be overridden');
}

/**
* Adds credentials to store only if it does not exist
* @param {MongoCredentials} credentials credentials to add to store
*/
addCredentials(credentials) {
const found = this.authStore.some(cred => cred.equals(credentials));

if (!found) {
this.authStore.push(credentials);
}
}

/**
* Re authenticate pool
* @method
* @param {SendAuthCommand} sendAuthCommand Writes an auth command directly to a specific connection
* @param {Connection[]} connections Connections to authenticate using this authenticator
* @param {authResultCallback} callback The callback to return the result from the authentication
*/
reauthenticate(sendAuthCommand, connections, callback) {
const authStore = this.authStore.slice(0);
let count = authStore.length;
if (count === 0) {
return callback(null, null);
}

for (let i = 0; i < authStore.length; i++) {
this.auth(sendAuthCommand, connections, authStore[i], function(err) {
count = count - 1;
if (count === 0) {
callback(err, null);
}
});
}
}

/**
* Remove credentials that have been previously stored in the auth provider
* @method
* @param {string} source Name of database we are removing authStore details about
* @return {object}
*/
logout(source) {
this.authStore = this.authStore.filter(credentials => credentials.source !== source);
}
}

/**
* A function that writes authentication commands to a specific connection
* @callback SendAuthCommand
* @param {Connection} connection The connection to write to
* @param {Command} command A command with a toBin method that can be written to a connection
* @param {AuthWriteCallback} callback Callback called when command response is received
*/

/**
* A callback for a specific auth command
* @callback AuthWriteCallback
* @param {Error} err If command failed, an error from the server
* @param {object} r The response from the server
*/

/**
* This is a result from an authentication strategy
*
* @callback authResultCallback
* @param {error} error An error object. Set to null if no error present
* @param {boolean} result The result of the authentication process
*/

module.exports = { AuthProvider };
Loading

0 comments on commit 0b9243d

Please sign in to comment.