/**
* @file Manages Salesforce OAuth2 operations
* @author Shinichi Tomita <shinichi.tomita@gmail.com>
*/
'use strict';
var querystring = require('querystring'),
_ = require('lodash/core'),
Transport = require('./transport');
var defaults = {
loginUrl : "https://login.salesforce.com"
};
/**
* OAuth2 class
*
* @class
* @constructor
* @param {Object} options - OAuth2 config options
* @param {String} [options.loginUrl] - Salesforce login server URL
* @param {String} [options.authzServiceUrl] - OAuth2 authorization service URL. If not specified, it generates from default by adding to login server URL.
* @param {String} [options.tokenServiceUrl] - OAuth2 token service URL. If not specified it generates from default by adding to login server URL.
* @param {String} options.clientId - OAuth2 client ID.
* @param {String} options.clientSecret - OAuth2 client secret.
* @param {String} options.redirectUri - URI to be callbacked from Salesforce OAuth2 authorization service.
*/
var OAuth2 = module.exports = function(options) {
if (options.authzServiceUrl && options.tokenServiceUrl) {
this.loginUrl = options.authzServiceUrl.split('/').slice(0, 3).join('/');
this.authzServiceUrl = options.authzServiceUrl;
this.tokenServiceUrl = options.tokenServiceUrl;
this.revokeServiceUrl = options.revokeServiceUrl;
} else {
this.loginUrl = options.loginUrl || defaults.loginUrl;
this.authzServiceUrl = this.loginUrl + "/services/oauth2/authorize";
this.tokenServiceUrl = this.loginUrl + "/services/oauth2/token";
this.revokeServiceUrl = this.loginUrl + "/services/oauth2/revoke";
}
this.clientId = options.clientId;
this.clientSecret = options.clientSecret;
this.redirectUri = options.redirectUri;
this._transport =
options.proxyUrl ? new Transport.ProxyTransport(options.proxyUrl) : new Transport();
};
/**
*
*/
_.extend(OAuth2.prototype, /** @lends OAuth2.prototype **/ {
/**
* Get Salesforce OAuth2 authorization page URL to redirect user agent.
*
* @param {Object} params - Parameters
* @param {String} params.scope - Scope values in space-separated string
* @param {String} params.state - State parameter
* @returns {String} Authorization page URL
*/
getAuthorizationUrl : function(params) {
params = _.extend({
response_type : "code",
client_id : this.clientId,
redirect_uri : this.redirectUri
}, params || {});
return this.authzServiceUrl +
(this.authzServiceUrl.indexOf('?') >= 0 ? "&" : "?") +
querystring.stringify(params);
},
/**
* @typedef TokenResponse
* @type {Object}
* @property {String} access_token
* @property {String} refresh_token
*/
/**
* OAuth2 Refresh Token Flow
*
* @param {String} refreshToken - Refresh token
* @param {Callback.<TokenResponse>} [callback] - Callback function
* @returns {Promise.<TokenResponse>}
*/
refreshToken : function(refreshToken, callback) {
return this._postParams({
grant_type : "refresh_token",
refresh_token : refreshToken,
client_id : this.clientId,
client_secret : this.clientSecret
}, callback);
},
/**
* OAuth2 Web Server Authentication Flow (Authorization Code)
* Access Token Request
*
* @param {String} code - Authorization code
* @param {Callback.<TokenResponse>} [callback] - Callback function
* @returns {Promise.<TokenResponse>}
*/
requestToken : function(code, callback) {
return this._postParams({
grant_type : "authorization_code",
code : code,
client_id : this.clientId,
client_secret : this.clientSecret,
redirect_uri : this.redirectUri
}, callback);
},
/**
* OAuth2 Username-Password Flow (Resource Owner Password Credentials)
*
* @param {String} username - Salesforce username
* @param {String} password - Salesforce password
* @param {Callback.<TokenResponse>} [callback] - Callback function
* @returns {Promise.<TokenResponse>}
*/
authenticate : function(username, password, callback) {
return this._postParams({
grant_type : "password",
username : username,
password : password,
client_id : this.clientId,
client_secret : this.clientSecret,
redirect_uri : this.redirectUri
}, callback);
},
/**
* OAuth2 Revoke Session Token
*
* @param {String} accessToken - Access token to revoke
* @param {Callback.<undefined>} [callback] - Callback function
* @returns {Promise.<undefined>}
*/
revokeToken : function(accessToken, callback) {
return this._transport.httpRequest({
method : 'POST',
url : this.revokeServiceUrl,
body: querystring.stringify({ token: accessToken }),
headers: {
"content-type" : "application/x-www-form-urlencoded"
}
}).then(function(response) {
if (response.statusCode >= 400) {
var res = querystring.parse(response.body);
if (!res || !res.error) {
res = { error: "ERROR_HTTP_"+response.statusCode, error_description: response.body };
}
var err = new Error(res.error_description);
err.name = res.error;
throw err;
}
}).thenCall(callback);
},
/**
* @private
*/
_postParams : function(params, callback) {
return this._transport.httpRequest({
method : 'POST',
url : this.tokenServiceUrl,
body : querystring.stringify(params),
headers : {
"content-type" : "application/x-www-form-urlencoded"
}
}).then(function(response) {
var res;
try {
res = JSON.parse(response.body);
} catch(e) {}
if (response.statusCode >= 400) {
res = res || { error: "ERROR_HTTP_"+response.statusCode, error_description: response.body };
var err = new Error(res.error_description);
err.name = res.error;
throw err;
}
return res;
}).thenCall(callback);
}
});