passport-channeladvisor
Version:
Channel Advisor authentication strategy for Passport.
275 lines (225 loc) • 8.84 kB
JavaScript
/**
* Module dependencies.
*/
var util = require('util')
, passport = require('passport-oauth2')
, url = require('url')
, OAuth2 = require('oauth').OAuth2 ;
function ChannelAdvisor(options, verify) {
options = options || {};
if (!options.client_id) { throw new TypeError('OAuth2Strategy requires a client_id option'); }
if (!options.client_secret) { throw new TypeError('OAuth2Strategy requires a client_secret option'); }
options.apiURL = options.apiURL || 'https://api.channeladvisor.com';
options.authorizationPath = options.authorizationPath || '/oauth2/authorize';
options.identityPath = options.identityPath || '/oauth2/identity';
options.identityURL = options.apiURL + options.identityPath;
options.authorizationURL = options.apiURL + options.authorizationPath;
options.tokenPath = options.tokenPath || '/oauth2/token';
options.tokenURL = options.apiURL + options.tokenPath;
options.response_type = options.response_type || 'code';
options.scope = options.scope || ['orders', 'inventory'];
options.clientID = options.client_id;
options.grant_type = options.grant_type || 'authorization_code'; //'refresh_token', 'soap'
options.clientSecret = options.client_secret;
options.access_type = options.access_type || 'offline';
options.callbackURL = options.redirect_uri || 'https://localhost:8443/callback';
options.scopeSeparator = options.scopeSeparator || ' ';
options.customHeaders = options.customHeaders || {};
if (!options.customHeaders['User-Agent']) {
options.customHeaders['User-Agent'] = options.userAgent || 'passport-channeladvisor';
}
passport.Strategy.call(this, options, verify);
this._verify = verify;
this.name = 'channeladvisor';
this._oauth2 = new OAuth2(options.clientID, options.clientSecret,
options.apiURL, options.authorizationPath , options.tokenPath, options.customHeaders);
this._oauth2.useAuthorizationHeaderforGET(true);
this._identityURL = options.identityURL;
this._refresh_token = options.refresh_token;
this._approval_prompt = options.approval_prompt;
this._access_type = options.access_type;
this._passReqToCallback = options.passReqToCallback;
this._skipUserProfile = (options.skipUserProfile === undefined) ? false : options.skipUserProfile;
}
util.inherits(ChannelAdvisor, passport.Strategy);
ChannelAdvisor.prototype.authenticate = function(req, options) {
options = options || {};
var self = this;
var getToken = function(code, params) {
params = params || {};
self._oauth2.getOAuthAccessToken(code, params,
function(err, accessToken, refreshToken, returnParams) {
if (err) {
return self.error(self._createOAuthError('Failed to obtain access token', err));
}
self._loadIdentities(accessToken, function (err, identity) {
if (err) {
return self.error(err);
}
function verified(err, user, info) {
if (err) { return self.error(err); }
if (!user) {
return self.fail(info);
}
self.success(user, info);
}
try {
if (self._passReqToCallback) {
var arity = self._verify.length;
if (arity == 6) {
self._verify(req, accessToken, refreshToken, returnParams, identity, verified);
} else { // arity == 5
self._verify(req, accessToken, refreshToken, identity, verified);
}
} else {
var arity = self._verify.length;
if (arity == 5) {
self._verify(accessToken, refreshToken, returnParams, identity, verified);
} else { // arity == 4
self._verify(accessToken, refreshToken, identity, verified);
}
}
} catch (ex) {
return self.error(ex);
}
});
});
};
if (req.query && req.query.error) {
if (req.query.error == 'access_denied') {
return this.fail({ message: req.query.error_description });
} else {
return this.error(new AuthorizationError(req.query.error_description, req.query.error, req.query.error_uri));
}
}
var callbackURL = options.callbackURL || this._callbackURL;
var code;
if (req.query && req.query.code) {
code = req.query.code;
if (this._state) {
if (!req.session) { return this.error(new Error('OAuth2Strategy requires session support when using state. Did you forget app.use(express.session(...))?')); }
var key = this._key;
if (!req.session[key]) {
return this.fail({ message: 'Unable to verify authorization request state.' }, 403);
}
var state = req.session[key].state;
if (!state) {
return this.fail({ message: 'Unable to verify authorization request state.' }, 403);
}
delete req.session[key].state;
if (Object.keys(req.session[key]).length === 0) {
delete req.session[key];
}
if (state !== req.query.state) {
return this.fail({ message: 'Invalid authorization request state.' }, 403);
}
}
options.grant_type = 'authorization_code';
options.redirect_uri = callbackURL;
var params = this.tokenParams(options);
getToken(code, params);
} else if(this._refresh_token) {
var params = this.refreshParams(options);
getToken(this._refresh_token, params);
}
else{
options.redirect_uri = callbackURL;
options.response_type = 'code';
var params = this.authorizationParams(options);
var scope = options.scope || this._scope;
if (scope) {
if (Array.isArray(scope)) { scope = scope.join(this._scopeSeparator); }
params.scope = scope;
}
var state = options.state;
if (state) {
params.state = state;
} else if (this._state) {
if (!req.session) { return this.error(new Error('OAuth2Strategy requires session support when using state. Did you forget app.use(express.session(...))?')); }
var key = this._key;
state = uid(24);
if (!req.session[key]) { req.session[key] = {}; }
req.session[key].state = state;
params.state = state;
}
var location = this._oauth2.getAuthorizeUrl(params);
this.redirect(location);
}
};
/**
* Return extra parameters to be included in the authorization request.
*
* Some OAuth 2.0 providers allow additional, non-standard parameters to be
* included when requesting authorization. Since these parameters are not
* standardized by the OAuth 2.0 specification, OAuth 2.0-based authentication
* strategies can overrride this function in order to populate these parameters
* as required by the provider.
*
* @param {Object} options
* @return {Object}
* @api protected
*/
ChannelAdvisor.prototype.authorizationParams = function(options) {
options = options || {};
options.approval_prompt = this._approval_prompt;
options.access_type = this._access_type;
return options;
};
ChannelAdvisor.prototype.refreshParams = function(options) {
options = options || {};
options.grant_type = 'refresh_token';
options.access_type = this._access_type;
return options;
};
ChannelAdvisor.prototype.tokenParams = function(options) {
return options || {};
};
/**
* Retrieve user profile from service provider.
*
* OAuth 2.0-based authentication strategies can overrride this function in
* order to load the user's profile from the service provider. This assists
* applications (and users of those applications) in the initial registration
* process by automatically submitting required information.
*
* @param {String} accessToken
* @param {Function} done
* @api protected
*/
ChannelAdvisor.prototype.userProfile = function(accessToken, done) {
this._oauth2.getProtectedResource(this._identityURL, accessToken , function(err, data, resp){
done(err, data);
});
};
/**
* Load user profile, contingent upon options.
*
* @param {String} accessToken
* @param {Function} done
* @api private
*/
ChannelAdvisor.prototype._loadIdentities = function(accessToken, done) {
var self = this;
function loadIt() {
return self.userProfile(accessToken, done);
}
function skipIt() {
return done(null);
}
if (typeof this._skipUserProfile == 'function' && this._skipUserProfile.length > 1) {
// async
this._skipUserProfile(accessToken, function(err, skip) {
if (err) { return done(err); }
if (!skip) { return loadIt(); }
return skipIt();
});
} else {
var skip = (typeof this._skipUserProfile == 'function') ? this._skipUserProfile() : this._skipUserProfile;
if (!skip) { return loadIt(); }
return skipIt();
}
};
/**
* Expose `Strategy`.
*/
module.exports = ChannelAdvisor;