Skip to content

Instantly share code, notes, and snippets.

@robertpitt
Created October 15, 2015 15:01
Show Gist options
  • Save robertpitt/5f0a9286a1a52d58bb18 to your computer and use it in GitHub Desktop.
Save robertpitt/5f0a9286a1a52d58bb18 to your computer and use it in GitHub Desktop.
/**
* Access Token
*/
_.extend(AccessToken, {
/**
* Update an access token
* @param {Function} callback Callback when token has been updated
*/
refreshAccessToken : function(service, force, callback) {
/**
* Defaultize service
*/
service = service || this.getDefaultService();
/**
* Call the meteor method
*/
Meteor.call('updateAccessToken', service, !!force, callback);
},
/**
* Return an access token
* @param {String} provider Provider
* @return {String} Access Token
*/
get : function(service) {
/**
* Defaultize service
*/
service = service || this.getDefaultService();
/**
* Fetch the services
* @type {Object}
*/
var services = Meteor.user().services;
/**
* Pick the index
*/
var idx = (service || this.defaultService);
/**
* Check the service
*/
if(!(idx in services)) {
throw new Meteor.Error("access-token", "Unable to locate service (" + idx + ")");
}
/**
* Assure we have an access token to return.
*/
if(!('accessToken' in services[idx])) {
throw new Meteor.error("access-token", "Service (" + idx + ") does not contain an access token");
}
/**
* Return the access token
*/
return services[idx].accessToken;
},
/**
* Return the amount of seconds remaining for the current access token
* @param {String} userid User Id
* @param {String} service Service Name
* @return {Number} Seconds Left, 0 if it's expired.
*/
getRemainingTTL : function(service){
/**
* Defaultize service
*/
service = service || this.getDefaultService();
/**
* Fetch the authentication
* @type {Object}
*/
var services = Meteor.user().services;
/**
* Check the service
*/
if(!(service in services)) {
throw new Meteor.Error("access-token", "Unable to locate service (" + service + ")");
}
/**
* Assure we have an access token to return.
*/
if(!('expiresAt' in services[service])) {
throw new Meteor.error("access-token", "Service (" + service + ") does not contain an expiresAt value.");
}
/**
* Return the calculated value
*/
return Math.floor((services[service].expiresAt - Date.now()) / 1000);
},
/**
* Check to see if the active access token has expired
* @param {String} userid User ID
* @param {String} service Service Name
* @return {Boolean} Returns true if the token need's refreshing
*/
isExpired: function(service){
/**
* Defaultize service
*/
service = service || this.getDefaultService();
/**
* Return the current access tokens ttl
*/
return this.getRemainingTTL(service) < 1;
},
/**
* Return the access token for a service
* @param {String} service Service name were looking at
* @return {String} Token Type, such as Bearer
*/
header : function(service) {
return "Bearer " + this.get(service);
}
});
/**
* Access Token Management
*/
if ('undefined' === typeof AccessToken) {
AccessToken = {};
}
/**
* Common AccessToken methods and functions
*/
_.extend(AccessToken, {
/**
* Default service name if service in function argument is ommitted.
* @type {String}
*/
defaultService : 'centiq',
/**
* Set the default service name
* @param {String} service Service Name such as facebook, google or centiq
*/
setDefaultService: function(service){
this.defaultService = service;
},
/**
* Set the default service name
* @return {String} Default service name,.
*/
getDefaultService: function(){
return this.defaultService;
}
})
/**
* Extend AccessToken interface
*/
_.extend(AccessToken, {
/**
* [refreshAccessToken description]
* @param {String} userid User id of the user who's access token we wish to update
* @param {String} service Optional service type, defualts to this.defaultService.
* @return {Boolean} Returns the value of the Meteor update command, if any
* of the http requests or data stuff fail then an
* exception is throw using {Meteor.error}
*/
refreshAccessToken: function(userid, service, force) {
/**
* Defaultize service
*/
service = service || this.getDefaultService();
/**
* Fetch the service configuration
* @type {Object}
*/
var config = ServiceConfiguration.configurations.findOne({service: service});
/**
* make sure the service provider is configured before we do anything at all.
*/
if(!config)
throw new Meteor.Error("oauth", "Service congfiguration error, missing configuration");
/**
* Fetch the current access token data
*/
var auth = Meteor.users.findOne({_id: userid}).services[service];
/**
* If the user has never logged in before with this servce
*/
if(!auth)
throw new Meteor.Error("oauth", "Missing service data for (" + service + ") on user (" + userid + ")");
/**
* Here we check to see if the access token is being forced or there
* is enough ttl buffer for us to skip the request.
*/
if(!force && this.getRemainingTTL(userid, service) > 15) {
return false;
}
/**
* Fetch the new token
*/
var result = Meteor.http.post(config.tokenEndpoint, {
params: {
client_id : config.clientId,
client_secret: config.secret,
refresh_token : auth.refreshToken,
grant_type: 'refresh_token'
}
});
if(result.statusCode !== 200) {
/**
* Here we can potentially expect there to be a long term issue, either the following:
*
* 1. The refresh token has been revoked, therefore it's useless
* 2. The client details have changed, such as the secret has been regenerated due
* to it being leaked etc.
* 3. The client no longer exists
*
* @note the above 2 options will only apply if the status code is within the range of 400-499,
* anything above can be treated as an external service provider issue, such as maintenance.
*/
throw new Meteor.Error("oauth", "Unable to update access token for " + userid + "#" + service);
}
var o = {};
o['services.'+service+'.accessToken'] = result.data.access_token;
o['services.'+service+'.expiresAt'] = Date.now() + (result.data.expires_in * 1000);
/**
* Check to see if token rotation s enabled
*/
if('refresh_token' in result.data) {
o['services.'+service+'.refreshToken'] = result.data.refresh_token;
}
/**
* Update the user objects
*/
return !!Meteor.users.update(userid, {$set: o});
},
/**
* Return teh current active access token for a particular user
* @param {String} userid User ID
* @param {String} service Service name
* @return {String} Access token for the user's service
*/
get : function(userid, service) {
/**
* Defaultize service
*/
service = service || this.getDefaultService();
/**
* Fetch the current access token data
* @todo Error checking.
*/
var services = Meteor.users.findOne({_id: userid}).services;
/**
* Check the service
*/
if(!(service in services)) {
throw new Meteor.Error("access-token", "Unable to locate service (" + service + ")");
}
/**
* Assure we have an access token to return.
*/
if(!('accessToken' in services[service])) {
throw new Meteor.error("access-token", "Service (" + service + ") does not contain an access token.");
}
/**
* Return the access token
*/
return services[service].accessToken;
},
/**
* Return the amount of seconds remaining for the current access token
* @param {String} userid User Id
* @param {String} service Service Name
* @return {Number} Seconds Left, 0 if it's expired.
*/
getRemainingTTL : function(userid, service){
/**
* Defaultize service
*/
service = service || this.getDefaultService();
/**
* Fetch the current access token data
* @todo Error checking.
*/
var services = Meteor.users.findOne({_id: userid}).services;
/**
* Check the service
*/
if(!(service in services)) {
throw new Meteor.Error("access-token", "Unable to locate service (" + service + ")");
}
/**
* Assure we have an access token to return.
*/
if(!('expiresAt' in services[service])) {
throw new Meteor.error("access-token", "Service (" + service + ") does not contain an expiresAt value.");
}
/**
* Return the calculated value
*/
return Math.floor((services[service].expiresAt - Date.now()) / 1000);
},
/**
* Check to see if the active access token has expired
* @param {String} userid User ID
* @param {String} service Service Name
* @return {Boolean} Returns true if the token need's refreshing
*/
isExpired: function(userid, service){
/**
* Defaultize service
*/
service = service || this.getDefaultService();
/**
* Return the current acce's tokens ttl
*/
return this.getRemainingTTL(userid, service) < 1;
},
/**
* Return the access token for a service
* @param {String} service Service name were looking at
* @return {String} Token Type, such as Bearer
*/
header : function(userid, service) {
return "Bearer " + this.get(userid, service);
}
});
/**
* Register the updateAccessToken
*/
Meteor.methods({
/**
* Trigger an access token update via a method scoped to the current user
* @param {String} service Service provider name *optional*
* @param {Boolean} force Force the update of the access token even if it still
* has a large enough ttl left.
* @return {Boolean} True if it was updated
*/
updateAccessToken: function(service, force) {
return AccessToken.refreshAccessToken(this.userId, service, !!force);
}
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment