var jive = require('../../../jive-sdk-service/api'); var q = require('q'); /** * An abstract class encapsulating the access token refresh flow. * <ol> * <li>Attempt an operation.</li> * <li>If the operation does not suceed due to 401, then attempt an OAuth2 access token refresh exchange.</li> * <li>If the refresh is successful, retry the operation.</li> * </ol> * Override the accessTokenRefresher member function, to handle the request logic specific to the target OAuth2 provider. * @class oauthHandler * @abstract */ /////////////////////////////////////////////////////////////////////////////////// // private /** * This is the default implementation of access token refresh. * @private * @memberof oauthHandler * @param {Object} oauth * @returns {Promise} Promise */ var accessTokenRefresher = function (oauth) { var postObject = { 'grant_type': 'refresh_token', 'refresh_token': oauth['refreshToken'], 'client_id': oauth['oauth2ConsumerKey'], 'client_secret': oauth['oauth2ConsumerSecret'] }; var headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; return jive.util.buildRequest(oauth['originServerTokenRequestUrl'], 'POST', postObject, headers); }; /////////////////////////////////////////////////////////////////////////////////// // public /** * The default implementation of access token refresh. If you * use oauth.js buildOAuthHandler, you will be required to provide * your own implementation of this function. * @memberof oauthHandler * @param oauth * @returns {} */ exports.accessTokenRefresher = function(oauth) { return accessTokenRefresher(oauth); }; exports.doRequest = function( options ) { options = options || {}; var url = options.url, headers = options.headers || {}, oauth = options.oauth, method = options.method, postBody = options.postBody, requestOptions = options.requestOptions; if( !oauth ) { jive.logger.warn("No oauth credentials found. Continuing without them."); return jive.util.buildRequest( url, method, postBody, headers, requestOptions ); } return exports.doOperation( function() { return jive.util.buildRequest( url, method, postBody, headers, requestOptions ); }, {}, oauth, true ); }; exports.doOperation = function( operation, operationContext, oauth ) { return this.handleOperation( operation, operationContext, oauth, true ); }; exports.doRefreshTokenFlow = function(operationContext, oauth ) { jive.logger.debug("Trying refresh flow"); var deferred = q.defer(); this.accessTokenRefresher(operationContext, oauth).then( // success function (operationContext) { // success jive.logger.debug('Successfully refreshed token.'); deferred.resolve(operationContext); }, // failure function (result) { jive.logger.warn("RefreshTokenFlow failed.", result || ''); deferred.reject( {statusCode: result.statusCode, error: 'Error refreshing token. Response in details field', details: result } ); } ); return deferred.promise; }; exports.handleError = function( operationContext, oauth, response, retryIfFail ) { var deferred = q.defer(); if (response.statusCode == 400) { jive.logger.info('Bad request (400)', response); deferred.reject(response); } else if (response.statusCode == 401 || response.statusCode == 403) { jive.logger.info("Unauthorized (" + response.statusCode + ")", response); if ( !retryIfFail ) { jive.logger.error('Not executing refresh flow. Failure on second attempt.', response); deferred.reject( response ); } else { this.doRefreshTokenFlow( operationContext, oauth ).then( // successful token refresh function(update) { deferred.resolve(update); }, // failed token refresh function(err) { deferred.reject( err ); } ); } } else { deferred.reject(response); } return deferred.promise; }; exports.handleOperation = function (operation, operationContext, oauth, retryIfFail) { var p = q.defer(); var self = this; operation( operationContext, oauth ).then( // successful push function (response) { p.resolve(response); }, // failed push function(response) { return self.handleError( operationContext, oauth, response, retryIfFail).then( function(updatedOAuth) { jive.logger.debug("Retrying operation."); // retry operation once if refreshtoken was reason for error self.handleOperation( operation, operationContext, updatedOAuth, false).then( function(r) { p.resolve(r); }, function(e) { p.reject(e); } ); }, function( err ) { p.reject(err); } ); } ); return p.promise; };