Repository: auth0/node-auth0
Pull request #310 (2018-11-04 11:33:01)

Custom domains support
Add support for custom domains

- [x] Add tests


Patch:
diff --git a/src/management/CustomDomainsManager.js b/src/management/CustomDomainsManager.js
new file mode 100644
index 000000000..fea099afc
--- /dev/null
+++ b/src/management/CustomDomainsManager.js
@@ -0,0 +1,190 @@
+var ArgumentError = require('rest-facade').ArgumentError;
+var utils = require('../utils');
+var Auth0RestClient = require('../Auth0RestClient');
+var RetryRestClient = require('../RetryRestClient');
+
+/**
+ * @class CustomDomainsManager
+ * Auth0 Custom Domains Manager.
+ *
+ * {@link https://auth0.com/docs/api/management/v2#!/Custom_Domains/get_custom_domains CustomDomains} represent
+ * custom domain names.
+ * You can learn more about this in the
+ * {@link https://auth0.com/docs/custom-domains CustomDomains} section of the
+ * documentation.
+ * @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 CustomDomainsManager = function(options) {
+  if (options === null || typeof options !== 'object') {
+    throw new ArgumentError('Must provide manager 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#!/Custom_Domains Auth0 Custom Domains endpoint}.
+   *
+   * @type {external:RestClient}
+   */
+  var auth0CustomDomainsRestClient = new Auth0RestClient(
+    options.baseUrl + '/custom-domains/:id',
+    clientOptions,
+    options.tokenProvider
+  );
+  this.resource = new RetryRestClient(auth0CustomDomainsRestClient, options.retry);
+
+  /**
+   * Provides an abstraction layer for consuming the
+   * {@link https://auth0.com/docs/api/v2#!/Custom_Domains Auth0 Custom Domains Verify endpoint}.
+   *
+   * @type {external:RestClient}
+   */
+  var auth0VerifyRestClient = new Auth0RestClient(
+    options.baseUrl + '/custom-domains/:id/verify',
+    clientOptions,
+    options.tokenProvider
+  );
+  this.vefifyResource = new RetryRestClient(auth0VerifyRestClient, options.retry);
+};
+
+/**
+ * Create an Auth0 Custom Domain.
+ *
+ * @method    create
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.customDomains.create(data, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // CustomDomain created.
+ * });
+ *
+ * @param   {Object}    data     The custom domain data object.
+ * @param   {Function}  [cb]     Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(CustomDomainsManager, 'create', 'resource.create');
+
+/**
+ * Get all Auth0 CustomDomains.
+ *
+ * @method    getAll
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.customDomains.getAll(function (err, customDomains) {
+ *   console.log(customDomains.length);
+ * });
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(CustomDomainsManager, 'getAll', 'resource.getAll');
+
+/**
+ * Get a Custom Domain.
+ *
+ * @method    get
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.customDomains.get({ id: CUSTOM_DOMAIN_ID }, function (err, customDomain) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(customDomain);
+ * });
+ *
+ * @param   {Object}    params            Custom Domain parameters.
+ * @param   {String}    params.id         Custom Domain ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(CustomDomainsManager, 'get', 'resource.get');
+
+/**
+ * Verify a Custom Domain.
+ *
+ * @method    verify
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.customDomains.verify({ id: CUSTOM_DOMAIN_ID }, function (err, customDomain) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(customDomain);
+ * });
+ *
+ * @param   {Object}    params            Custom Domain parameters.
+ * @param   {String}    params.id         Custom Domain ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+CustomDomainsManager.prototype.verify = function(params, cb) {
+  if (!params || !params.id) {
+    throw new ArgumentError('The custom domain id cannot be null or undefined');
+  }
+
+  if (cb && cb instanceof Function) {
+    return this.vefifyResource.create(params, {}, cb);
+  }
+
+  return this.vefifyResource.create(params, {});
+};
+
+/**
+ * Delete a Custom Domain.
+ *
+ * @method    delete
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.customDomains.delete({ id: CUSTOM_DOMAIN_ID }, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // CustomDomain deleted.
+ * });
+ *
+ * @param   {Object}    params            Custom Domain parameters.
+ * @param   {String}    params.id         Custom Domain ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(CustomDomainsManager, 'delete', 'resource.delete');
+
+module.exports = CustomDomainsManager;
diff --git a/src/management/index.js b/src/management/index.js
index 598c66a85..b9bce9cf0 100644
--- a/src/management/index.js
+++ b/src/management/index.js
@@ -27,6 +27,7 @@ var ManagementTokenProvider = require('./ManagementTokenProvider');
 var RulesConfigsManager = require('./RulesConfigsManager');
 var EmailTemplatesManager = require('./EmailTemplatesManager');
 var GuardianManager = require('./GuardianManager');
+var CustomDomainsManager = require('./CustomDomainsManager');
 
 var BASE_URL_FORMAT = 'https://%s/api/v2';
 var MANAGEMENT_API_AUD_FORMAT = 'https://%s/api/v2/';
@@ -164,6 +165,14 @@ var ManagementClient = function(options) {
    */
   this.guardian = new GuardianManager(managerOptions);
 
+  /**
+   * Simple abstraction for performing CRUD operations on the
+   * custom domains endpoint.
+   *
+   * @type {CustomDomainsManager}
+   */
+  this.customDomains = new CustomDomainsManager(managerOptions);
+
   /**
    * Simple abstraction for performing CRUD operations on the
    * connections endpoint.
@@ -1929,6 +1938,112 @@ utils.wrapPropertyMethod(ManagementClient, 'getRulesConfigs', 'rulesConfigs.getA
  */
 utils.wrapPropertyMethod(ManagementClient, 'deleteRulesConfig', 'rulesConfigs.delete');
 
+/**
+ * Create an Auth0 Custom Domain.
+ *
+ * @method    create
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.createCustomDomain(data, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // CustomDomain created.
+ * });
+ *
+ * @param   {Object}    data     The custom domain data object.
+ * @param   {Function}  [cb]     Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'createCustomDomain', 'customDomains.create');
+
+/**
+ * Get all Auth0 CustomDomains.
+ *
+ * @method    getAll
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.getCustomDomains(function (err, customDomains) {
+ *   console.log(customDomains.length);
+ * });
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getCustomDomains', 'customDomains.getAll');
+
+/**
+ * Get a Custom Domain.
+ *
+ * @method    get
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.getCustomDomain({ id: CUSTOM_DOMAIN_ID }, function (err, customDomain) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(customDomain);
+ * });
+ *
+ * @param   {Object}    params            Custom Domain parameters.
+ * @param   {String}    params.id         Custom Domain ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'getCustomDomain', 'customDomains.get');
+
+/**
+ * Verify a Custom Domain.
+ *
+ * @method    verify
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.verifyCustomDomain({ id: CUSTOM_DOMAIN_ID }, function (err, customDomain) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   console.log(customDomain);
+ * });
+ *
+ * @param   {Object}    params            Custom Domain parameters.
+ * @param   {String}    params.id         Custom Domain ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'verifyCustomDomain', 'customDomains.verify');
+
+/**
+ * Delete a Custom Domain.
+ *
+ * @method    delete
+ * @memberOf  module:management.CustomDomainsManager.prototype
+ *
+ * @example
+ * management.deleteCustomDomain({ id: CUSTOM_DOMAIN_ID }, function (err) {
+ *   if (err) {
+ *     // Handle error.
+ *   }
+ *
+ *   // CustomDomain deleted.
+ * });
+ *
+ * @param   {Object}    params            Custom Domain parameters.
+ * @param   {String}    params.id         Custom Domain ID.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(ManagementClient, 'deleteCustomDomain', 'customDomains.delete');
+
 /**
  * Create a Guardian enrollment ticket.
  *


Test patch:
diff --git a/test/management/custom-domains.tests.js b/test/management/custom-domains.tests.js
new file mode 100644
index 000000000..1afcc5433
--- /dev/null
+++ b/test/management/custom-domains.tests.js
@@ -0,0 +1,398 @@
+var expect = require('chai').expect;
+var nock = require('nock');
+
+var SRC_DIR = '../../src';
+var API_URL = 'https://tenant.auth0.com';
+
+var CustomDomainsManager = require(SRC_DIR + '/management/CustomDomainsManager');
+var ArgumentError = require('rest-facade').ArgumentError;
+
+describe('CustomDomainsManager', function() {
+  before(function() {
+    this.token = 'TOKEN';
+    this.customDomains = new CustomDomainsManager({
+      headers: { authorization: 'Bearer ' + this.token },
+      baseUrl: API_URL
+    });
+  });
+
+  describe('instance', function() {
+    var methods = ['get', 'getAll', 'create', 'delete', 'verify'];
+
+    methods.forEach(function(method) {
+      it('should have a ' + method + ' method', function() {
+        expect(this.customDomains[method]).to.exist.to.be.an.instanceOf(Function);
+      });
+    });
+  });
+
+  describe('#constructor', function() {
+    it('should error when no options are provided', function() {
+      expect(CustomDomainsManager).to.throw(ArgumentError, 'Must provide manager options');
+    });
+
+    it('should throw an error when no base URL is provided', function() {
+      var client = CustomDomainsManager.bind(null, {});
+
+      expect(client).to.throw(ArgumentError, 'Must provide a base URL for the API');
+    });
+
+    it('should throw an error when the base URL is invalid', function() {
+      var client = CustomDomainsManager.bind(null, { baseUrl: '' });
+
+      expect(client).to.throw(ArgumentError, 'The provided base URL is invalid');
+    });
+  });
+
+  describe('#getAll', function() {
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .get('/custom-domains')
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.customDomains.getAll(function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.customDomains
+        .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('/custom-domains')
+        .reply(500);
+
+      this.customDomains.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 = [{ custom_domain_id: 'cd_0000000000000001' }];
+      var request = nock(API_URL)
+        .get('/custom-domains')
+        .reply(200, data);
+
+      this.customDomains.getAll().then(function(customDomains) {
+        expect(customDomains).to.be.an.instanceOf(Array);
+
+        expect(customDomains.length).to.equal(data.length);
+
+        expect(customDomains[0].test).to.equal(data[0].test);
+
+        done();
+      });
+    });
+
+    it('should perform a GET request to /api/v2/custom-domains', function(done) {
+      var request = this.request;
+
+      this.customDomains.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('/custom-domains')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.customDomains.getAll().then(function() {
+        expect(request.isDone()).to.be.true;
+        done();
+      });
+    });
+  });
+
+  describe('#get', function() {
+    beforeEach(function() {
+      this.data = [
+        {
+          custom_domain_id: 'cd_0000000000000001',
+          domain: 'login.mycompany.com',
+          primary: false,
+          status: 'ready',
+          type: 'self_managed_certs',
+          origin_domain_name: 'mycompany_cd_0000000000000001.edge.tenants.auth0.com',
+          verification: {
+            methods: ['object']
+          }
+        }
+      ];
+
+      this.request = nock(API_URL)
+        .get('/custom-domains/' + this.data[0].custom_domain_id)
+        .reply(200, this.data);
+    });
+
+    it('should accept a callback', function(done) {
+      var params = { id: this.data[0].custom_domain_id };
+
+      this.customDomains.get(params, done.bind(null, null));
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.customDomains
+        .get({ id: this.data[0].custom_domain_id })
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should perform a POST request to /api/v2/custom-domains/cd_0000000000000001', function(done) {
+      var request = this.request;
+
+      this.customDomains.get({ id: this.data[0].custom_domain_id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/custom-domains/' + this.data.id)
+        .reply(500);
+
+      this.customDomains.get({ id: this.data.id }).catch(function(err) {
+        expect(err).to.exist;
+
+        done();
+      });
+    });
+
+    it('should include the token in the Authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .get('/custom-domains/' + this.data.id)
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.customDomains.get({ id: this.data.id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+
+  describe('#create', function() {
+    var data = {
+      custom_domain_id: 'cd_0000000000000001',
+      domain: 'login.mycompany.com',
+      primary: false,
+      status: 'ready',
+      type: 'self_managed_certs',
+      origin_domain_name: 'mycompany_cd_0000000000000001.edge.tenants.auth0.com',
+      verification: {
+        methods: ['object']
+      }
+    };
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .post('/custom-domains')
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.customDomains.create(data, function() {
+        done();
+      });
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.customDomains
+        .create(data)
+        .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)
+        .post('/custom-domains')
+        .reply(500);
+
+      this.customDomains.create(data).catch(function(err) {
+        expect(err).to.exist;
+
+        done();
+      });
+    });
+
+    it('should perform a POST request to /api/v2/custom-domains', function(done) {
+      var request = this.request;
+
+      this.customDomains.create(data).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should pass the data in the body of the request', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post('/custom-domains', data)
+        .reply(200);
+
+      this.customDomains.create(data).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)
+        .post('/custom-domains')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.customDomains.create(data).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+
+  describe('#delete', function() {
+    var id = 'cd_0000000000000001';
+
+    beforeEach(function() {
+      this.request = nock(API_URL)
+        .delete('/custom-domains/' + id)
+        .reply(200);
+    });
+
+    it('should accept a callback', function(done) {
+      this.customDomains.delete({ id: id }, done.bind(null, null));
+    });
+
+    it('should return a promise when no callback is given', function(done) {
+      this.customDomains.delete({ id: id }).then(done.bind(null, null));
+    });
+
+    it('should perform a delete request to /custom-domains/' + id, function(done) {
+      var request = this.request;
+
+      this.customDomains.delete({ id: id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .delete('/custom-domains/' + id)
+        .reply(500);
+
+      this.customDomains.delete({ id: id }).catch(function(err) {
+        expect(err).to.exist;
+
+        done();
+      });
+    });
+
+    it('should include the token in the authorization header', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .delete('/custom-domains/' + id)
+        .matchHeader('authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.customDomains.delete({ id: id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+
+  describe('#verify', function() {
+    beforeEach(function() {
+      this.data = { id: 'cd_0000000000000001' };
+
+      this.request = nock(API_URL)
+        .post('/custom-domains/' + this.data.id + '/verify')
+        .reply(200, this.data);
+    });
+
+    it('should accept a callback', function(done) {
+      this.customDomains.verify({ id: this.data.id }, done.bind(null, null));
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.customDomains
+        .verify({ id: this.data.id }, {})
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should perform a POST request to /api/v2/custom-domains/cd_0000000000000001/verify', function(done) {
+      var request = this.request;
+
+      this.customDomains.verify({ id: this.data.id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should include the new data in the body of the request', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post('/custom-domains/' + this.data.id + '/verify')
+        .reply(200);
+
+      this.customDomains.verify({ id: this.data.id }).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+
+    it('should pass any errors to the promise catch handler', function(done) {
+      nock.cleanAll();
+
+      var request = nock(API_URL)
+        .post('/custom-domains/' + this.data.id + '/verify')
+        .reply(500);
+
+      this.customDomains.verify({ id: this.data.id }).catch(function(err) {
+        expect(err).to.exist;
+
+        done();
+      });
+    });
+  });
+});