kndt84/passport-cognito

View on GitHub
lib/strategy.js

Summary

Maintainability
A
3 hrs
Test Coverage
// Load modules.
var AWS = require('aws-sdk');
var util = require('util');
var passport = require('passport-strategy');
var CognitoSDK = require('amazon-cognito-identity-js');

AWS.CognitoIdentityServiceProvider.AuthenticationDetails = CognitoSDK.AuthenticationDetails;
AWS.CognitoIdentityServiceProvider.CognitoUserPool = CognitoSDK.CognitoUserPool;
AWS.CognitoIdentityServiceProvider.CognitoUser = CognitoSDK.CognitoUser;

/**
 * `Strategy` constructor.
 *
 * The Cognito User Pools authentication strategy.
 *
 * Applications must supply a `verify` callback which accepts an `accessToken`, `idToken`
 * `refreshToken` and service-specific `profile`, and then calls the `cb`
 * callback supplying a `user`, which should be set to `false` if the
 * credentials are not valid.  If an exception occured, `err` should be set.
 *
 * Options:
 *   - `userPoolId`     Cognito User Pool Id        // required
 *   - `clientId`       Cognito app client Id       // required
 *   - `region`         AWS region                  // required
 *
 * Examples:
 *
 *     passport.use(new CognitoStrategy({
 *         userPoolId: 'ap-northeast-1_eSjqLfqKc',
 *         clientId: 'vtvg02tr21zmxvspyvawtv09b',
 *         region: 'ap-northeast-1'
 *       },
 *       function(accessToken, refreshToken, profile, cb) {
 *         User.findOrCreate(..., function (err, user) {
 *           cb(err, user);
 *         });
 *       }
 *     ));
 *
 * @constructor
 * @param {object} options
 * @param {function} verify
 * @access public
 */
function CognitoStrategy(options, verify) {

  if (!options) throw new Error('Cognito strategy requires options');
  if (!verify) throw new Error('Cognito strategy requires a verify callback');

  AWS.config.region = options.region;

  passport.Strategy.call(this);
  this.name = 'cognito';
  this._userPoolId = options.userPoolId;
  this._clientId = options.clientId;
  this._verify = verify;
}


// Inherit from `passport-strategy`.
util.inherits(CognitoStrategy, passport.Strategy);


/**
 * Authenticate request
 *
 * @param {http.IncomingMessage} req
 * @param {object} options
 * @access protected
 */
CognitoStrategy.prototype.authenticate = function(req, options) {

  var user = {};
  var username = req.body.username;
  var password = req.body.password;

  if (!username || !password) {
    return this.fail({ message: 'Missing credentials' }, 400);
  }

  var authenticationDetails = this._createAuthenticationDetails(username, password);
  var cognitoUser = this._createCognitoUser(username);

  var self = this;

  function verified(err, user, info) {
    if (err) { return self.error(err); }
    if (!user) { return self.fail(info); }
    self.success(user, info);
  }

  cognitoUser.authenticateUser(authenticationDetails, {
    onSuccess: function (session) {

      var accessToken = session.getAccessToken().getJwtToken();
      var idToken = session.getIdToken().getJwtToken();
      var refreshToken = session.getRefreshToken().getToken();

      if (!accessToken || !idToken) {
        return self.fail({ message: options.badRequestMessage || 'Missing token' }, 400);
      }

      self._getUserAttributes(cognitoUser, function(profile) {
        profile.username = username;
        try {
          if (self._verify.length == 6) {
            self._verify(accessToken, idToken, refreshToken, profile, session, verified)
          } else { // length = 5
            self._verify(accessToken, idToken, refreshToken, profile, verified);
          }
        } catch (ex) {
          return self.error(ex);
        }
      })
    },
    onFailure: function(err) {
      // console.log(err);
      return self.fail(err);
    },
    mfaRequired: function(codeDeliveryDetails) {
      // MFA is required to complete user authentication. 
      // Get the code from user and call 
      // cognitoUser.sendMFACode(mfaCode, this)
      return self.fail({ message: options.badRequestMessage || 'Multi factor authentication Required' }, 424);
    },
    newPasswordRequired: function(userAttributes, requiredAttributes) {
      // User was signed up by an admin and must provide new 
      // password and required attributes, if any, to complete 
      // authentication.

      // userAttributes: object, which is the user's current profile. It will list all attributes that are associated with the user. 
      // Required attributes according to schema, which don’t have any values yet, will have blank values.
      // requiredAttributes: list of attributes that must be set by the user along with new password to complete the sign-in.

      var newPassword = req.body.newpassword;
      if (newPassword) {
        var attributesData = [];
        requiredAttributes.map(function(att) {
          attributesData[att] = req.body[att];
        });

        console.log("newPasswordRequired2 ", attributesData, requiredAttributes);
        //Try to validate user
        return cognitoUser.completeNewPasswordChallenge(newPassword, attributesData, this);
      } else {
        return self.fail({ message: options.badRequestMessage || 'New Password Required' }, 412);
      }
    }
  });

};


/**
 * Create authentication detail object
 *
 * @param {string} username
 * @param {string} password
 * @return {AuthenticationDetails}
 * @access protected
 */
CognitoStrategy.prototype._createAuthenticationDetails = function(username, password) {
  var authenticationData = {
    Username: username,
    Password: password
  };

  return new AWS.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
};


/**
 * Create cognito user object
 *
 * @param {string} username
 * @return {CognitoUser}
 * @access protected
 */
CognitoStrategy.prototype._createCognitoUser = function(username) {

  var poolData = { 
    UserPoolId : this._userPoolId,
    ClientId : this._clientId
  };
  var userPool = new AWS.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
  var userData = {
    Username : username,
    Pool : userPool
  };

  return new AWS.CognitoIdentityServiceProvider.CognitoUser(userData);
};


/**
 * Get user attributes as a object
 *
 * @param {CognitoUser} cognitoUser
 * @param {function} callback
 * @return {object}
 * @access protected
 */
CognitoStrategy.prototype._getUserAttributes = function(cognitoUser, callback) {

  var self = this;
  cognitoUser.getUserAttributes(function(err, result) {
    if (err) return self.fail(err);

    var user = {}
    result.forEach(function(attr) {
      var obj = attr.getName();
      if(typeof(obj.Name) == "string"){
        user[obj.Name] = obj.Value;
      } else {
        // when using passport-cognito with 'amazon-cognito-identity-js'
        // an error occurs, this is a quick fix for this
        user[attr.getName()] = attr.getValue();
      }
    })
    callback(user);
  });
};


// Expose constructor.
module.exports = CognitoStrategy;