Skip to content

Commit

Permalink
applied feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
dctoon committed Sep 25, 2017
1 parent ad047dc commit 8c813cf
Show file tree
Hide file tree
Showing 7 changed files with 401 additions and 175 deletions.
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,26 +68,27 @@ auth0.clientCredentialsGrant({

> Make sure your ClientId is allowed to request tokens from Management API in [Auth0 Dashboard](https://manage.auth0.com/#/apis)
Also you can request a token when the user authenticates using any of our client side SDKs, e.g. [auth0.js](https://github.com/auth0/auth0.js).

Or initialize your client class with the ManagementTokenProvider.
To obtain **automatically** a Management API token via the ManagementClient, you can specify the parameters `clientId`, `clientSecret` (use a Non Interactive Client), `audience` (identifier of the Auth0 Management API) and optionally `scope`.
Behind the scenes the Client Credentials Grant is used to obtain the `access_token` and is by default cached for the duration of the returned `expires_in` value.
~~~js
var ManagementClient = require('auth0').ManagementClient;
var ManagementTokenProvider = require('auth0').ManagementTokenProvider;
var auth0 = new ManagementClient({
domain: '{YOUR_ACCOUNT}.auth0.com',
tokenProvider: new ManagementTokenProvider({
clientId: '{YOUR_NON_INTERACTIVE_CLIENT_ID}',
clientSecret: '{YOUR_NON_INTERACTIVE_CLIENT_SECRET}',
domain: '{YOUR_ACCOUNT}.auth0.com',
scope: '{MANAGEMENT_API_SCOPES}',
useCache: true //default
})
});
clientId: '{YOUR_NON_INTERACTIVE_CLIENT_ID}',
clientSecret: '{YOUR_NON_INTERACTIVE_CLIENT_SECRET}',
scope: "read:users write:users",
audience: 'https://{YOUR_TENANT_NAME}.auth0.com/api/v2/',
tokenProvider: {
enableCache: true, // default value
cacheTTLInSeconds: 10 // By default the `expires_in` value will be used to determine the cached time of the token
}
});
~~~

> Note: When using at browser you should use `telemetry: false`.
Also you can request a token when the user authenticates using any of our client side SDKs, e.g. [auth0.js](https://github.com/auth0/auth0.js).

## Promises and callbacks

Be aware that all methods can be used with promises or callbacks. However, when a callback is provided no promise will be returned.
Expand Down
3 changes: 1 addition & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,5 @@

module.exports = {
ManagementClient: require('./management'),
AuthenticationClient: require('./auth'),
ManagementTokenProvider: require('./management/ManagementTokenProvider')
AuthenticationClient: require('./auth')
};
104 changes: 60 additions & 44 deletions src/management/ManagementTokenProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,59 +4,73 @@ var AuthenticationClient = require('../auth');
var memoizer = require('lru-memoizer');
var Promise = require('bluebird');

var BASE_URL_FORMAT = 'https://%s';
var DEFAULT_OPTIONS = { useCache : true };
var DEFAULT_OPTIONS = { enableCache: true };

/**
* @class ManagementTokenProvider
* Auth0 Management API Token Provider.
* @constructor
* @memberOf module:management
*
* @param {Object} options Options for the ManagementTokenProvider.
* @param {String} options.domain ManagementClient server domain.
* @param {String} options.clientId Non Interactive Client Id.
* @param {String} options.clientSecret Non Interactive Client Secret.
* @param {String} [options.useCache] Enable caching (default true)
* @param {String} [options.scope] Scope
* @example <caption>
* Initialize a Management Token Provider class.
* </caption>
*
* var ManagementTokenProvider = require('auth0').ManagementTokenProvider;
* var provider = new ManagementTokenProvider({
* clientId: '{YOUR_NON_INTERACTIVE_CLIENT_ID}',
* clientSecret: '{YOUR_NON_INTERACTIVE_CLIENT_SECRET}',
* domain: '{YOUR_ACCOUNT}.auth0.com'
* });
* @param {Object} options Options for the ManagementTokenProvider.
* @param {String} options.domain ManagementClient server domain.
* @param {String} options.clientId Non Interactive Client Id.
* @param {String} options.clientSecret Non Interactive Client Secret.
* @param {String} options.audience Audience of the Management API.
* @param {String} options.scope Non Interactive Client Scope.
* @param {Boolean} [options.enableCache=true] Enabled or Disable Cache
* @param {Number} [options.cacheTTLInSeconds] By default the `expires_in` value will be used to determine the cached time of the token, this can be overridden.
*/
var ManagementTokenProvider = function (options) {
if (!options || typeof options !== 'object') {
throw new ArgumentError('Options must be an object');
}

var params = assign({}, DEFAULT_OPTIONS, options);

if (!params.domain || params.domain.length === 0) {
throw new ArgumentError('Must provide a domain');
}

if (!params.clientId || params.clientId.length === 0) {
throw new ArgumentError('Must provide a Client Id');
throw new ArgumentError('Must provide a clientId');
}

if (!params.clientSecret || params.clientSecret.length === 0) {
throw new ArgumentError('Must provide a Client Secret');
throw new ArgumentError('Must provide a clientSecret');
}

if(typeof params.useCache !== 'boolean'){
throw new ArgumentError('The useCache must be a boolean');
if (!params.audience || params.audience.length === 0) {
throw new ArgumentError('Must provide a audience');
}

if(typeof params.enableCache !== 'boolean'){
throw new ArgumentError('enableCache must be a boolean');
}

this.options = params;
if(params.enableCache && params.cacheTTLInSeconds){
if(typeof params.cacheTTLInSeconds !== 'number'){
throw new ArgumentError('cacheTTLInSeconds must be a number');
}

if(params.cacheTTLInSeconds <= 0) {
throw new ArgumentError('cacheTTLInSeconds must be a greater than 0');
}
}

this.authenticationClient = new AuthenticationClient({
domain: params.domain,
clientId: params.clientId,
clientSecret: params.clientSecret,
telemetry: params.telemetry
});
if(params.scope && typeof params.scope !== 'string'){
throw new ArgumentError('scope must be a string');
}

this.options = params;
var authenticationClientOptions = {
domain: this.options.domain,
clientId: this.options.clientId,
clientSecret: this.options.clientSecret,
telemetry: this.options.telemetry
};
//console.log('authenticationClientOptions', authenticationClientOptions);
this.authenticationClient = new AuthenticationClient(authenticationClientOptions);
}

/**
Expand All @@ -68,14 +82,13 @@ var ManagementTokenProvider = function (options) {
* @return {Promise} Promise returning an access_token.
*/
ManagementTokenProvider.prototype.getAccessToken = function () {

if(this.options.useCache){
return this.getCachedAccessToken(this.options.domain, this.options.clientId, this.options.scope)
if(this.options.enableCache){
return this.getCachedAccessToken(this.options)
.then(function (data) {
return data.access_token
});
}else{
return this.clientCredentialsGrant(this.options.domain, this.options.scope)
return this.clientCredentialsGrant(this.options.domain, this.options.scope, this.options.audience)
.then(function (data) {
return data.access_token
});
Expand All @@ -84,36 +97,39 @@ ManagementTokenProvider.prototype.getAccessToken = function () {

ManagementTokenProvider.prototype.getCachedAccessToken = Promise.promisify(
memoizer({
load: function (domain, clientId, scope, callback) {
this.clientCredentialsGrant(domain, scope)
load: function (options, callback) {
this.clientCredentialsGrant(options.domain, options.scope, options.audience)
.then(function (data) {
callback(null, data);
})
.catch(function (err) {
callback(err);
});
},
hash: function (domain, clientId, scope) {
return domain + '-' + clientId + '-' + scope;
hash: function (options) {
return options.domain + '-' + options.clientId + '-' + options.scope;
},
itemMaxAge: function (domain, clientid, scope, data) {
itemMaxAge: function (options, data) {
if(options.cacheTTLInSeconds){
return options.cacheTTLInSeconds * 1000;
}

// if the expires_in is lower than 10 seconds, do not subtract 10 additional seconds.
if (data.expires_in && data.expires_in < 10 /* seconds */){
return data.expires_in * 1000;
}else if(data.expires_in){
// Subtract 10 seconds from expires_in to fetch a new one, before it expires.
return data.expires_in * 1000 - 10000/* milliseconds */;
return data.expires_in * 1000 - 10000 /* milliseconds */;
}
return 3600 * 1000 // 1h;
return 60 * 60 * 1000; //1h
},
max: 100,
maxAge: 1000 * 60
max: 100
})
);

ManagementTokenProvider.prototype.clientCredentialsGrant = function (domain, scope) {
ManagementTokenProvider.prototype.clientCredentialsGrant = function (domain, scope, audience) {
return this.authenticationClient.clientCredentialsGrant({
audience: 'https://' + domain + '/api/v2/',
audience: audience,
scope: scope
});
};
Expand Down
71 changes: 41 additions & 30 deletions src/management/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ var pkg = require('../../package.json');
var utils = require('../utils');
var jsonToBase64 = utils.jsonToBase64;
var ArgumentError = require('rest-facade').ArgumentError;
var assign = Object.assign || require('object.assign');

// Managers.
var ClientsManager = require('./ClientsManager');
Expand Down Expand Up @@ -45,30 +46,40 @@ var BASE_URL_FORMAT = 'https://%s/api/v2';
*
* var ManagementClient = require('auth0').ManagementClient;
* var auth0 = new ManagementClient({
* token: '{YOUR_API_V2_TOKEN}',
* domain: '{YOUR_ACCOUNT}.auth0.com'
* domain: '{YOUR_ACCOUNT}.auth0.com',
* token: '{YOUR_API_V2_TOKEN}'
* });
*
*
* @example <caption>
* Initialize your client class with the Management Token Provider.
* Initialize your client class, by using a Non Interactive Client to fetch an access_token
* via the Client Credentials Grant.
* </caption>
*
* var ManagementClient = require('auth0').ManagementClient;
* var ManagementTokenProvider = require('auth0').ManagementTokenProvider;
* var auth0 = new ManagementClient({
* tokenProvider: new ManagementTokenProvider({
* clientId: '{YOUR_NON_INTERACTIVE_CLIENT_ID}',
* clientSecret: '{YOUR_NON_INTERACTIVE_CLIENT_SECRET}',
* domain: '{YOUR_ACCOUNT}.auth0.com'
* })
* domain: '{YOUR_ACCOUNT}.auth0.com',
* clientId: '{YOUR_NON_INTERACTIVE_CLIENT_ID}',
* clientSecret: '{YOUR_NON_INTERACTIVE_CLIENT_SECRET}',
* scope: "read:users write:users",
* audience: 'https://{YOUR_TENANT_NAME}.auth0.com/api/v2/',
* tokenProvider: {
* enableCache: true,
* cacheTTLInSeconds: 10
* }
* });
*
* @param {Object} options Options for the ManagementClient SDK.
* Required properties depend on the way initialization is performed as you can see in the examples.
* @param {String} [options.token] API access token.
* @param {String} [options.domain] ManagementClient server domain.
* @param {String} [options.tokenProvider] Token Provider.
* @param {Object} options Options for the ManagementClient SDK.
* If a token is provided only the domain is required, other parameters are ignored.
* If no token is provided domain, clientId, clientSecret and audience are required
* @param {String} options.domain ManagementClient server domain.
* @param {String} [options.token] API access token.
* @param {String} [options.clientId] Management API Non Interactive Client Id.
* @param {String} [options.clientSecret] Management API Non Interactive Client Secret.
* @param {String} [options.audience] Management API Audience.
* @param {String} [options.scope] Management API Scopes.
* @param {String} [options.tokenProvider.enableCache=true] Enabled or Disable Cache.
* @param {Number} [options.cacheTTLInSeconds] By default the `expires_in` value will be used to determine the cached time of the token, this can be overridden.
*
*/
var ManagementClient = function (options) {
Expand All @@ -77,36 +88,36 @@ var ManagementClient = function (options) {
}

if (!options.domain || options.domain.length === 0) {
throw new ArgumentError('Must provide a Domain');
}

if(!options.tokenProvider){
if (!options.token || options.token.length === 0) {
throw new ArgumentError('Must provide a Token');
}
}else{
if(!options.tokenProvider.getAccessToken || typeof options.tokenProvider.getAccessToken !== 'function'){
throw new ArgumentError('The tokenProvider does not have a function getAccessToken');
}
this.tokenProvider = options.tokenProvider;
throw new ArgumentError('Must provide a domain');
}

var managerOptions = {
headers: {
'User-agent': 'node.js/' + process.version.replace('v', ''),
'Content-Type': 'application/json'
},
baseUrl: util.format(BASE_URL_FORMAT, options.domain),
tokenProvider: this.tokenProvider
baseUrl: util.format(BASE_URL_FORMAT, options.domain)
};

if (options.token && options.token.length !== 0) {
if(options.token === undefined){
var config = assign({}, options);

if(options.tokenProvider){
config.enableCache = options.tokenProvider.enableCache;
config.cacheTTLInSeconds = options.tokenProvider.cacheTTLInSeconds;
delete config.tokenProvider;
}

this.tokenProvider = new ManagementTokenProvider(config);
managerOptions.tokenProvider = this.tokenProvider;
}else if(typeof options.token !== 'string' || options.token.length === 0){
throw new ArgumentError('Must provide a token');
}else{
managerOptions.headers['Authorization'] = 'Bearer ' + options.token;
}

if (options.telemetry !== false) {
var telemetry = jsonToBase64(options.clientInfo || this.getClientInfo());

managerOptions.headers['Auth0-Client'] = telemetry;
}

Expand Down
6 changes: 0 additions & 6 deletions test/auth0.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,4 @@ describe('Auth0 module', function () {
expect(auth0.ManagementClient)
.to.equal(ManagementClient);
});


it('should expose the ManagementTokenProvider', function () {
expect(auth0.ManagementTokenProvider)
.to.equal(ManagementTokenProvider);
});
});
Loading

0 comments on commit 8c813cf

Please sign in to comment.