Created
October 15, 2015 15:01
-
-
Save robertpitt/5f0a9286a1a52d58bb18 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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; | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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); | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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