sspi.js 3.11 KB
'use strict';

const AuthProvider = require('./auth_provider').AuthProvider;
const retrieveKerberos = require('../utils').retrieveKerberos;
let kerberos;

/**
 * Creates a new SSPI authentication mechanism
 * @class
 * @extends AuthProvider
 */
class SSPI extends AuthProvider {
  /**
   * Implementation of authentication for a single connection
   * @override
   */
  _authenticateSingleConnection(sendAuthCommand, connection, credentials, callback) {
    // TODO: Destructure this
    const username = credentials.username;
    const password = credentials.password;
    const mechanismProperties = credentials.mechanismProperties;
    const gssapiServiceName =
      mechanismProperties['gssapiservicename'] ||
      mechanismProperties['gssapiServiceName'] ||
      'mongodb';

    SSIPAuthenticate(
      this,
      kerberos.processes.MongoAuthProcess,
      username,
      password,
      gssapiServiceName,
      sendAuthCommand,
      connection,
      mechanismProperties,
      callback
    );
  }

  /**
   * Authenticate
   * @override
   * @method
   */
  auth(sendAuthCommand, connections, credentials, callback) {
    if (kerberos == null) {
      try {
        kerberos = retrieveKerberos();
      } catch (e) {
        return callback(e, null);
      }
    }

    super.auth(sendAuthCommand, connections, credentials, callback);
  }
}

function SSIPAuthenticate(
  self,
  MongoAuthProcess,
  username,
  password,
  gssapiServiceName,
  sendAuthCommand,
  connection,
  options,
  callback
) {
  const authProcess = new MongoAuthProcess(
    connection.host,
    connection.port,
    gssapiServiceName,
    options
  );

  function authCommand(command, authCb) {
    sendAuthCommand(connection, '$external.$cmd', command, authCb);
  }

  authProcess.init(username, password, err => {
    if (err) return callback(err, false);

    authProcess.transition('', (err, payload) => {
      if (err) return callback(err, false);

      const command = {
        saslStart: 1,
        mechanism: 'GSSAPI',
        payload,
        autoAuthorize: 1
      };

      authCommand(command, (err, doc) => {
        if (err) return callback(err, false);

        authProcess.transition(doc.payload, (err, payload) => {
          if (err) return callback(err, false);
          const command = {
            saslContinue: 1,
            conversationId: doc.conversationId,
            payload
          };

          authCommand(command, (err, doc) => {
            if (err) return callback(err, false);

            authProcess.transition(doc.payload, (err, payload) => {
              if (err) return callback(err, false);
              const command = {
                saslContinue: 1,
                conversationId: doc.conversationId,
                payload
              };

              authCommand(command, (err, response) => {
                if (err) return callback(err, false);

                authProcess.transition(null, err => {
                  if (err) return callback(err, null);
                  callback(null, response);
                });
              });
            });
          });
        });
      });
    });
  });
}

module.exports = SSPI;