Repository: auth0/node-auth0
Pull request #569 (2021-01-08 19:30:55)

Adding Support for Guardian factor settings endpoints
### Changes
We are wanting to add support for the DUO and WebAuthn config settings through the SDK.

- Endpoints added
   - **GET** `/guardian/factors/:name/settings`
   - **PUT** `/guardian/factors/:name/settings`

- Classes and methods added, deleted, deprecated, or changed
   - `management.guardian.updateFactorSettings`
   - `management.guardian.getFactorSettings`
   - `management.guardianUpdateFactorSettings`
   - `management.guardianGetFactorSettings`
  

### Testing

Pull the local branch, authenticate, and get a token using the SDK.
Call the getFactorSettings like so: 
```
  management.guardian.getFactorSettings({ name: 'webauthn-roaming' }, function (err, settings) {
    console.log(settings);
  });
 ```
 Ensure that the settings for the factor are returned.
 
 Call the updateFactorSettings like so: 
 ```
  management.guardian.updateFactorSettings(
   { name: 'webauthn-roaming' },
   { userVerification: 'discouraged', overrideRelyingParty: false },
   function (err, settings) {
    console.log(settings);
  });
 ```
 Ensure that the setting is updated either through getting the config, or viewing it in the manage dashboard.

- [ ] This change adds unit test coverage
- [x] This change adds integration test coverage

### 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/GuardianManager.js b/src/management/GuardianManager.js
index e5125d3b5..12393a338 100644
--- a/src/management/GuardianManager.js
+++ b/src/management/GuardianManager.js
@@ -75,6 +75,18 @@ var GuardianManager = function(options) {
   );
   this.factors = new RetryRestClient(guardianFactorsAuth0RestClient, options.retry);
 
+  /**
+   * Provides an abstraction layer for configuring Factor settings
+   *
+   * @type {external:RestClient}
+   */
+  var guardianFactorSettingsAuth0RestClient = new Auth0RestClient(
+    options.baseUrl + '/guardian/factors/:name/settings',
+    clientOptions,
+    options.tokenProvider
+  );
+  this.factorSettings = new RetryRestClient(guardianFactorSettingsAuth0RestClient, options.retry);
+
   /**
    * Provides an abstraction layer for retrieving Guardian factor templates.
    *
@@ -220,6 +232,45 @@ utils.wrapPropertyMethod(GuardianManager, 'createEnrollmentTicket', 'tickets.cre
  */
 utils.wrapPropertyMethod(GuardianManager, 'getFactors', 'factors.getAll');
 
+/**
+ * Get Guardian factor configuration
+ *
+ * @method    getFactorSettings
+ * @memberOf  module:management.GuardianManager.prototype
+ *
+ * @example
+ * management.guardian.getFactorSettings({ name: 'webauthn-roaming' }, function (err, settings) {
+ *   console.log(settings);
+ * });
+ *
+ * @param   {Object}    params            Factor  parameters.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(GuardianManager, 'getFactorSettings', 'factorSettings.get');
+
+/**
+ * Update Guardian factor configuration
+ *
+ * @method    updateFactorSettings
+ * @memberOf  module:management.GuardianManager.prototype
+ *
+ * @example
+ * management.guardian.updateFactorSettings(
+ *  { name: 'webauthn-roaming' },
+ *  { userVerification: 'discouraged', overrideRelyingParty: false },
+ *  function (err, settings) {
+ *   console.log(settings);
+ * });
+ *
+ * @param   {Object}    params            Factor  parameters.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(GuardianManager, 'updateFactorSettings', 'factorSettings.update');
+
 /**
  * Get Guardian factor provider configuration
  *
diff --git a/src/management/index.js b/src/management/index.js
index 79465fbf3..092ec8ac7 100644
--- a/src/management/index.js
+++ b/src/management/index.js
@@ -2834,6 +2834,28 @@ utils.wrapPropertyMethod(
  */
 utils.wrapPropertyMethod(ManagementClient, 'getGuardianFactors', 'guardian.getFactors');
 
+/**
+ * Get the settings of a Guardian factor.
+ *
+ * @method    getGuardianFactorSettings
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.getGuardianFactorSettings({ name: 'duo' }, function (err, settings) {
+ *   console.log(settings);
+ * });
+ *
+ * @param   {Object}    params            Factor parameters.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(
+  ManagementClient,
+  'getGuardianFactorSettings',
+  'guardian.getFactorSettings'
+);
+
 /**
  * Get Guardian factor provider configuration
  *
@@ -2883,6 +2905,32 @@ utils.wrapPropertyMethod(
   'guardian.updateFactorProvider'
 );
 
+/**
+ * Update a Guardian's factor settings
+ *
+ * @method    updateGuardianFactorSettings
+ * @memberOf  module:management.ManagementClient.prototype
+ *
+ * @example
+ * management.updateGuardianFactorSettings(
+ *  { name: 'webauthn-roaming' },
+ *  { userVerification: 'discouraged', overrideRelyingParty: false },
+ *  function (err, settings) {
+ *   console.log(settings);
+ * })
+ *
+ * @param   {Object}    params            Factor parameters.
+ * @param   {Object}    data              Updated Factor settings data.
+ * @param   {Function}  [cb]              Callback function.
+ *
+ * @return  {Promise|undefined}
+ */
+utils.wrapPropertyMethod(
+  ManagementClient,
+  'updateGuardianFactorSettings',
+  'guardian.updateFactorSettings'
+);
+
 /**
  * Get Guardian enrollment and verification factor templates
  *


Test patch:
diff --git a/test/management/guardian.tests.js b/test/management/guardian.tests.js
index cb1113be1..9f313f220 100644
--- a/test/management/guardian.tests.js
+++ b/test/management/guardian.tests.js
@@ -21,11 +21,13 @@ describe('GuardianManager', function() {
       'getGuardianEnrollment',
       'deleteGuardianEnrollment',
       'getFactors',
+      'getFactorSettings',
       'getFactorProvider',
       'updateFactorProvider',
       'getFactorTemplates',
       'updateFactorTemplates',
       'updateFactor',
+      'updateFactorSettings',
       'getPhoneFactorSelectedProvider',
       'updatePhoneFactorSelectedProvider',
       'getPhoneFactorMessageTypes',
@@ -346,6 +348,69 @@ describe('GuardianManager', function() {
     });
   });
 
+  describe('#getFactorSettings', function() {
+    beforeEach(function() {
+      this.params = { name: 'webauthn-roaming' };
+      this.data = {
+        userVerification: 'discouraged',
+        overrideRelyingParty: false
+      };
+      this.request = nock(API_URL)
+        .get('/guardian/factors/' + this.params.name + '/settings')
+        .reply(200, this.data);
+    });
+
+    it('should accept a callback', function(done) {
+      this.guardian.getFactorSettings(this.params, done.bind(null, null));
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.guardian
+        .getFactorSettings(this.params)
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should perform a GET request to /api/v2/guardian/factors/webauthn-roaming/settings', function(done) {
+      var request = this.request;
+
+      this.guardian.getFactorSettings(this.params).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('/guardian/factors/' + this.params.name + '/settings')
+        .reply(500);
+
+      this.guardian.getFactorSettings(this.params).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('/guardian/factors/' + this.params.name + '/settings')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.guardian.getFactorSettings(this.params).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+
   describe('#getFactorProvider', function() {
     beforeEach(function() {
       this.params = { name: 'sms', provider: 'twilio' };
@@ -711,6 +776,82 @@ describe('GuardianManager', function() {
     });
   });
 
+  describe('#updateFactorSettings', function() {
+    beforeEach(function() {
+      this.params = { name: 'webauthn-roaming' };
+      this.data = {
+        userVerification: 'discouraged',
+        overrideRelyingParty: false
+      };
+    });
+
+    it('should accept a callback', function(done) {
+      this.guardian.updateFactorSettings({ id: 5 }, {}, done.bind(null, null));
+    });
+
+    it('should return a promise if no callback is given', function(done) {
+      this.guardian
+        .updateFactorSettings(this.params, {})
+        .then(done.bind(null, null))
+        .catch(done.bind(null, null));
+    });
+
+    it('should perform a PUT request to /api/v2/guardian/factors/webauthn-roaming/settings', function(done) {
+      var request = nock(API_URL)
+        .put('/guardian/factors/' + this.params.name + '/settings')
+        .reply(200, this.data);
+
+      this.guardian.updateFactorSettings(this.params, this.data).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)
+        .put('/guardian/factors/' + this.params.name + '/settings', this.data)
+        .reply(200);
+
+      this.guardian.updateFactorSettings(this.params, 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)
+        .put('/guardian/factors/' + this.params.name + '/settings')
+        .reply(500);
+
+      this.guardian.updateFactorSettings(this.params, 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)
+        .put('/guardian/factors/' + this.params.name + '/settings')
+        .matchHeader('Authorization', 'Bearer ' + this.token)
+        .reply(200);
+
+      this.guardian.updateFactorSettings(this.params, this.data).then(function() {
+        expect(request.isDone()).to.be.true;
+
+        done();
+      });
+    });
+  });
+
   describe('#getPolicies', function() {
     beforeEach(function() {
       this.data = ['all-applications'];
diff --git a/test/management/management-client.tests.js b/test/management/management-client.tests.js
index fdcc7cc71..fee7794f6 100644
--- a/test/management/management-client.tests.js
+++ b/test/management/management-client.tests.js
@@ -899,8 +899,10 @@ describe('ManagementClient', function() {
       'deleteRulesConfig',
       'createGuardianEnrollmentTicket',
       'getGuardianFactors',
+      'getGuardianFactorSettings',
       'getGuardianFactorProvider',
       'updateGuardianFactorProvider',
+      'updateGuardianFactorSettings',
       'getGuardianFactorTemplates',
       'updateGuardianFactorTemplates',
       'updateGuardianFactor',