google-oauth-jwt
Version:
Implementation of Google OAuth 2.0 for server-to-server interactions, allowing secure use of Google APIs without interaction from an end-user.
111 lines (94 loc) • 3.04 kB
JavaScript
var debug = require('debug')('google-oauth-jwt');
/**
* A cache of tokens for reusing previously requested tokens until they expire.
*
* Tokens are requested by calling the `authenticate` method and cached for any combination of `options.email` and
* `options.scopes`.
*
* @constructor TokenCache
*/
function TokenCache() {
// cache is just a key/value pair
this._cache = {};
};
/**
* Retrieve an authentication token, or reuse a previously obtained one if it is not expired.
* Only one request will be performed for any combination of `options.email`, `options.scopes`
* and `options.delegationEmail`.
*
* @param options The JWT generation options.
* @callback {Function} The callback to invoke with the resulting token.
*/
TokenCache.prototype.get = function (options, callback) {
var key = options.email + ':' + options.scopes.join(',');
if (options.delegationEmail) key += ':' + options.delegationEmail;
if (!this._cache[key]) {
this._cache[key] = new TokenRequest(this.authenticate, options);
}
this._cache[key].get(callback);
};
/**
* Clear all tokens previously requested by this instance.
*/
TokenCache.prototype.clear = function () {
this._cache = {};
};
/**
* The method to use to perform authentication and retrieving a token.
* Used for overriding the authentication mechanism.
*
* @param {Object} options The JWT generation options.
* @callback {Function} callback The callback to invoke with the resulting token.
*/
TokenCache.prototype.authenticate = require('./auth').authenticate;
/**
* A single cacheable token request with support for concurrency.
* @private
* @constructor
*/
function TokenRequest(authenticate, options) {
var self = this;
this.status = 'expired';
this.pendingCallbacks = [];
// execute accumulated callbacks during the 'pending' state
function fireCallbacks(err, token) {
self.pendingCallbacks.forEach(function (callback) {
callback(err, token);
});
self.pendingCallbacks = [];
}
TokenRequest.prototype.get = function (callback) {
if (this.status == 'expired') {
this.status = 'pending';
this.pendingCallbacks.push(callback);
authenticate(options, function (err, token) {
if (err) {
self.status = 'expired';
return fireCallbacks(err, null);
}
self.issued = Date.now();
self.duration = options.expiration || 60 * 60 * 1000;
self.token = token;
self.status = 'completed';
return fireCallbacks(null, token);
});
} else if (this.status == 'pending') {
// wait for the pending request instead of issuing a new one
this.pendingCallbacks.push(callback);
} else if (this.status == 'completed') {
if (this.issued + this.duration < Date.now()) {
this.status = 'expired';
debug('token is expired, a new token will be requested');
this.get(callback);
} else {
callback(null, this.token);
}
}
};
}
/**
* The global token cache that can be used as a default instance.
* @type TokenCache
*/
TokenCache.global = new TokenCache();
module.exports = TokenCache;