Source: jive-sdk-api/lib/util/oauthHandler.js

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;
};