Repository: auth0/node-auth0
Pull request #343 (2019-03-13 07:40:47)

Add support for Auth0 Grants
### Changes

- Add GrantsManager based on ClientGrantsManager.
  (https://auth0.com/docs/api/management/v2#!/Grants/get_grants)
### References

- Closes https://github.com/auth0/node-auth0/issues/341
- Closes support ticket #00423933

### Testing
- [X] This change adds unit test coverage
  Add `test/management/grants.tests.js` based on `test/management/client-grants.tests.js`

### Checklist

- [X] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)

- [X] I have read the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)

- [X] All existing and new tests complete without errors



Patch:
diff --git a/src/management/GrantsManager.js b/src/management/GrantsManager.js
new file mode 100644
index 000000000..9ccaf09a7
--- /dev/null
+++ b/src/management/GrantsManager.js
@@ -0,0 +1,119 @@
+var ArgumentError = require('rest-facade').ArgumentError;
+var utils = require('../utils');
+var Auth0RestClient = require('../Auth0RestClient');
+var RetryRestClient = require('../RetryRestClient');
+/**
+ * @class GrantsManager
+ * Auth0 Grants Manager.
+ *
+ * See {@link https://auth0.com/docs/api/v2#!/Grants Grants}
+ *
+ * @constructor
+ * @memberOf module:management
+ *
+ * @param {Object} options            The client options.
+ * @param {String} options.baseUrl    The URL of the API.
+ * @param {Object} [options.headers]  Headers to be included in all requests.
+ * @param {Object} [options.retry]    Retry Policy Config
+ */
+var GrantsManager = function(options) {
+  if (options === null || typeof options !== 'object') {
+    throw new ArgumentError('Must provide client options');
+  }
+
+  if (options.baseUrl === null || options.baseUrl === undefined) {
+    throw new ArgumentError('Must provide a base URL for the API');
+  }
+
+  if ('string' !== typeof options.baseUrl || options.baseUrl.length === 0) {
+    throw new ArgumentError('The provided base URL is invalid');
+  }
+
+  /**
+   * Options object for the Rest Client instance.
+   *
+   * @type {Object}
+   */
+  var clientOptions = {
+    errorFormatter: { message: 'message', name: 'error' },
+    headers: options.headers,
+    query: { repeatParams: false }
+  };
+
+  /**
+   * Provides an abstraction layer for consuming the
+   * {@link https://auth0.com/docs/api/v2#!/Grants Auth0 Grants endpoint}.
+   *
+   * @type {external:RestClient}
+   */
+  var auth0RestClient = new Auth0RestClient(
+    options.baseUrl + '/grants/:id',
+    clientOptions,
+    options.tokenProvider
+  );
+  this.resource = new RetryRestClient(auth0RestClient, options.retry);
+};
+
+/**
+ * Get all Auth0 Grants.
+ *
+ * @method    getAll
+ * @memberOf  module:management.GrantsManager.prototype
+ *
+ * @example
+ * var params = {
+ *   per_page: 10,
+ *   page: 0,
+ *   include_totals: true,
+ *   user_id: 'USER_ID',
+ *   client_id: 'CLIENT_ID',
+ *   audience: 'AUDIENCE'
+ * };
+ *
+ * management.getGrants(params, function (err, grants) {
+ *   console.log(grants.length);
+ * });
+ *
+ * @param   {Object}    params                Grants parameters.
+ * @param   {Number}    params.per_page       Number of results per page.
+ * @param   {Number}    params.page           Page number, zero indexed.
+ * @param   {Boolean}   params.include_totals true if a query summary must be included in the result, false otherwise. Default false;
+ * @param   {String}    params.user_id        The user_id of the grants to retrieve.
+ * @param   {String}    params.client_id      The client_id of the grants to retrieve.
+ * @param   {String}    params.audience       The audience of the grants to retrieve.
+ * @param   {Function}  [cb]                  Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(GrantsManager, 'getAll', 'resource.getAll');
+
+/**
+ * Delete an Auth0 grant.
+ *
+ * @method    delete
+ * @memberOf  module:management.GrantsManager.prototype
+ *
+ * @example
+ * var params = {
+ *    id: 'GRANT_ID',
+ *    user_id: 'USER_ID'
+ * };
+ *
+ * management.deleteGrant(params, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // Grant deleted.
+ * });
+ *
+ * @param   {Object}    params         Grant parameters.
+ * @param   {String}    params.id      Grant ID.
+ * @param   {String}    params.user_id The user_id of the grants to delete.
+ * @param   {Function}  [cb]           Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(GrantsManager, 'delete', 'resource.delete');
+
+module.exports = GrantsManager;
diff --git a/src/management/index.js b/src/management/index.js
index ee75f5964..106128d6a 100644
--- a/src/management/index.js
+++ b/src/management/index.js
@@ -11,6 +11,7 @@ var assign = Object.assign || require('object.assign');
 // Managers.
 var ClientsManager = require('./ClientsManager');
 var ClientGrantsManager = require('./ClientGrantsManager');
+var GrantsManager = require('./GrantsManager');
 var UsersManager = require('./UsersManager');
 var ConnectionsManager = require('./ConnectionsManager');
 var BlacklistedTokensManager = require('./BlacklistedTokensManager');
@@ -149,6 +150,14 @@ var ManagementClient = function(options) {
    */
   this.clientGrants = new ClientGrantsManager(managerOptions);
 
+  /**
+   * Simple abstraction for performing CRUD operations on the grants
+   * endpoint.
+   *
+   * @type {GrantsManager}
+   */
+  this.grants = new GrantsManager(managerOptions);
+
   /**
    * Simple abstraction for performing CRUD operations on the
    * users endpoint.
@@ -662,6 +671,68 @@ utils.wrapPropertyMethod(ManagementClient, 'updateClientGrant', 'clientGrants.up
  */
 utils.wrapPropertyMethod(ManagementClient, 'deleteClientGrant', 'clientGrants.delete');
 
+/**
+ * Get all Auth0 Grants.
+ *
+ * @method    getGrants
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * var params = {
+ *   per_page: 10,
+ *   page: 0,
+ *   include_totals: true,
+ *   user_id: USER_ID,
+ *   client_id: CLIENT_ID,
+ *   audience: AUDIENCE
+ * };
+ *
+ * management.getGrants(params, function (err, grants) {
+ *   console.log(grants.length);
+ * });
+ *
+ * @param   {Object}    params                Grants parameters.
+ * @param   {Number}    params.per_page       Number of results per page.
+ * @param   {Number}    params.page           Page number, zero indexed.
+ * @param   {Boolean}   params.include_totals true if a query summary must be included in the result, false otherwise. Default false;
+ * @param   {String}    params.user_id        The user_id of the grants to retrieve.
+ * @param   {String}    params.client_id      The client_id of the grants to retrieve.
+ * @param   {String}    params.audience       The audience of the grants to retrieve.
+ * @param   {Function}  [cb]                  Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getGrants', 'grants.getAll');
+
+/**
+ * Delete an Auth0 grant.
+ *
+ * @method    deleteGrant
+ * @memberOf  module:management.GrantsManager.prototype
+ *
+ * @example
+ * var params = {
+ *    id: GRANT_ID,
+ *    user_id: USER_ID
+ * };
+ *
+ * management.deleteGrant(params, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // Grant deleted.
+ * });
+ *
+ * @param   {Object}    params         Grant parameters.
+ * @param   {String}    params.id      Grant ID.
+ * @param   {String}    params.user_id The user_id of the grants to delete.
+ * @param   {Function}  [cb]           Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteGrant', 'grants.delete');
+
 /**
  * Create an Auth0 credential.
  *


Test patch:
diff --git a/test/management/grants.tests.js b/test/management/grants.tests.js
new file mode 100644
index 000000000..665a34a7f
--- /dev/null
+++ b/test/management/grants.tests.js
@@ -0,0 +1,174 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+var Promise = require('bluebird');
+
+var SRC_DIR = '../../src';
+var API_URL = 'https://tenant.auth0.com';
+
+var GrantsManager = require(SRC_DIR + '/management/GrantsManager');
+var ArgumentError = require('rest-facade').ArgumentError;
+
+describe('GrantsManager', function() {
+  before(function() {
+    this.token = 'TOKEN';
+    this.grants = new GrantsManager({
+      headers: {
+        authorization: 'Bearer ' + this.token
+      },
+      baseUrl: API_URL
+    });
+  });
+
+  afterEach(function() {
+    nock.cleanAll();
+  });
+
+  describe('instance', function() {
+    var methods = ['getAll', 'delete'];
+
+    methods.forEach(function(method) {
+      it('should have a ' + method + ' method', function() {
+        expect(this.grants[method]).to.exist.to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+  describe('#constructor', function() {
+    it('should error when no options are provided', function() {
+      expect(GrantsManager).to.throw(ArgumentError, 'Must provide client options');
+    });
+
+    it('should throw an error when no base URL is provided', function() {
+      var grants = GrantsManager.bind(null, {});
+
+      expect(grants).to.throw(ArgumentError, 'Must provide a base URL for the API');
+    });
+
+    it('should throw an error when the base URL is invalid', function() {
+      var grants = GrantsManager.bind(null, { baseUrl: '' });
+
+      expect(grants).to.throw(ArgumentError, 'The provided base URL is invalid');
+    });
+  });
+
+  describe('#getAll', function() {
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .get('/grants')
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.grants.getAll(function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.grants
+        .getAll()
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/grants')
+        .reply(500);
+
+      this.grants.getAll().catch(function(err) {
+        expect(err).to.exist;
+        done();
+      });
+    });
+
+    it('should pass the body of the response to the "then" handler', function(done) {
+      nock.cleanAll();
+
+      var data = [{ test: true }];
+      var request = nock(API_URL)
+        .get('/grants')
+        .reply(200, data);
+
+      this.grants.getAll().then(function(grants) {
+        expect(grants).to.be.an.instanceOf(Array);
+
+        expect(grants.length).to.equal(data.length);
+
+        expect(grants[0].test).to.equal(data[0].test);
+
+        done();
+      });
+    });
+
+    it('should perform a GET request to /api/v2/grants', function(done) {
+      var request = this.request;
+
+      this.grants.getAll().then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+
+    it('should include the token in the Authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/grants')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.grants.getAll().then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+
+    it('should pass the parameters in the query-string', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/grants')
+        .query({
+          include_fields: true,
+          fields: 'test'
+        })
+        .reply(200);
+
+      this.grants.getAll({ include_fields: true, fields: 'test' }).then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+  });
+
+  describe('#delete', function() {
+    var id = 5;
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .delete('/grants/' + id)
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.grants.delete({ id: id }, done.bind(null, null));
+    });
+
+    it('should return a promise when no callback is given', function(done) {
+      this.grants.delete({ id: id }).then(done.bind(null, null));
+    });
+
+    it('should perform a DELETE request to /grants/' + id, function(done) {
+      var request = this.request;
+
+      this.grants.delete({ id: id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+});
diff --git a/test/management/management-client.tests.js b/test/management/management-client.tests.js
index 86ef52613..955fb6c2c 100644
--- a/test/management/management-client.tests.js
+++ b/test/management/management-client.tests.js
@@ -8,6 +8,7 @@ var UsersManager = require('../../src/management/UsersManager');
 var BlacklistedTokensManager = require('../../src/management/BlacklistedTokensManager');
 var ClientsManager = require('../../src/management/ClientsManager');
 var ClientGrantsManager = require('../../src/management/ClientGrantsManager');
+var GrantsManager = require('../../src/management/GrantsManager');
 var ConnectionsManager = require('../../src/management/ConnectionsManager');
 var DeviceCredentialsManager = require('../../src/management/DeviceCredentialsManager');
 var EmailProviderManager = require('../../src/management/EmailProviderManager');
@@ -111,6 +112,10 @@ describe('ManagementClient', function() {
         property: 'clientGrants',
         cls: ClientGrantsManager
       },
+      GrantsManager: {
+        property: 'grants',
+        cls: GrantsManager
+      },
       ConnectionsManager: {
         property: 'connections',
         cls: ConnectionsManager
@@ -177,6 +182,8 @@ describe('ManagementClient', function() {
       'createClientGrant',
       'updateClientGrant',
       'deleteClientGrant',
+      'getGrants',
+      'deleteGrant',
       'createDevicePublicKey',
       'getDeviceCredentials',
       'deleteDeviceCredential',