nowk/cobbler.js

View on GitHub
index.js

Summary

Maintainability
A
0 mins
Test Coverage
/* jshint node: true */

/*
 * expose
 */

module.exports = cobbler;

// backup originals
cobbler.__ = {};

/*
 * cobbler
 *
 * @param {Stategy|String|Constructor|Prototype} strategy
 * @param {Object} profile
 * @param {Object} opts
 * @api public
 */

function cobbler(strategy, profile, opts) {
  if ('undefined' === typeof profile || null === profile) {
    throw new Error('`profile` must be an object or false');
  }

  opts = opts || {};
  var passauth = !!profile;
  var _proto = proto(strategy);
  var ovr = override(_proto);

  if ('session' === strategy) {
    ovr
      .method('authenticate', (function(user) {
        return function(req, options) {
          // user should be the object that is sent to passport.deserializeUser method
          // ex: May be a user.id for a user lookup, or can be the entire user object itself
          req._passport.session.user = user;
          cobbler.__.authenticate.call(this, req, options); // go about as original
        };
      })(profile));
  } else {
    ovr
      .method('userProfile', userProfile(profile))
      .method('authenticate', authenticate(passauth, opts));
  }

  this.restore = restore.bind(this, _proto);
  return this;
}

/*
 * backup original methods
 *
 * @param {Prototype} proto
 * @return {Object}
 * @api private
 */

function backup(proto) {
  var method = function(name) {
    cobbler.__[name] = proto[name];
    return this;
  };

  return {
    method: method
  };
}

/*
 * override method
 *
 * @param {Prototype}
 * @return {Object}
 * @api private
 */

function override(proto) {
  var method = function(name, fn) {
    if (!(name in proto)) {
      throw new Error('No method `'+name+'` to override');
    }

    backup(proto).method(name);

    proto[name] = fn;
    return this;
  };

  return {
    method: method
  };
}

/*
 * return the prototype
 *
 * @param {String|Strategy|Prototype} strategy
 * @return {Prototype}
 * @api private
 */

function proto(strategy) {
  // Session
  if ('session' === strategy) {
    return require('passport').strategies.SessionStrategy.prototype;
  }

  // Strategy
  if ('string' === typeof strategy) {      // npm name
    strategy = require(strategy).Strategy;
  } else if ('Strategy' in strategy) {     // exports
    strategy = strategy.Strategy;
  }

  // is prototype or get the prototype
  return ('userProfile' in strategy) ? strategy : strategy.prototype;
}

/*
 * restore the original methods
 *
 * @param {Prototype} proto
 * @api private
 */

function restore(proto) {
  var methods = Object.keys(cobbler.__);
  var i = 0;
  var len = methods.length;
  for(; i<len; i++) {
    var name = methods[i];
    proto[name] = cobbler.__[name];
    delete cobbler.__[name];
  }
}

/*
 * Override `authenticate`
 * https://github.com/jaredhanson/passport-oauth2/blob/master/lib/strategy.js#L118
 *
 * We call the original method to allow it to go through the normal process
 * while mocking the OAuth2 object `this._oauth2`
 *
 * @param {Boolean} passauth
 * @param {Object} opts
 * @return {Function}
 * @api private
 */

function authenticate(passauth, opts) {
  /*
   * mock oauth2 object
   *
   * - `getAuthorizeUrl` returns the authorization url
   *   eg. https://github.com/login/oauth/authorize
   *
   *   We want to avoid actually hitting the OAuth endpoint and redirect directly to
   *   the "callback url" w/ any query params corresponding to the status of the req
   *
   *  - `getOAuthAccessToken` retrieves the accessToken used to grab the user profile
   *
   *   This is one of the network calls out to the internet.
   */

  var mockoauth2 = {
    getAuthorizeUrl: function(params) {
      return params.redirect_uri+queryp(passauth);
    },
    getOAuthAccessToken: function(code, params, callback) {
      callback(); // TODO arity and ability to define values for aguments
    }
  };

  return function(req, options) {
    options.callbackURL = opts.callbackURL || options.callbackURL;

    /*
     * you can overwrite the delegator to the inherited `userProfile` method
     * however, `userProfile` is the method calling the actual `oauth2.get()`
     *
     * this._loadUserProfile = function(accessToken, done) {
     *   done(null, user);
     * };
     */

    // TODO do we need to back this up? This is instance based
    this._oauth2 = mockoauth2; // mock the oauth2 object
    cobbler.__.authenticate.call(this, req, options); // go about as original
  };
}

/*
 * Override `userProfile`
 *
 * Unlike the `authenticate` function, this is normally defined in
 * each inheritted strategy. This is other netowkr call out to the internet.
 *
 * @param {Object} profile
 * @return {Function}
 * @api private
 */

function userProfile(profile) {
  return function(accessToken, done) {
    done(null, profile);
  };
}

/*
 * add callback url query params
 *
 * @param {Boolean} passauth
 * @return {String}
 * @api private
 */

function queryp(passauth) {
  return passauth ? '?code=12345' : '?error=access_denied';
}