From 64f4b239f73ca318e3e3718c54bde743ee614299 Mon Sep 17 00:00:00 2001 From: Andy Trevorah Date: Tue, 10 Jul 2018 17:36:19 +0100 Subject: [PATCH 1/6] fix documentation typo --- src/auth/OAuthAuthenticator.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js index 125b8878e..7c1d1a4dc 100644 --- a/src/auth/OAuthAuthenticator.js +++ b/src/auth/OAuthAuthenticator.js @@ -56,7 +56,7 @@ var OAuthAuthenticator = function(options) { * var data = { * client_id: '{CLIENT_ID}', // Optional field. * username: '{USERNAME}', - * password: '{PASSWORD} + * password: '{PASSWORD}', * connection: '{CONNECTION_NAME}', * scope: 'openid' // Optional field. * }; @@ -121,7 +121,7 @@ OAuthAuthenticator.prototype.signIn = function(userData, cb) { * var data = { * client_id: '{CLIENT_ID}', // Optional field. * username: '{USERNAME}', - * password: '{PASSWORD}' + * password: '{PASSWORD}', * realm: '{CONNECTION_NAME}', // Optional field. * scope: 'openid' // Optional field. * }; From f461857247ba6063eeea9aabd678c17064cf11d7 Mon Sep 17 00:00:00 2001 From: Andy Trevorah Date: Tue, 10 Jul 2018 17:38:11 +0100 Subject: [PATCH 2/6] fix documentation typo --- src/auth/OAuthAuthenticator.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/auth/OAuthAuthenticator.js b/src/auth/OAuthAuthenticator.js index 125b8878e..0c910c877 100644 --- a/src/auth/OAuthAuthenticator.js +++ b/src/auth/OAuthAuthenticator.js @@ -6,7 +6,7 @@ var RestClient = require('rest-facade').Client; /** * @class * Abstracts the sign-in, sign-up and change-password processes for Database & - * Active Directory auhtentication services. + * Active Directory authentication services. * @constructor * @memberOf module:auth * From 766190badd4f890dc704e8c2f3b6c2492cdc7212 Mon Sep 17 00:00:00 2001 From: Felipe Medina Date: Tue, 26 Jun 2018 17:49:32 -0300 Subject: [PATCH 3/6] add guardian enrollment methods to management --- src/management/GuardianManager.js | 96 +++++++++++++++++++++++++++++++ src/management/index.js | 59 +++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/management/GuardianManager.js diff --git a/src/management/GuardianManager.js b/src/management/GuardianManager.js new file mode 100644 index 000000000..010aa9ebc --- /dev/null +++ b/src/management/GuardianManager.js @@ -0,0 +1,96 @@ +var ArgumentError = require('rest-facade').ArgumentError; +var Auth0RestClient = require('../Auth0RestClient'); +var RetryRestClient = require('../RetryRestClient'); + +/** + * Simple facade for consuming a REST API endpoint. + * @external RestClient + * @see https://github.com/ngonzalvez/rest-facade + */ + +/** + * @class + * Abstracts interaction with the Guardian endpoint. + * @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 GuardianManager = 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'); + } + + var clientOptions = { + errorFormatter: { message: 'message', name: 'error' }, + headers: options.headers, + query: { repeatParams: false } + }; + + /** + * Provides an abstraction layer for retrieving Guardian enrollments. + * + * @type {external:RestClient} + */ + var guardianEnrollmentsAuth0RestClient = new Auth0RestClient( + options.baseUrl + '/guardian/enrollments/:id', + clientOptions, + options.tokenProvider + ); + this.enrollments = new RetryRestClient(guardianEnrollmentsAuth0RestClient, options.retry); +}; + +/** + * Get a single Guardian enrollment. + * + * @method getGuardianEnrollment + * @memberOf module:management.GuardianManager.prototype + * + * @example + * management.users.getGuardianEnrollment({ id: USER_ID }, function (err, enrollment) { + * console.log(enrollment); + * }); + * + * @param {Object} data The user data object. + * @param {String} data.id The user id. + * @param {Function} [cb] Callback function. + * + * @return {Promise|undefined} + */ +GuardianManager.prototype.getGuardianEnrollment = function(params, cb) { + return this.enrollments.get(params, cb); +}; + +/** + * Delete a Guardian enrollment. + * + * @method deleteGuardianEnrollment + * @memberOf module:management.GuardianManager.prototype + * + * @example + * management.users.deleteGuardianEnrollment({ id: USER_ID }, function (err, enrollments) { + * console.log(enrollments); + * }); + * + * @param {Object} data The user data object. + * @param {String} data.id The user id. + * @param {Function} [cb] Callback function. + * + * @return {Promise|undefined} + */ +GuardianManager.prototype.deleteGuardianEnrollment = function(params, cb) { + return this.enrollments.delete(params, cb); +}; + +module.exports = GuardianManager; diff --git a/src/management/index.js b/src/management/index.js index ec0691f1d..7dcad3afa 100644 --- a/src/management/index.js +++ b/src/management/index.js @@ -26,6 +26,7 @@ var ResourceServersManager = require('./ResourceServersManager'); var ManagementTokenProvider = require('./ManagementTokenProvider'); var RulesConfigsManager = require('./RulesConfigsManager'); var EmailTemplatesManager = require('./EmailTemplatesManager'); +var GuardianManager = require('./GuardianManager'); var BASE_URL_FORMAT = 'https://%s/api/v2'; var MANAGEMENT_API_AUD_FORMAT = 'https://%s/api/v2/'; @@ -155,6 +156,14 @@ var ManagementClient = function(options) { */ this.users = new UsersManager(managerOptions); + /** + * Simple abstraction for performing CRUD operations on the + * guardian endpoint. + * + * @type {GuardianManager} + */ + this.guardian = new GuardianManager(managerOptions); + /** * Simple abstraction for performing CRUD operations on the * connections endpoint. @@ -1236,6 +1245,56 @@ utils.wrapPropertyMethod( 'users.getGuardianEnrollments' ); +/** + * Get a single Guardian enrollment. + * + * @method getGuardianEnrollment + * @memberOf module:management.ManagementClient.prototype + * + * @example + * management.getGuardianEnrollment({ id: ENROLLMENT_ID }, function (err, enrollment) { + * console.log(enrollment); + * }); + * + * @param {Object} data The Guardian enrollment data object. + * @param {String} data.id The Guardian enrollment id. + * @param {Function} [cb] Callback function. + * + * @return {Promise|undefined} + */ +utils.wrapPropertyMethod( + ManagementClient, + 'getGuardianEnrollment', + 'guardian.getGuardianEnrollment' +); + +/** + * Delete a user's Guardian enrollment. + * + * @method deleteGuardianEnrollment + * @memberOf module:management.ManagementClient.prototype + * + * @example + * management.deleteGuardianEnrollment({ id: ENROLLMENT_ID }, function (err) { + * if (err) { + * // Handle error. + * } + * + * // Email provider deleted. + * }); + * + * @param {Object} data The Guardian enrollment data object. + * @param {String} data.id The Guardian enrollment id. + * @param {Function} [cb] Callback function. + * + * @return {Promise|undefined} + */ +utils.wrapPropertyMethod( + ManagementClient, + 'deleteGuardianEnrollment', + 'guardian.deleteGuardianEnrollment' +); + /** * Get all blacklisted tokens. * From a4bedef5875124e7b57b71e038a2a7a73f4d7bc0 Mon Sep 17 00:00:00 2001 From: Felipe Medina Date: Tue, 26 Jun 2018 18:27:57 -0300 Subject: [PATCH 4/6] add tests for guardian manager --- test/management/guardian.tests.js | 189 ++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 test/management/guardian.tests.js diff --git a/test/management/guardian.tests.js b/test/management/guardian.tests.js new file mode 100644 index 000000000..75f7e2bac --- /dev/null +++ b/test/management/guardian.tests.js @@ -0,0 +1,189 @@ +var expect = require('chai').expect; +var nock = require('nock'); + +var SRC_DIR = '../../src'; +var API_URL = 'https://tenants.auth0.com'; + +var GuardianManager = require(SRC_DIR + '/management/GuardianManager'); +var ArgumentError = require('rest-facade').ArgumentError; + +describe('GuardianManager', function() { + before(function() { + this.token = 'TOKEN'; + this.guardian = new GuardianManager({ + headers: { authorization: 'Bearer ' + this.token }, + baseUrl: API_URL + }); + }); + + describe('instance', function() { + var methods = ['getGuardianEnrollment', 'deleteGuardianEnrollment']; + + methods.forEach(function(method) { + it('should have a ' + method + ' method', function() { + expect(this.guardian[method]).to.exist.to.be.an.instanceOf(Function); + }); + }); + }); + + describe('#constructor', function() { + it('should error when no options are provided', function() { + expect(GuardianManager).to.throw(ArgumentError, 'Must provide manager options'); + }); + + it('should throw an error when no base URL is provided', function() { + var manager = GuardianManager.bind(null, {}); + + expect(manager).to.throw(ArgumentError, 'Must provide a base URL for the API'); + }); + + it('should throw an error when the base URL is invalid', function() { + var manager = GuardianManager.bind(null, { baseUrl: '' }); + + expect(manager).to.throw(ArgumentError, 'The provided base URL is invalid'); + }); + }); + + describe('#getGuardianEnrollment', function() { + beforeEach(function() { + this.data = { + id: 'dev_0000000000000001' + }; + this.params = { id: this.data.id }; + + this.request = nock(API_URL) + .get('/guardian/enrollments/' + this.data.id) + .reply(200); + }); + + it('should accept a callback', function(done) { + this.guardian.getGuardianEnrollment(this.params, done.bind(null, null)); + }); + + it('should return a promise if no callback is given', function(done) { + this.guardian + .getGuardianEnrollment(this.params) + .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('/guardian/enrollment') + .reply(500); + + this.guardian.getGuardianEnrollment(this.params).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 = { + id: 'dev_0000000000000001', + status: 'pending', + name: 'iPhone 7', + identifier: '76dc-a90c-a88c-a90c-a88c-a88c-a90c', + phone_number: '+1 999999999999', + enrolled_at: '2016-07-12T17:56:26.804Z', + last_auth: '2016-07-12T17:56:26.804Z' + }; + var request = nock(API_URL) + .get('/guardian/enrollments/' + data.id) + .reply(200, data); + + this.guardian.getGuardianEnrollment(this.params).then(function(enrollment) { + expect(enrollment).to.deep.equal(data); + + done(); + }); + }); + + it('should perform a GET request to /api/v2/guardian/enrollments', function(done) { + var request = this.request; + + var params = { id: this.data.id }; + this.guardian.getGuardianEnrollment(this.params).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('/guardian/enrollments/' + this.data.id) + .matchHeader('Authorization', 'Bearer ' + this.token) + .reply(200); + + this.guardian.getGuardianEnrollment(this.params).then(function() { + expect(request.isDone()).to.be.true; + done(); + }); + }); + }); + + describe('#deleteGuardianEnrollment', function() { + beforeEach(function() { + this.data = { + id: 'dev_0000000000000001' + }; + + this.request = nock(API_URL) + .delete('/guardian/enrollments/' + this.data.id) + .reply(200); + }); + + it('should accept a callback', function(done) { + this.guardian.deleteGuardianEnrollment(this.data, done.bind(null, null)); + }); + + it('should return a promise when no callback is given', function(done) { + this.guardian.deleteGuardianEnrollment(this.data).then(done.bind(null, null)); + }); + + it('should perform a DELETE request to /guardian/enrollments/:id', function(done) { + var request = this.request; + + this.guardian.deleteGuardianEnrollment(this.data).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('/guardian/enrollments/' + this.data.id) + .reply(500); + + this.guardian.deleteGuardianEnrollment(this.data).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('/guardian/enrollments/' + this.data.id) + .matchHeader('authorization', 'Bearer ' + this.token) + .reply(200); + + this.guardian.deleteGuardianEnrollment(this.data).then(function() { + expect(request.isDone()).to.be.true; + + done(); + }); + }); + }); +}); From 0a6af3f94ef0c8cd7d5f703baa9057ab99310047 Mon Sep 17 00:00:00 2001 From: Felipe Medina Date: Tue, 3 Jul 2018 10:02:53 -0300 Subject: [PATCH 5/6] add regenerate recovery code endpoint --- src/management/UsersManager.js | 44 ++++++++++++++++++++ src/management/index.js | 23 +++++++++++ test/management/users.tests.js | 74 +++++++++++++++++++++++++++++++++- 3 files changed, 140 insertions(+), 1 deletion(-) diff --git a/src/management/UsersManager.js b/src/management/UsersManager.js index 6cf05f7c4..534a52409 100644 --- a/src/management/UsersManager.js +++ b/src/management/UsersManager.js @@ -106,6 +106,21 @@ var UsersManager = function(options) { options.tokenProvider ); this.usersByEmail = new RetryRestClient(usersByEmailClient, options.retry); + + /** + * Provides an abstraction layer for regenerating Guardian recovery codes. + * + * @type {external:RestClient} + */ + var recoveryCodeRegenerationAuth0RestClients = new Auth0RestClient( + options.baseUrl + '/users/:id/recovery-code-regeneration', + clientOptions, + options.tokenProvider + ); + this.recoveryCodeRegenerations = new RetryRestClient( + recoveryCodeRegenerationAuth0RestClients, + options.retry + ); }; /** @@ -577,4 +592,33 @@ UsersManager.prototype.getGuardianEnrollments = function() { return this.enrollments.get.apply(this.enrollments, arguments); }; +/** + * Generate new Guardian recovery code. + * + * @method regenerateRecoveryCode + * @memberOf module:management.UsersManager.prototype + * + * @example + * management.users.regenerateRecoveryCode("USER_ID", function (err, result) { + * console.log(result.recovery_code); + * }); + * + * @param {Object} params Get logs data. + * @param {String} params.id User id. + * @param {Function} [cb] Callback function. + * + * @return {Promise|undefined} + */ +UsersManager.prototype.regenerateRecoveryCode = function(params, cb) { + if (!params || !params.id) { + throw new ArgumentError('The userId cannot be null or undefined'); + } + + if (cb && cb instanceof Function) { + return this.recoveryCodeRegenerations.create(params, {}, cb); + } + + return this.recoveryCodeRegenerations.create(params, {}); +}; + module.exports = UsersManager; diff --git a/src/management/index.js b/src/management/index.js index 7dcad3afa..2f60ed207 100644 --- a/src/management/index.js +++ b/src/management/index.js @@ -1245,6 +1245,29 @@ utils.wrapPropertyMethod( 'users.getGuardianEnrollments' ); +/** + * Generate new Guardian recovery code. + * + * @method regenerateRecoveryCode + * @memberOf module:management.ManagementClient.prototype + * + * @example + * management.regenerateRecoveryCode({ id: USER_ID }, function (err, newRecoveryCode) { + * console.log(newRecoveryCode); + * }); + * + * @param {Object} data The user data object. + * @param {String} data.id The user id. + * @param {Function} [cb] Callback function. + * + * @return {Promise|undefined} + */ +utils.wrapPropertyMethod( + ManagementClient, + 'regenerateRecoveryCode', + 'users.regenerateRecoveryCode' +); + /** * Get a single Guardian enrollment. * diff --git a/test/management/users.tests.js b/test/management/users.tests.js index 33ede4485..7d55bb7b1 100644 --- a/test/management/users.tests.js +++ b/test/management/users.tests.js @@ -30,7 +30,8 @@ describe('UsersManager', function() { 'deleteMultifactorProvider', 'updateUserMetadata', 'updateAppMetadata', - 'getGuardianEnrollments' + 'getGuardianEnrollments', + 'regenerateRecoveryCode' ]; methods.forEach(function(method) { @@ -963,4 +964,75 @@ describe('UsersManager', function() { }); }); }); + + describe('#regenerateRecoveryCode', function() { + var data = { + id: 'USER_ID' + }; + + beforeEach(function() { + this.request = nock(API_URL) + .post('/users/' + data.id + '/recovery-code-regeneration') + .reply(200); + }); + + it('should validate empty userId', function() { + var _this = this; + expect(function() { + _this.users.regenerateRecoveryCode(null, function() {}); + }).to.throw('The userId cannot be null or undefined'); + }); + + it('should accept a callback', function(done) { + this.users.regenerateRecoveryCode(data, function() { + done(); + }); + }); + + it('should return a promise if no callback is given', function(done) { + this.users + .regenerateRecoveryCode(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('/users/' + data.id + '/recovery-code-regeneration') + .reply(500); + + this.users.regenerateRecoveryCode(data).catch(function(err) { + expect(err).to.exist; + + done(); + }); + }); + + it('should perform a POST request to /api/v2/users/:id/recovery-code-regeneration', function(done) { + var request = this.request; + + this.users.regenerateRecoveryCode(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('/users/' + data.id + '/recovery-code-regeneration') + .matchHeader('Authorization', 'Bearer ' + this.token) + .reply(200); + + this.users.regenerateRecoveryCode(data).then(function() { + expect(request.isDone()).to.be.true; + + done(); + }); + }); + }); }); From 92f2c9494baba8b1323b37773ce0d47edd021c14 Mon Sep 17 00:00:00 2001 From: Felipe Medina Date: Wed, 1 Aug 2018 10:00:24 -0300 Subject: [PATCH 6/6] fix documentation mistake --- src/management/GuardianManager.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/management/GuardianManager.js b/src/management/GuardianManager.js index 010aa9ebc..eede511d2 100644 --- a/src/management/GuardianManager.js +++ b/src/management/GuardianManager.js @@ -58,7 +58,7 @@ var GuardianManager = function(options) { * @memberOf module:management.GuardianManager.prototype * * @example - * management.users.getGuardianEnrollment({ id: USER_ID }, function (err, enrollment) { + * management.users.getGuardianEnrollment({ id: ENROLLMENT_ID }, function (err, enrollment) { * console.log(enrollment); * }); * @@ -79,7 +79,7 @@ GuardianManager.prototype.getGuardianEnrollment = function(params, cb) { * @memberOf module:management.GuardianManager.prototype * * @example - * management.users.deleteGuardianEnrollment({ id: USER_ID }, function (err, enrollments) { + * management.users.deleteGuardianEnrollment({ id: ENROLLMENT_ID }, function (err, enrollments) { * console.log(enrollments); * }); *