diff --git a/README.md b/README.md
index 81d732d37..7a404fea6 100644
--- a/README.md
+++ b/README.md
@@ -31,7 +31,6 @@ var auth0 = new AuthenticationClient({
## Management API Client
The Auth0 Management API is meant to be used by back-end servers or trusted parties performing administrative tasks. Generally speaking, anything that can be done through the Auth0 dashboard (and more) can also be done through this API.
-
Initialize your client class with an API v2 token and a domain.
```js
@@ -43,7 +42,7 @@ var management = new ManagementClient({
});
```
-> When using at browser you should use `telemetry: false`.
+> Note: When using at browser you should use `telemetry: false`.
To obtain a Management API token from your node backend, you can use Client Credentials Grant using your registered Auth0 Non Interactive Clients
@@ -71,6 +70,24 @@ auth0.clientCredentialsGrant({
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.
+~~~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
+ })
+ });
+~~~
+
+> Note: When using at browser you should use `telemetry: false`.
+
## 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.
diff --git a/package.json b/package.json
index 82523b0ef..9d81232c5 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,8 @@
"homepage": "https://github.com/auth0/node-auth0",
"dependencies": {
"bluebird": "^2.10.2",
+ "lru-memoizer": "^1.11.1",
+ "object.assign": "^4.0.4",
"request": "^2.81.0",
"rest-facade": "^1.5.0"
},
diff --git a/src/Auth0RestClient.js b/src/Auth0RestClient.js
new file mode 100644
index 000000000..0b29e8e06
--- /dev/null
+++ b/src/Auth0RestClient.js
@@ -0,0 +1,71 @@
+var RestClient = require('rest-facade').Client;
+var Promise = require('bluebird');
+var ArgumentError = require('rest-facade').ArgumentError;
+
+var Auth0RestClient = function (resourceUrl, options, provider) {
+ if (resourceUrl === null || resourceUrl === undefined) {
+ throw new ArgumentError('Must provide a Resource Url');
+ }
+
+ if ('string' !== typeof resourceUrl || resourceUrl.length === 0) {
+ throw new ArgumentError('The provided Resource Url is invalid');
+ }
+
+ if (options === null || typeof options !== 'object') {
+ throw new ArgumentError('Must provide options');
+ }
+
+ this.options = options;
+ this.provider = provider;
+ this.restClient = new RestClient(resourceUrl, options);
+
+ this.wrappedProvider = function (method, args) {
+ if (!this.provider) {
+ return this.restClient[method].apply(this.restClient, args);
+ }
+
+ var callback;
+ if(args && args[args.length -1] instanceof Function){
+ callback = args[args.length -1];
+ }
+
+ var self = this;
+ return this.provider.getAccessToken()
+ .then(function (access_token) {
+ self.options.headers['Authorization'] = 'Bearer ' + access_token;
+ return self.restClient[method].apply(self.restClient, args);
+ }).catch(function(err){
+ if(callback){
+ return callback(err);
+ }
+ return Promise.reject(err);
+ });
+ }
+};
+
+Auth0RestClient.prototype.getAll = function ( /* [params], [callback] */ ) {
+ return this.wrappedProvider('getAll', arguments);
+};
+
+
+Auth0RestClient.prototype.get = function ( /* [params], [callback] */ ) {
+ return this.wrappedProvider('get', arguments);
+}
+
+Auth0RestClient.prototype.create = function ( /* [params], [callback] */ ) {
+ return this.wrappedProvider('create', arguments);
+}
+
+Auth0RestClient.prototype.patch = function ( /* [params], [callback] */ ) {
+ return this.wrappedProvider('patch', arguments);
+}
+
+Auth0RestClient.prototype.update = function ( /* [params], [callback] */ ) {
+ return this.wrappedProvider('update', arguments);
+}
+
+Auth0RestClient.prototype.delete = function ( /* [params], [callback] */ ) {
+ return this.wrappedProvider('delete', arguments);
+}
+
+module.exports = Auth0RestClient;
diff --git a/src/index.js b/src/index.js
index b36054859..c5032cd50 100644
--- a/src/index.js
+++ b/src/index.js
@@ -6,5 +6,6 @@
module.exports = {
ManagementClient: require('./management'),
- AuthenticationClient: require('./auth')
+ AuthenticationClient: require('./auth'),
+ ManagementTokenProvider: require('./management/ManagementTokenProvider')
};
diff --git a/src/management/BlacklistedTokensManager.js b/src/management/BlacklistedTokensManager.js
index 83d8d4a68..f6e03f147 100644
--- a/src/management/BlacklistedTokensManager.js
+++ b/src/management/BlacklistedTokensManager.js
@@ -1,7 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* @class BlacklistedTokensManager
@@ -44,7 +43,7 @@ var BlacklistedTokensManager = function (options) {
*
* @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/blacklists/tokens', clientOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/blacklists/tokens', clientOptions, options.tokenProvider);
};
diff --git a/src/management/ClientGrantsManager.js b/src/management/ClientGrantsManager.js
index 9f74e1882..87db12bc7 100644
--- a/src/management/ClientGrantsManager.js
+++ b/src/management/ClientGrantsManager.js
@@ -1,7 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* @class ClientGrantsManager
@@ -46,7 +45,7 @@ var ClientGrantsManager = function (options) {
*
* @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/client-grants/:id', clientOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/client-grants/:id', clientOptions, options.tokenProvider);
};
diff --git a/src/management/ClientsManager.js b/src/management/ClientsManager.js
index f8c9642f3..8444d3fe1 100644
--- a/src/management/ClientsManager.js
+++ b/src/management/ClientsManager.js
@@ -1,7 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* @class ClientsManager
@@ -49,7 +48,7 @@ var ClientsManager = function (options) {
*
* @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/clients/:client_id', clientOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/clients/:client_id', clientOptions, options.tokenProvider);
};
diff --git a/src/management/ConnectionsManager.js b/src/management/ConnectionsManager.js
index fc69f04f2..8f4ac579c 100644
--- a/src/management/ConnectionsManager.js
+++ b/src/management/ConnectionsManager.js
@@ -1,7 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* @class ConnectionsManager
@@ -43,7 +42,7 @@ var ConnectionsManager = function (options) {
*
* @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/connections/:id ', apiOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/connections/:id ', apiOptions, options.tokenProvider);
};
diff --git a/src/management/DeviceCredentialsManager.js b/src/management/DeviceCredentialsManager.js
index aae9025d2..6d46491a4 100644
--- a/src/management/DeviceCredentialsManager.js
+++ b/src/management/DeviceCredentialsManager.js
@@ -1,7 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* Simple facade for consuming a REST API endpoint.
@@ -49,9 +48,9 @@ var DeviceCredentialsManager = function (options) {
* {@link https://auth0.com/docs/api/v2#!/Device_Credentials
* Auth0 DeviceCredentialsManagers endpoint}.
*
- * @type {external:RestDeviceCredentialsManager}
+ * @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/device-credentials/:id', clientOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/device-credentials/:id', clientOptions, options.tokenProvider);
};
diff --git a/src/management/EmailProviderManager.js b/src/management/EmailProviderManager.js
index 33ade9a0c..2e672a7c9 100644
--- a/src/management/EmailProviderManager.js
+++ b/src/management/EmailProviderManager.js
@@ -1,7 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* Simple facade for consuming a REST API endpoint.
@@ -50,7 +49,7 @@ var EmailProviderManager = function (options) {
*
* @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/emails/provider', clientOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/emails/provider', clientOptions, options.tokenProvider);
};
diff --git a/src/management/JobsManager.js b/src/management/JobsManager.js
index 53490fe2a..ea79adc79 100644
--- a/src/management/JobsManager.js
+++ b/src/management/JobsManager.js
@@ -3,9 +3,8 @@ var extend = require('util')._extend;
var Promise = require('bluebird');
var fs = require('fs');
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* Simple facade for consuming a REST API endpoint.
@@ -51,7 +50,7 @@ var JobsManager = function (options){
*
* @type {external:RestClient}
*/
- this.jobs = new RestClient(options.baseUrl + '/jobs/:id', clientOptions);
+ this.jobs = new Auth0RestClient(options.baseUrl + '/jobs/:id', clientOptions, options.tokenProvider);
};
diff --git a/src/management/LogsManager.js b/src/management/LogsManager.js
index 0070f4b3e..332dbbdf0 100644
--- a/src/management/LogsManager.js
+++ b/src/management/LogsManager.js
@@ -1,7 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* @class LogsManager
@@ -43,7 +42,7 @@ var LogsManager = function (options) {
*
* @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/logs/:id ', apiOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/logs/:id ', apiOptions, options.tokenProvider);
};
/**
diff --git a/src/management/ManagementTokenProvider.js b/src/management/ManagementTokenProvider.js
new file mode 100644
index 000000000..c0638c6fb
--- /dev/null
+++ b/src/management/ManagementTokenProvider.js
@@ -0,0 +1,121 @@
+var ArgumentError = require('rest-facade').ArgumentError;
+var assign = Object.assign || require('object.assign');
+var AuthenticationClient = require('../auth');
+var memoizer = require('lru-memoizer');
+var Promise = require('bluebird');
+
+var BASE_URL_FORMAT = 'https://%s';
+var DEFAULT_OPTIONS = { useCache : 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
+ * Initialize a Management Token Provider class.
+ *
+ *
+ * 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'
+ * });
+ */
+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.clientId || params.clientId.length === 0) {
+ throw new ArgumentError('Must provide a Client Id');
+ }
+
+ if (!params.clientSecret || params.clientSecret.length === 0) {
+ throw new ArgumentError('Must provide a Client Secret');
+ }
+
+ if(typeof params.useCache !== 'boolean'){
+ throw new ArgumentError('The useCache must be a boolean');
+ }
+
+ this.options = params;
+
+ this.authenticationClient = new AuthenticationClient({
+ domain: params.domain,
+ clientId: params.clientId,
+ clientSecret: params.clientSecret,
+ telemetry: params.telemetry
+ });
+}
+
+/**
+ * Returns the access_token.
+ *
+ * @method getAccessToken
+ * @memberOf module:management.ManagementTokenProvider.prototype
+ *
+ * @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)
+ .then(function (data) {
+ return data.access_token
+ });
+ }else{
+ return this.clientCredentialsGrant(this.options.domain, this.options.scope)
+ .then(function (data) {
+ return data.access_token
+ });
+ }
+}
+
+ManagementTokenProvider.prototype.getCachedAccessToken = Promise.promisify(
+ memoizer({
+ load: function (domain, clientId, scope, callback) {
+ this.clientCredentialsGrant(domain, scope)
+ .then(function (data) {
+ callback(null, data);
+ })
+ .catch(function (err) {
+ callback(err);
+ });
+ },
+ hash: function (domain, clientId, scope) {
+ return domain + '-' + clientId + '-' + scope;
+ },
+ itemMaxAge: function (domain, clientid, scope, data) {
+ // 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 3600 * 1000 // 1h;
+ },
+ max: 100,
+ maxAge: 1000 * 60
+ })
+);
+
+ManagementTokenProvider.prototype.clientCredentialsGrant = function (domain, scope) {
+ return this.authenticationClient.clientCredentialsGrant({
+ audience: 'https://' + domain + '/api/v2/',
+ scope: scope
+ });
+};
+
+module.exports = ManagementTokenProvider;
diff --git a/src/management/ResourceServersManager.js b/src/management/ResourceServersManager.js
index 178bbb632..44de35ddc 100644
--- a/src/management/ResourceServersManager.js
+++ b/src/management/ResourceServersManager.js
@@ -1,6 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
+var Auth0RestClient = require('../Auth0RestClient');
/**
* @class ResourceServersManager
@@ -48,7 +48,7 @@ var ResourceServersManager = function (options) {
*
* @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/resource-servers/:id', apiOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/resource-servers/:id', apiOptions, options.tokenProvider);
};
/**
diff --git a/src/management/RulesManager.js b/src/management/RulesManager.js
index 45a2d7052..7073db00e 100644
--- a/src/management/RulesManager.js
+++ b/src/management/RulesManager.js
@@ -1,7 +1,6 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
var utils = require('../utils');
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* Simple facade for consuming a REST API endpoint.
@@ -50,7 +49,7 @@ var RulesManager = function (options) {
*
* @type {external:RestClient}
*/
- this.resource = new RestClient(options.baseUrl + '/rules/:id ', apiOptions);
+ this.resource = new Auth0RestClient(options.baseUrl + '/rules/:id ', apiOptions, options.tokenProvider);
};
diff --git a/src/management/StatsManager.js b/src/management/StatsManager.js
index a7aaae0de..067a0972a 100644
--- a/src/management/StatsManager.js
+++ b/src/management/StatsManager.js
@@ -1,6 +1,5 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* Simple facade for consuming a REST API endpoint.
@@ -44,7 +43,7 @@ var StatsManager = function (options){
*
* @type {external:RestClient}
*/
- this.stats = new RestClient(options.baseUrl + '/stats/:type', clientOptions);
+ this.stats = new Auth0RestClient(options.baseUrl + '/stats/:type', clientOptions, options.tokenProvider);
};
diff --git a/src/management/TenantManager.js b/src/management/TenantManager.js
index 6a78c4edf..41429bd05 100644
--- a/src/management/TenantManager.js
+++ b/src/management/TenantManager.js
@@ -1,5 +1,5 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
+var Auth0RestClient = require('../Auth0RestClient');
/**
@@ -44,7 +44,7 @@ var TenantManager = function (options){
*
* @type {external:RestClient}
*/
- this.tenant = new RestClient(options.baseUrl + '/tenants/settings', clientOptions);
+ this.tenant = new Auth0RestClient(options.baseUrl + '/tenants/settings', clientOptions, options.tokenProvider);
};
/**
diff --git a/src/management/TicketsManager.js b/src/management/TicketsManager.js
index 12bb42b4b..5061e73ff 100644
--- a/src/management/TicketsManager.js
+++ b/src/management/TicketsManager.js
@@ -1,5 +1,5 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
+var Auth0RestClient = require('../Auth0RestClient');
/**
@@ -33,7 +33,7 @@ var TicketsManager = function (options){
*
* @type {external:RestClient}
*/
- this.ticket = new RestClient(options.baseUrl + '/tickets/:type', clientOptions);
+ this.ticket = new Auth0RestClient(options.baseUrl + '/tickets/:type', clientOptions, options.tokenProvider);
};
diff --git a/src/management/UsersManager.js b/src/management/UsersManager.js
index a6d1c3d23..4f1ed8bf0 100644
--- a/src/management/UsersManager.js
+++ b/src/management/UsersManager.js
@@ -1,6 +1,5 @@
-var RestClient = require('rest-facade').Client;
var ArgumentError = require('rest-facade').ArgumentError;
-
+var Auth0RestClient = require('../Auth0RestClient');
/**
* Simple facade for consuming a REST API endpoint.
@@ -37,8 +36,8 @@ var UsersManager = function (options){
headers: options.headers,
query: { repeatParams: false }
};
-
- this.users = new RestClient(options.baseUrl + '/users/:id', clientOptions);
+
+ this.users = new Auth0RestClient(options.baseUrl + '/users/:id', clientOptions, options.tokenProvider);
/**
* Provides an abstraction layer for consuming the
@@ -47,28 +46,28 @@ var UsersManager = function (options){
*
* @type {external:RestClient}
*/
- this.multifactor = new RestClient(options.baseUrl + '/users/:id/multifactor/:provider', clientOptions);
+ this.multifactor = new Auth0RestClient(options.baseUrl + '/users/:id/multifactor/:provider', clientOptions, options.tokenProvider);
/**
* Provides a simple abstraction layer for linking user accounts.
*
* @type {external:RestClient}
*/
- this.identities = new RestClient(options.baseUrl + '/users/:id/identities/:provider/:user_id', clientOptions);
+ this.identities = new Auth0RestClient(options.baseUrl + '/users/:id/identities/:provider/:user_id', clientOptions, options.tokenProvider);
/**
* Provides a simple abstraction layer for user logs
*
* @type {external:RestClient}
*/
- this.userLogs = new RestClient(options.baseUrl + '/users/:id/logs', clientOptions);
+ this.userLogs = new Auth0RestClient(options.baseUrl + '/users/:id/logs', clientOptions, options.tokenProvider);
/**
* Provides an abstraction layer for retrieving Guardian enrollments.
*
* @type {external:RestClient}
*/
- this.enrollments = new RestClient(options.baseUrl + '/users/:id/enrollments', clientOptions);
+ this.enrollments = new Auth0RestClient(options.baseUrl + '/users/:id/enrollments', clientOptions, options.tokenProvider);
};
diff --git a/src/management/index.js b/src/management/index.js
index 16eabf460..9b67c5aed 100644
--- a/src/management/index.js
+++ b/src/management/index.js
@@ -22,6 +22,7 @@ var JobsManager = require('./JobsManager');
var TicketsManager = require('./TicketsManager');
var LogsManager = require('./LogsManager');
var ResourceServersManager = require('./ResourceServersManager');
+var ManagementTokenProvider = require('./ManagementTokenProvider');
var BASE_URL_FORMAT = 'https://%s/api/v2';
@@ -47,33 +48,62 @@ var BASE_URL_FORMAT = 'https://%s/api/v2';
* token: '{YOUR_API_V2_TOKEN}',
* domain: '{YOUR_ACCOUNT}.auth0.com'
* });
+ *
+ *
+ * @example
+ * Initialize your client class with the Management Token Provider.
+ *
+ *
+ * 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'
+ * })
+ * });
*
- * @param {Object} options Options for the ManagementClient SDK.
- * @param {String} options.token API access token.
- * @param {String} [options.domain] ManagementClient server domain.
+ * @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.
+ *
*/
var ManagementClient = function (options) {
if (!options || typeof options !== 'object') {
throw new ArgumentError('Management API SDK options must be an object');
}
- if (!options.token || options.token.length === 0) {
- throw new ArgumentError('An access token must be provided');
+ if (!options.domain || options.domain.length === 0) {
+ throw new ArgumentError('Must provide a Domain');
}
- 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;
}
var managerOptions = {
headers: {
- 'Authorization': 'Bearer ' + options.token,
'User-agent': 'node.js/' + process.version.replace('v', ''),
'Content-Type': 'application/json'
},
- baseUrl: util.format(BASE_URL_FORMAT, options.domain)
+ baseUrl: util.format(BASE_URL_FORMAT, options.domain),
+ tokenProvider: this.tokenProvider
};
+ if (options.token && options.token.length !== 0) {
+ managerOptions.headers['Authorization'] = 'Bearer ' + options.token;
+ }
+
if (options.telemetry !== false) {
var telemetry = jsonToBase64(options.clientInfo || this.getClientInfo());
diff --git a/test/auth0-rest-client.tests.js b/test/auth0-rest-client.tests.js
new file mode 100644
index 000000000..f65d62532
--- /dev/null
+++ b/test/auth0-rest-client.tests.js
@@ -0,0 +1,119 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+
+var ArgumentError = require('rest-facade').ArgumentError;
+var ManagementTokenProvider = require('../src/management/ManagementTokenProvider')
+var Auth0RestClient = require('../src/Auth0RestClient');
+
+var API_URL = 'https://tenant.auth0.com';
+
+describe('Auth0RestClient', function () {
+ before(function () {
+ this.providerMock = {
+ getAccessToken: function () {
+ return Promise.resolve('access_token');
+ }
+ }
+ });
+
+ it('should raise an error when no resource Url is provided', function () {
+ expect(Auth0RestClient)
+ .to.throw(ArgumentError, 'Must provide a Resource Url');
+ });
+
+ it('should raise an error when resource Url is invalid', function () {
+ var client = Auth0RestClient.bind(null, '');
+ expect(client)
+ .to.throw(ArgumentError, 'The provided Resource Url is invalid');
+ });
+
+ it('should raise an error when no options is provided', function () {
+ var client = Auth0RestClient.bind(null, '/some-resource');
+ expect(client)
+ .to.throw(ArgumentError, 'Must provide options');
+ });
+
+ it('should accept a callback', function (done) {
+ nock(API_URL).get('/some-resource')
+ .reply(200, { data: 'value' });
+
+ var options = {
+ headers: {}
+ }
+ var client = new Auth0RestClient(API_URL + '/some-resource', options, this.providerMock);
+ client.getAll(function (err, data) {
+ expect(data).to.deep.equal({ data: 'value' });
+ done();
+ nock.cleanAll()
+ });
+ });
+
+ it('should return a promise if no callback is given', function (done) {
+ nock(API_URL).get('/some-resource')
+ .reply(200, { data: 'value' });
+
+ var options = {
+ headers: {}
+ }
+
+ var client = new Auth0RestClient(API_URL + '/some-resource', options, this.providerMock);
+ client.getAll().then(function(data){
+ expect(data).to.deep.equal({ data: 'value' });
+ done();
+ nock.cleanAll()
+ });
+ });
+
+ it('should accept a callback and handle errors', function (done) {
+ var providerMock = {
+ getAccessToken: function () {
+ return Promise.reject(new Error('Some Error'));
+ }
+ }
+
+ nock(API_URL).get('/some-resource')
+ .reply(500);
+
+ var options = {
+ headers: {}
+ }
+ var client = new Auth0RestClient(API_URL + '/some-resource', options, providerMock);
+ client.getAll(function (err, data) {
+ expect(err).to.not.null;
+ expect(err.message).to.be.equal('Some Error');
+ done();
+ nock.cleanAll();
+ });
+ });
+
+ it('should set access token as Authorization header in options object', function (done) {
+ nock(API_URL).get('/some-resource')
+ .reply(200);
+
+ var options = {
+ headers: {}
+ }
+
+ var client = new Auth0RestClient(API_URL + '/some-resource', options, this.providerMock);
+ client.getAll().then(function(data){
+ expect(client.options.headers['Authorization']).to.be.equal('Bearer access_token');
+ done();
+ nock.cleanAll();
+ });
+ });
+
+ it('should catch error when provider.getAccessToken throws an error', function (done) {
+ var providerMock = {
+ getAccessToken: function () {
+ return Promise.reject(new Error('Some Error'));
+ }
+ }
+
+ var client = new Auth0RestClient('/some-resource', {}, providerMock);
+ client.getAll().catch(function (err) {
+ expect(err).to.not.null;
+ expect(err.message).to.be.equal('Some Error');
+ done();
+ })
+ });
+});
diff --git a/test/auth0.tests.js b/test/auth0.tests.js
index bfd8fbb30..a07f03cf1 100644
--- a/test/auth0.tests.js
+++ b/test/auth0.tests.js
@@ -3,6 +3,7 @@ var expect = require('chai').expect;
var auth0 = require('../src');
var AuthenticationClient = require('../src/auth');
var ManagementClient = require('../src/management');
+var ManagementTokenProvider = require('../src/management/ManagementTokenProvider')
describe('Auth0 module', function () {
@@ -18,4 +19,9 @@ describe('Auth0 module', function () {
.to.equal(ManagementClient);
});
+
+ it('should expose the ManagementTokenProvider', function () {
+ expect(auth0.ManagementTokenProvider)
+ .to.equal(ManagementTokenProvider);
+ });
});
diff --git a/test/management/management-client.tests.js b/test/management/management-client.tests.js
index 168eb6bab..3c6686ed7 100644
--- a/test/management/management-client.tests.js
+++ b/test/management/management-client.tests.js
@@ -23,23 +23,51 @@ describe('ManagementClient', function () {
.to.throw(ArgumentError, 'Management API SDK options must be an object');
});
-
it('should raise an error when the token is not valid', function () {
var options = { token: '', domain: 'tenant.auth.com' };
var client = ManagementClient.bind(null, options);
expect(client)
- .to.throw(ArgumentError, 'An access token must be provided');
+ .to.throw(ArgumentError, 'Must provide a Token');
});
-
it('should raise an error when the domain is not valid', function () {
- var client = ManagementClient.bind(null, { token: 'token', domain: '' });
+ var client = ManagementClient.bind(null, { token: 'token' });
+
+ expect(client)
+ .to.throw(ArgumentError, 'Must provide a Domain');
+ });
+
+ it('should raise an error when the token provider does not have a function getAccessToken', function () {
+ var client = ManagementClient.bind(null, { tokenProvider : {} });
+
+ expect(client)
+ .to.throw(ArgumentError, 'Must provide a Domain');
+ });
+
+ it('should raise an error when the domain is not valid and a tokenProvider is specified', function () {
+ var client = ManagementClient.bind(null, { domain: 'domain', tokenProvider: {} });
+
+ expect(client)
+ .to.throw(ArgumentError, 'The tokenProvider does not have a function getAccessToken');
+ });
+
+ it('should raise an error when the token provider does have a property getAccessToken that is not a function', function () {
+ var client = ManagementClient.bind(null, { domain: 'domain', tokenProvider : { getAccessToken: [] } });
expect(client)
- .to.throw(ArgumentError, 'Must provide a domain');
+ .to.throw(ArgumentError, 'The tokenProvider does not have a function getAccessToken');
});
+ it('should set the tokenProvider instance property if provider is passed', function () {
+ var fakeTokenProvider = { getAccessToken: function(){} };
+ var options = { domain: 'domain', tokenProvider : fakeTokenProvider };
+ var client = new ManagementClient(options);
+
+ expect(client.tokenProvider)
+ .to.exist
+ .to.be.equal(fakeTokenProvider);
+ });
describe('instance properties', function () {
var manager;
diff --git a/test/management/management-token-provider.tests.js b/test/management/management-token-provider.tests.js
new file mode 100644
index 000000000..a12c4f7a4
--- /dev/null
+++ b/test/management/management-token-provider.tests.js
@@ -0,0 +1,288 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+var assign = Object.assign || require('object.assign');
+var ArgumentError = require('rest-facade').ArgumentError;
+var APIError = require('rest-facade').APIError;
+
+var ManagementTokenProvider = require('../../src/management/ManagementTokenProvider');
+
+describe('ManagementTokenProvider', function () {
+ var defaultConfig = { clientId: 'clientId', clientSecret: 'clientSecret', 'domain': 'auth0-node-sdk.auth0.com' };
+
+ it('should expose an instance of ManagementTokenProvider', function () {
+ expect(new ManagementTokenProvider(defaultConfig))
+ .to.exist
+ .to.be.an.instanceOf(ManagementTokenProvider);
+ });
+
+ it('should raise an error when no options object is provided', function () {
+ expect(ManagementTokenProvider)
+ .to.throw(ArgumentError, 'Options must be an object');
+ });
+
+ it('should raise an error when the domain is not set', function () {
+ var provider = ManagementTokenProvider.bind(null, { clientId: 'clientId', clientSecret: 'clientSecret' });
+
+ expect(provider)
+ .to.throw(ArgumentError, 'Must provide a domain');
+ });
+
+ it('should raise an error when the domain is not valid', function () {
+ var provider = ManagementTokenProvider.bind(null, { clientId: 'clientId', clientSecret: 'clientSecret', 'domain': '' });
+
+ expect(provider)
+ .to.throw(ArgumentError, 'Must provide a domain');
+ });
+
+ it('should raise an error when the clientId is not set', function () {
+ var provider = ManagementTokenProvider.bind(null, { clientSecret: 'clientSecret', domain: 'domain' });
+
+ expect(provider)
+ .to.throw(ArgumentError, 'Must provide a Client Id');
+ });
+
+ it('should raise an error when the clientId is not valid', function () {
+ var provider = ManagementTokenProvider.bind(null, { clientId: '', clientSecret: 'clientSecret', 'domain': 'domain' });
+
+ expect(provider)
+ .to.throw(ArgumentError, 'Must provide a Client Id');
+ });
+
+ it('should raise an error when the clientSecret is not set', function () {
+ var provider = ManagementTokenProvider.bind(null, { clientId: 'clientId' , domain: 'domain' });
+
+ expect(provider)
+ .to.throw(ArgumentError, 'Must provide a Client Secret');
+ });
+
+ it('should raise an error when the clientSecret is not valid', function () {
+ var provider = ManagementTokenProvider.bind(null, { clientId: 'clientId', clientSecret: '', 'domain': 'domain' });
+
+ expect(provider)
+ .to.throw(ArgumentError, 'Must provide a Client Secret');
+ });
+
+ it('should raise an error when the useCache is not of type boolean', function () {
+ var provider = ManagementTokenProvider.bind(null, { clientId: 'clientId', clientSecret: 'clientSecret', 'domain': 'domain', 'useCache': 'false' });
+
+ expect(provider)
+ .to.throw(ArgumentError, 'The useCache must be a boolean');
+ });
+
+ it('should set useCache to true when not specified', function () {
+ var provider = new ManagementTokenProvider({ clientId: 'clientId', clientSecret: 'clientSecret', 'domain': 'domain' });
+ expect(provider.options.useCache).to.be.true;
+ });
+
+ it('should set useCache to true when passed as true', function () {
+ var provider = new ManagementTokenProvider({ clientId: 'clientId', clientSecret: 'clientSecret', 'domain': 'domain', 'useCache': true });
+ expect(provider.options.useCache).to.be.true;
+ });
+
+ it('should set useCache to false when passed as false', function () {
+ var provider = new ManagementTokenProvider({ clientId: 'clientId', clientSecret: 'clientSecret', 'domain': 'domain', 'useCache': false });
+ expect(provider.options.useCache).to.be.false;
+ });
+
+ it('should handle network errors correctly', function (done) {
+ var config = assign({}, defaultConfig);
+ config.domain = 'domain';
+ var client = new ManagementTokenProvider(config);
+
+ nock('https://' + config.domain)
+ .post('/oauth/token')
+ .reply(401);
+
+ client.getAccessToken()
+ .catch(function(err){
+ expect(err).to.exist
+ done();
+ });
+ });
+
+ it('should handle unauthorized errors correctly', function (done) {
+ var client = new ManagementTokenProvider(defaultConfig);
+
+ nock('https://' + defaultConfig.domain)
+ .post('/oauth/token')
+ .reply(401);
+
+ client.getAccessToken()
+ .catch(function(err){
+ expect(err)
+ .to.exist
+ .to.be.an.instanceOf(APIError);
+ expect(err.statusCode).to.be.equal(401);
+ done();
+ nock.cleanAll();
+ });
+ });
+
+ it('should return access token', function (done) {
+ var config = assign({}, defaultConfig);
+ config.domain = 'auth0-node-sdk-1.auth0.com'
+ var client = new ManagementTokenProvider(config);
+
+ nock('https://' + config.domain)
+ .post('/oauth/token')
+ .reply(200, {
+ access_token: 'token',
+ expires_in: 3600
+ })
+
+ client.getAccessToken()
+ .then(function(access_token){
+ expect(access_token).to.exist;
+ expect(access_token).to.be.equal('token');
+ done();
+ nock.cleanAll();
+ });
+ });
+
+ it('should contain correct body payload', function (done) {
+ var config = assign({}, defaultConfig);
+ config.domain = 'auth0-node-sdk-2.auth0.com'
+ var client = new ManagementTokenProvider(config);
+
+ nock('https://' + config.domain)
+ .post('/oauth/token',function(body) {
+
+ expect(body.client_id).to.equal('clientId');
+ expect(body.client_secret).to.equal('clientSecret');
+ expect(body.grant_type).to.equal('client_credentials');
+ expect(body.audience).to.equal('https://auth0-node-sdk-2.auth0.com/api/v2/');
+ return true;
+ })
+ .reply(function(uri, requestBody, cb) {
+ return cb(null, [200, { access_token: 'token', expires_in: 3600 }]);
+ });
+
+ client.getAccessToken()
+ .then(function(data){
+
+ done();
+ nock.cleanAll();
+ });
+ });
+
+ it('should return access token from the cache the second call', function (done) {
+ var config = assign({}, defaultConfig);
+ config.domain = 'auth0-node-sdk-3.auth0.com'
+ var client = new ManagementTokenProvider(config);
+
+ nock('https://' + config.domain)
+ .post('/oauth/token')
+ .once()
+ .reply(200, {
+ access_token: 'access_token',
+ expires_in: 3600
+ });
+
+ client.getAccessToken()
+ .then(function(access_token){
+ expect(access_token).to.exist;
+ expect(access_token).to.be.equal('access_token');
+
+ client.getAccessToken()
+ .then(function(access_token){
+ expect(access_token).to.exist;
+ expect(access_token).to.be.equal('access_token');
+ done();
+ nock.cleanAll();
+ });
+ });
+ });
+
+ it('should request new access token when cache is expired', function (done) {
+ var config = assign({}, defaultConfig);
+ config.domain = 'auth0-node-sdk-4.auth0.com'
+ var client = new ManagementTokenProvider(config);
+
+ nock('https://' + config.domain)
+ .post('/oauth/token')
+ .reply(200, {
+ access_token: 'access_token',
+ expires_in: 1 / 40 // 1sec / 40 = 25ms
+ })
+ .post('/oauth/token')
+ .reply(200, {
+ access_token: 'new_access_token',
+ expires_in: 3600
+ })
+
+ client.getAccessToken()
+ .then(function(access_token){
+ expect(access_token).to.exist;
+ expect(access_token).to.be.equal('access_token');
+
+ setTimeout(function() {
+ client.getAccessToken()
+ .then(function(access_token){
+ expect(access_token).to.exist;
+ expect(access_token).to.be.equal('new_access_token');
+ done();
+ nock.cleanAll();
+ });
+ }, 40); // 40ms
+ });
+ });
+
+ it('should return new access token on the second call when cache is disabled', function (done) {
+ var config = assign({ useCache: false }, defaultConfig);
+ config.domain = 'auth0-node-sdk-3.auth0.com'
+ var client = new ManagementTokenProvider(config);
+
+ nock('https://' + config.domain)
+ .post('/oauth/token')
+ .reply(200, {
+ access_token: 'access_token',
+ expires_in: 3600
+ })
+ .post('/oauth/token')
+ .reply(200, {
+ access_token: 'new_access_token',
+ expires_in: 3600
+ })
+
+ client.getAccessToken()
+ .then(function(access_token){
+ expect(access_token).to.exist;
+ expect(access_token).to.be.equal('access_token');
+
+ client.getAccessToken()
+ .then(function(access_token){
+ expect(access_token).to.exist;
+ expect(access_token).to.be.equal('new_access_token');
+ done();
+ nock.cleanAll();
+ }).catch(function(err){
+ expect.fail();
+ done();
+ nock.cleanAll();
+ });
+ });
+ });
+
+ it('should pass the correct payload in the body of the oauth/token request', function (done) {
+ var config = assign({ scope: 'read:foo read:bar' }, defaultConfig);
+ var client = new ManagementTokenProvider(config);
+
+ nock('https://' + config.domain)
+ .post('/oauth/token', function(payload){
+ expect(payload).to.exist;
+ expect(payload.scope).to.be.equal('read:foo read:bar');
+ expect(payload.client_id).to.be.equal('clientId');
+ expect(payload.client_secret).to.be.equal('clientSecret');
+ expect(payload.grant_type).to.be.equal('client_credentials');
+ expect(payload.audience).to.be.equal('https://auth0-node-sdk.auth0.com/api/v2/');
+ return true;
+ })
+ .reply(200);
+
+ client.getAccessToken()
+ .then(function(access_token){
+ done();
+ nock.cleanAll();
+ });
+ });
+});