Skip to content

Commit

Permalink
Added ClientsManager.rotateClientSecret method (#721)
Browse files Browse the repository at this point in the history
* Added 'Rotate a client secret' endpoint

* Removed client_id check

* Added client_id check.
Added unit test for client_id check.

* Fixed eslint errors

* Removed reference to this.data

Co-authored-by: Stephen Melvin <[email protected]>
  • Loading branch information
sbmelvin and Stephen Melvin authored May 19, 2022
1 parent 022286a commit 0ed8919
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 43 deletions.
43 changes: 43 additions & 0 deletions src/management/ClientsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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;
145 changes: 102 additions & 43 deletions test/management/client.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}`,
},
Expand All @@ -25,8 +31,8 @@ describe('ClientsManager', () => {
const methods = ['getAll', 'get', 'create', 'update', 'delete'];

methods.forEach((method) => {
it(`should have a ${method} method`, function () {
expect(this.clients[method]).to.exist.to.be.an.instanceOf(Function);
it(`should have a ${method} method`, () => {
expect(clients[method]).to.exist.to.be.an.instanceOf(Function);
});
});
});
Expand Down Expand Up @@ -56,34 +62,34 @@ describe('ClientsManager', () => {
this.request = nock(API_URL).get('/clients').reply(200);
});

it('should accept a callback', function (done) {
this.clients.getAll(() => {
it('should accept a callback', (done) => {
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));
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);

this.clients.getAll().catch((err) => {
clients.getAll().catch((err) => {
expect(err).to.exist;
done();
});
});

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 }];
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);
Expand All @@ -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();
});
Expand All @@ -111,13 +117,13 @@ describe('ClientsManager', () => {
.matchHeader('Authorization', `Bearer ${this.token}`)
.reply(200);

this.clients.getAll().then(() => {
clients.getAll().then(() => {
expect(request.isDone()).to.be.true;
done();
});
});

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)
Expand All @@ -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();
});
Expand All @@ -142,18 +148,18 @@ describe('ClientsManager', () => {
this.request = nock(API_URL).post('/clients').reply(201, data);
});

it('should accept a callback', function (done) {
this.clients.create(data, done.bind(null, null));
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) {
this.clients.create(data).then(done.bind(null, null)).catch(done.bind(null, null));
it('should return a promise if no callback is given', (done) => {
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();
Expand All @@ -168,19 +174,19 @@ 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();
});
});

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);

this.clients.create(data).then(() => {
clients.create(data).then(() => {
expect(request.isDone()).to.be.true;

done();
Expand All @@ -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();
Expand All @@ -230,21 +233,18 @@ describe('ClientsManager', () => {
this.request = nock(API_URL).patch(`/clients/${this.data.id}`).reply(200, this.data);
});

it('should accept a callback', function (done) {
this.clients.update({ client_id: 5 }, {}, done.bind(null, null));
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) {
this.clients
.update({ client_id: 5 }, {})
.then(done.bind(null, null))
.catch(done.bind(null, null));
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));
});

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();
Expand All @@ -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();
Expand All @@ -271,22 +271,81 @@ describe('ClientsManager', () => {
this.request = nock(API_URL).delete(`/clients/${id}`).reply(200);
});

it('should accept a callback', function (done) {
this.clients.delete({ client_id: id }, done.bind(null, null));
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) {
this.clients.delete({ client_id: id }).then(done.bind(null, null));
it('should return a promise when no callback is given', (done) => {
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();
});
});
});

describe('#rotateSecret', () => {
const client_id = 5;

beforeEach(function () {
this.request = nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(200);
});

it('should accept a callback', (done) => {
clients.rotateClientSecret({ client_id }, done.bind(null, null));
});

it('should return a promise if no callback is given', (done) => {
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;

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', (done) => {
nock.cleanAll();

const request = nock(API_URL).post(`/clients/${client_id}/rotate-secret`).reply(200);

clients.rotateClientSecret({ client_id }).then(() => {
expect(request.isDone()).to.be.true;

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);

clients.rotateClientSecret({ client_id }).catch((err) => {
expect(err).to.exist;

done();
});
});
});
});

0 comments on commit 0ed8919

Please sign in to comment.