From fb8c3f8b83f7d6e1df32044f3429841c7b153dc4 Mon Sep 17 00:00:00 2001 From: Stephen Melvin Date: Wed, 18 May 2022 10:46:50 -0400 Subject: [PATCH 1/5] Added 'Rotate a client secret' endpoint --- src/management/ClientsManager.js | 43 +++++++++++++++++++++++++ test/management/client.tests.js | 54 ++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/management/ClientsManager.js b/src/management/ClientsManager.js index bf66e1970..655e5161b 100644 --- a/src/management/ClientsManager.js +++ b/src/management/ClientsManager.js @@ -54,6 +54,19 @@ class ClientsManager { options.tokenProvider ); this.resource = new RetryRestClient(auth0RestClient, options.retry); + + /** + * Provides an abstraction layer for consuming the + * {@link https://auth0.com/docs/api/management/v2#!/Clients/post_rotate_secret Auth0 Clients Rotate a client secret}. + * + * @type {external:RestClient} + */ + const auth0RotateSecretClient = new Auth0RestClient( + `${options.baseUrl}/clients/:client_id/rotate-secret`, + clientOptions, + options.tokenProvider + ); + this.rotateSecretResource = new RetryRestClient(auth0RotateSecretClient, options.retry); } /** @@ -166,6 +179,36 @@ class ClientsManager { delete(...args) { return this.resource.delete(...args); } + + /** + * Rotate a client secret + * + * @example + * management.clients.rotateClientSecret({ client_id: CLIENT_ID }, function (err, user) { + * if (err) { + * // Handle error. + * } + * + * // Client secret rotated. + * }); + * @param {object} params params object + * @param {string} params.client_id Application client ID. + * @returns {Promise|undefined} + */ + rotateClientSecret(params, cb) { + const query = params || {}; + + // Require a client ID. + if (!params.client_id) { + throw new ArgumentError('The client_id cannot be null or undefined'); + } + + if (cb && cb instanceof Function) { + return this.rotateSecretResource.create(query, {}, cb); + } + + return this.rotateSecretResource.create(query, {}); + } } module.exports = ClientsManager; diff --git a/test/management/client.tests.js b/test/management/client.tests.js index 1720202ad..9d244432c 100644 --- a/test/management/client.tests.js +++ b/test/management/client.tests.js @@ -285,6 +285,60 @@ describe('ClientsManager', () => { this.clients.delete({ client_id: id }).then(() => { expect(request.isDone()).to.be.true; + done(); + }); + }); + }); + describe('#rotateSecret', () => { + const client_id = 5; + + beforeEach(function () { + this.request = nock(API_URL) + .post(`/clients/${client_id}/rotate-secret`) + .reply(200, this.data); + }); + + it('should accept a callback', function (done) { + this.clients.rotateClientSecret({ client_id }, done.bind(null, null)); + }); + + it('should return a promise if no callback is given', function (done) { + this.clients + .rotateClientSecret({ client_id }, {}) + .then(done.bind(null, null)) + .catch(done.bind(null, null)); + }); + + it('should perform a POST request to /api/v2/clients/5/rotate-secret', function (done) { + const { request } = this; + + this.clients.rotateClientSecret({ client_id }).then(() => { + expect(request.isDone()).to.be.true; + + done(); + }); + }); + + it('should include the new data in the body of the request', function (done) { + nock.cleanAll(); + + const request = nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(200); + + this.clients.rotateClientSecret({ client_id }).then(() => { + expect(request.isDone()).to.be.true; + + done(); + }); + }); + + it('should pass any errors to the promise catch handler', function (done) { + nock.cleanAll(); + + nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(500); + + this.clients.rotateClientSecret({ client_id }).catch((err) => { + expect(err).to.exist; + done(); }); }); From ba37fd7fbef6b315b4c440e7eb9cf309ef147a72 Mon Sep 17 00:00:00 2001 From: Stephen Melvin Date: Wed, 18 May 2022 11:28:44 -0400 Subject: [PATCH 2/5] Removed client_id check --- src/management/ClientsManager.js | 5 ----- test/management/client.tests.js | 1 + 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/management/ClientsManager.js b/src/management/ClientsManager.js index 655e5161b..c8e34e3a8 100644 --- a/src/management/ClientsManager.js +++ b/src/management/ClientsManager.js @@ -198,11 +198,6 @@ class ClientsManager { rotateClientSecret(params, cb) { const query = params || {}; - // Require a client ID. - if (!params.client_id) { - throw new ArgumentError('The client_id cannot be null or undefined'); - } - if (cb && cb instanceof Function) { return this.rotateSecretResource.create(query, {}, cb); } diff --git a/test/management/client.tests.js b/test/management/client.tests.js index 9d244432c..f73002548 100644 --- a/test/management/client.tests.js +++ b/test/management/client.tests.js @@ -289,6 +289,7 @@ describe('ClientsManager', () => { }); }); }); + describe('#rotateSecret', () => { const client_id = 5; From 27e3c21dc9a9674f6ab1a42ecb55bf582d1e4f2d Mon Sep 17 00:00:00 2001 From: Stephen Melvin Date: Wed, 18 May 2022 12:09:28 -0400 Subject: [PATCH 3/5] Added client_id check. Added unit test for client_id check. --- src/management/ClientsManager.js | 5 +++ test/management/client.tests.js | 76 +++++++++++++++++--------------- 2 files changed, 46 insertions(+), 35 deletions(-) diff --git a/src/management/ClientsManager.js b/src/management/ClientsManager.js index c8e34e3a8..655e5161b 100644 --- a/src/management/ClientsManager.js +++ b/src/management/ClientsManager.js @@ -198,6 +198,11 @@ class ClientsManager { rotateClientSecret(params, cb) { const query = params || {}; + // Require a client ID. + if (!params.client_id) { + throw new ArgumentError('The client_id cannot be null or undefined'); + } + if (cb && cb instanceof Function) { return this.rotateSecretResource.create(query, {}, cb); } diff --git a/test/management/client.tests.js b/test/management/client.tests.js index f73002548..02b57fe81 100644 --- a/test/management/client.tests.js +++ b/test/management/client.tests.js @@ -6,10 +6,16 @@ const API_URL = 'https://tenant.auth0.com'; const ClientsManager = require(`../../src/management/ClientsManager`); const { ArgumentError } = require('rest-facade'); +/** + * @type {ClientsManager} + */ + +let clients; + describe('ClientsManager', () => { before(function () { this.token = 'TOKEN'; - this.clients = new ClientsManager({ + clients = new ClientsManager({ headers: { authorization: `Bearer ${this.token}`, }, @@ -26,7 +32,7 @@ describe('ClientsManager', () => { methods.forEach((method) => { it(`should have a ${method} method`, function () { - expect(this.clients[method]).to.exist.to.be.an.instanceOf(Function); + expect(clients[method]).to.exist.to.be.an.instanceOf(Function); }); }); }); @@ -57,13 +63,13 @@ describe('ClientsManager', () => { }); it('should accept a callback', function (done) { - this.clients.getAll(() => { + clients.getAll(() => { done(); }); }); it('should return a promise if no callback is given', function (done) { - this.clients.getAll().then(done.bind(null, null)).catch(done.bind(null, null)); + clients.getAll().then(done.bind(null, null)).catch(done.bind(null, null)); }); it('should pass any errors to the promise catch handler', function (done) { @@ -71,7 +77,7 @@ describe('ClientsManager', () => { nock(API_URL).get('/clients').reply(500); - this.clients.getAll().catch((err) => { + clients.getAll().catch((err) => { expect(err).to.exist; done(); }); @@ -83,7 +89,7 @@ describe('ClientsManager', () => { const data = [{ test: true }]; nock(API_URL).get('/clients').reply(200, data); - this.clients.getAll().then((clients) => { + clients.getAll().then((clients) => { expect(clients).to.be.an.instanceOf(Array); expect(clients.length).to.equal(data.length); @@ -97,7 +103,7 @@ describe('ClientsManager', () => { it('should perform a GET request to /api/v2/clients', function (done) { const { request } = this; - this.clients.getAll().then(() => { + clients.getAll().then(() => { expect(request.isDone()).to.be.true; done(); }); @@ -111,7 +117,7 @@ describe('ClientsManager', () => { .matchHeader('Authorization', `Bearer ${this.token}`) .reply(200); - this.clients.getAll().then(() => { + clients.getAll().then(() => { expect(request.isDone()).to.be.true; done(); }); @@ -128,7 +134,7 @@ describe('ClientsManager', () => { }) .reply(200); - this.clients.getAll({ include_fields: true, fields: 'test' }).then(() => { + clients.getAll({ include_fields: true, fields: 'test' }).then(() => { expect(request.isDone()).to.be.true; done(); }); @@ -143,17 +149,17 @@ describe('ClientsManager', () => { }); it('should accept a callback', function (done) { - this.clients.create(data, done.bind(null, null)); + clients.create(data, done.bind(null, null)); }); it('should return a promise if no callback is given', function (done) { - this.clients.create(data).then(done.bind(null, null)).catch(done.bind(null, null)); + clients.create(data).then(done.bind(null, null)).catch(done.bind(null, null)); }); it('should perform a POST request to /api/v2/clients', function (done) { const { request } = this; - this.clients.create(data).then(() => { + clients.create(data).then(() => { expect(request.isDone()).to.be.true; done(); @@ -168,7 +174,7 @@ describe('ClientsManager', () => { .matchHeader('Authorization', `Bearer ${this.token}`) .reply(201, data); - this.clients.create(data).then(() => { + clients.create(data).then(() => { expect(request.isDone()).to.be.true; done(); @@ -180,7 +186,7 @@ describe('ClientsManager', () => { const request = nock(API_URL).post('/clients', data).reply(201, data); - this.clients.create(data).then(() => { + clients.create(data).then(() => { expect(request.isDone()).to.be.true; done(); @@ -202,20 +208,17 @@ describe('ClientsManager', () => { it('should accept a callback', function (done) { const params = { id: this.data.id }; - this.clients.get(params, done.bind(null, null)); + clients.get(params, done.bind(null, null)); }); it('should return a promise if no callback is given', function (done) { - this.clients - .get({ id: this.data.id }) - .then(done.bind(null, null)) - .catch(done.bind(null, null)); + clients.get({ id: this.data.id }).then(done.bind(null, null)).catch(done.bind(null, null)); }); it('should perform a POST request to /api/v2/clients/5', function (done) { const { request } = this; - this.clients.get({ client_id: this.data.id }).then(() => { + clients.get({ client_id: this.data.id }).then(() => { expect(request.isDone()).to.be.true; done(); @@ -231,20 +234,17 @@ describe('ClientsManager', () => { }); it('should accept a callback', function (done) { - this.clients.update({ client_id: 5 }, {}, done.bind(null, null)); + clients.update({ client_id: 5 }, {}, done.bind(null, null)); }); it('should return a promise if no callback is given', function (done) { - this.clients - .update({ client_id: 5 }, {}) - .then(done.bind(null, null)) - .catch(done.bind(null, null)); + clients.update({ client_id: 5 }, {}).then(done.bind(null, null)).catch(done.bind(null, null)); }); it('should perform a PATCH request to /api/v2/clients/5', function (done) { const { request } = this; - this.clients.update({ client_id: 5 }, {}).then(() => { + clients.update({ client_id: 5 }, {}).then(() => { expect(request.isDone()).to.be.true; done(); @@ -256,7 +256,7 @@ describe('ClientsManager', () => { const request = nock(API_URL).patch(`/clients/${this.data.id}`, this.data).reply(200); - this.clients.update({ client_id: 5 }, this.data).then(() => { + clients.update({ client_id: 5 }, this.data).then(() => { expect(request.isDone()).to.be.true; done(); @@ -272,17 +272,17 @@ describe('ClientsManager', () => { }); it('should accept a callback', function (done) { - this.clients.delete({ client_id: id }, done.bind(null, null)); + clients.delete({ client_id: id }, done.bind(null, null)); }); it('should return a promise when no callback is given', function (done) { - this.clients.delete({ client_id: id }).then(done.bind(null, null)); + clients.delete({ client_id: id }).then(done.bind(null, null)); }); it(`should perform a DELETE request to /clients/${id}`, function (done) { const { request } = this; - this.clients.delete({ client_id: id }).then(() => { + clients.delete({ client_id: id }).then(() => { expect(request.isDone()).to.be.true; done(); @@ -300,11 +300,11 @@ describe('ClientsManager', () => { }); it('should accept a callback', function (done) { - this.clients.rotateClientSecret({ client_id }, done.bind(null, null)); + clients.rotateClientSecret({ client_id }, done.bind(null, null)); }); it('should return a promise if no callback is given', function (done) { - this.clients + clients .rotateClientSecret({ client_id }, {}) .then(done.bind(null, null)) .catch(done.bind(null, null)); @@ -313,19 +313,25 @@ describe('ClientsManager', () => { it('should perform a POST request to /api/v2/clients/5/rotate-secret', function (done) { const { request } = this; - this.clients.rotateClientSecret({ client_id }).then(() => { + clients.rotateClientSecret({ client_id }).then(() => { expect(request.isDone()).to.be.true; done(); }); }); + it('should return an error when client_id is not sent', () => { + expect(() => { + clients.rotateClientSecret({}); + }).to.throw(ArgumentError, 'The client_id cannot be null or undefined'); + }); + it('should include the new data in the body of the request', function (done) { nock.cleanAll(); const request = nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(200); - this.clients.rotateClientSecret({ client_id }).then(() => { + clients.rotateClientSecret({ client_id }).then(() => { expect(request.isDone()).to.be.true; done(); @@ -337,7 +343,7 @@ describe('ClientsManager', () => { nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(500); - this.clients.rotateClientSecret({ client_id }).catch((err) => { + clients.rotateClientSecret({ client_id }).catch((err) => { expect(err).to.exist; done(); From 28b864b5c34278bc37ff3fb995cdbfa109701e41 Mon Sep 17 00:00:00 2001 From: Stephen Melvin Date: Wed, 18 May 2022 12:18:48 -0400 Subject: [PATCH 4/5] Fixed eslint errors --- test/management/client.tests.js | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/test/management/client.tests.js b/test/management/client.tests.js index 02b57fe81..49ffe077b 100644 --- a/test/management/client.tests.js +++ b/test/management/client.tests.js @@ -31,7 +31,7 @@ describe('ClientsManager', () => { const methods = ['getAll', 'get', 'create', 'update', 'delete']; methods.forEach((method) => { - it(`should have a ${method} method`, function () { + it(`should have a ${method} method`, () => { expect(clients[method]).to.exist.to.be.an.instanceOf(Function); }); }); @@ -62,17 +62,17 @@ describe('ClientsManager', () => { this.request = nock(API_URL).get('/clients').reply(200); }); - it('should accept a callback', function (done) { + it('should accept a callback', (done) => { clients.getAll(() => { done(); }); }); - it('should return a promise if no callback is given', function (done) { + it('should return a promise if no callback is given', (done) => { clients.getAll().then(done.bind(null, null)).catch(done.bind(null, null)); }); - it('should pass any errors to the promise catch handler', function (done) { + it('should pass any errors to the promise catch handler', (done) => { nock.cleanAll(); nock(API_URL).get('/clients').reply(500); @@ -83,7 +83,7 @@ describe('ClientsManager', () => { }); }); - it('should pass the body of the response to the "then" handler', function (done) { + it('should pass the body of the response to the "then" handler', (done) => { nock.cleanAll(); const data = [{ test: true }]; @@ -123,7 +123,7 @@ describe('ClientsManager', () => { }); }); - it('should pass the parameters in the query-string', function (done) { + it('should pass the parameters in the query-string', (done) => { nock.cleanAll(); const request = nock(API_URL) @@ -148,11 +148,11 @@ describe('ClientsManager', () => { this.request = nock(API_URL).post('/clients').reply(201, data); }); - it('should accept a callback', function (done) { + it('should accept a callback', (done) => { clients.create(data, done.bind(null, null)); }); - it('should return a promise if no callback is given', function (done) { + it('should return a promise if no callback is given', (done) => { clients.create(data).then(done.bind(null, null)).catch(done.bind(null, null)); }); @@ -181,7 +181,7 @@ describe('ClientsManager', () => { }); }); - it('should include the new client data in the request body', function (done) { + it('should include the new client data in the request body', (done) => { nock.cleanAll(); const request = nock(API_URL).post('/clients', data).reply(201, data); @@ -233,11 +233,11 @@ describe('ClientsManager', () => { this.request = nock(API_URL).patch(`/clients/${this.data.id}`).reply(200, this.data); }); - it('should accept a callback', function (done) { + it('should accept a callback', (done) => { clients.update({ client_id: 5 }, {}, done.bind(null, null)); }); - it('should return a promise if no callback is given', function (done) { + it('should return a promise if no callback is given', (done) => { clients.update({ client_id: 5 }, {}).then(done.bind(null, null)).catch(done.bind(null, null)); }); @@ -271,11 +271,11 @@ describe('ClientsManager', () => { this.request = nock(API_URL).delete(`/clients/${id}`).reply(200); }); - it('should accept a callback', function (done) { + it('should accept a callback', (done) => { clients.delete({ client_id: id }, done.bind(null, null)); }); - it('should return a promise when no callback is given', function (done) { + it('should return a promise when no callback is given', (done) => { clients.delete({ client_id: id }).then(done.bind(null, null)); }); @@ -299,11 +299,11 @@ describe('ClientsManager', () => { .reply(200, this.data); }); - it('should accept a callback', function (done) { + it('should accept a callback', (done) => { clients.rotateClientSecret({ client_id }, done.bind(null, null)); }); - it('should return a promise if no callback is given', function (done) { + it('should return a promise if no callback is given', (done) => { clients .rotateClientSecret({ client_id }, {}) .then(done.bind(null, null)) @@ -326,7 +326,7 @@ describe('ClientsManager', () => { }).to.throw(ArgumentError, 'The client_id cannot be null or undefined'); }); - it('should include the new data in the body of the request', function (done) { + it('should include the new data in the body of the request', (done) => { nock.cleanAll(); const request = nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(200); @@ -338,7 +338,7 @@ describe('ClientsManager', () => { }); }); - it('should pass any errors to the promise catch handler', function (done) { + it('should pass any errors to the promise catch handler', (done) => { nock.cleanAll(); nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(500); From 24a2e35fedd9b26374d1adc6ebe340d7a5bb68b3 Mon Sep 17 00:00:00 2001 From: Stephen Melvin Date: Wed, 18 May 2022 14:52:21 -0400 Subject: [PATCH 5/5] Removed reference to this.data --- test/management/client.tests.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/management/client.tests.js b/test/management/client.tests.js index 49ffe077b..5560554d6 100644 --- a/test/management/client.tests.js +++ b/test/management/client.tests.js @@ -294,9 +294,7 @@ describe('ClientsManager', () => { const client_id = 5; beforeEach(function () { - this.request = nock(API_URL) - .post(`/clients/${client_id}/rotate-secret`) - .reply(200, this.data); + this.request = nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(200); }); it('should accept a callback', (done) => {