Repository: auth0/auth0.js
Pull request #1057 (2019-12-03 16:26:41)

[CAUTH-239] Add getChallenge method
### Changes

Add a method to create and retrieve a challenge (aka captcha).

### References

https://auth0team.atlassian.net/browse/CAUTH-239

### Testing

- [x] This change adds unit test coverage

- [ ] 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 tests and linters described in the [Develop section](https://github.com/auth0/auth0.js#develop) run without errors


lbalmaceda: Looks good after the updates. Let's wait for that endpoint to go public before merging this. 

Patch:
diff --git a/src/authentication/index.js b/src/authentication/index.js
index 0cf41c2..b08b912 100644
--- a/src/authentication/index.js
+++ b/src/authentication/index.js
@@ -546,6 +546,28 @@ Authentication.prototype.userInfo = function(accessToken, cb) {
     .end(responseHandler(cb, { ignoreCasing: true }));
 };
 
+/**
+ * Makes a call to the `/usernamepassword/challenge` endpoint
+ * and returns the challenge (captcha) if necessary.
+ *
+ * @method getChallenge
+ * @param {callback} cb
+ */
+Authentication.prototype.getChallenge = function(cb) {
+  assert.check(cb, { type: 'function', message: 'cb parameter is not valid' });
+
+  if (!this.baseOptions.state) {
+    return cb();
+  }
+
+  var url = urljoin(this.baseOptions.rootUrl, 'usernamepassword', 'challenge');
+
+  return this.request
+    .post(url)
+    .send({ state: this.baseOptions.state })
+    .end(responseHandler(cb, { ignoreCasing: true }));
+};
+
 /**
  * @callback delegationCallback
  * @param {Error} [err] error returned by Auth0 with the reason why the delegation failed


Test patch:
diff --git a/test/authentication/authentication.test.js b/test/authentication/authentication.test.js
index 09a57e7..c416007 100644
--- a/test/authentication/authentication.test.js
+++ b/test/authentication/authentication.test.js
@@ -10,7 +10,6 @@ import RequestBuilder from '../../src/helper/request-builder';
 import windowHelper from '../../src/helper/window';
 import Storage from '../../src/helper/storage';
 import Authentication from '../../src/authentication';
-import WebAuth from '../../src/web-auth';
 
 var telemetryInfo = new RequestBuilder({}).getTelemetryData();
 
@@ -23,6 +22,7 @@ describe('auth0.authentication', function() {
       }
     };
   });
+
   describe('initialization', function() {
     it('should use first argument as options when only one argument is used', function() {
       var auth0 = new Authentication({ domain: 'foo', clientID: 'cid' });
@@ -617,6 +617,96 @@ describe('auth0.authentication', function() {
     });
   });
 
+  context('challenge', function() {
+    context('when the client does not have state', function() {
+      before(function() {
+        this.auth0 = new Authentication(this.webAuthSpy, {
+          domain: 'me.auth0.com',
+          clientID: '...',
+          redirectUri: 'http://page.com/callback',
+          responseType: 'code',
+          _sendTelemetry: false
+        });
+      });
+
+      it('should return nothing', function(done) {
+        this.auth0.getChallenge((err, challenge) => {
+          expect(err).to.not.be.ok();
+          expect(challenge).to.not.be.ok();
+          done();
+        });
+      });
+    });
+
+    context('when the client has state', function() {
+      before(function() {
+        this.auth0 = new Authentication(this.webAuthSpy, {
+          domain: 'me.auth0.com',
+          clientID: '...',
+          redirectUri: 'http://page.com/callback',
+          responseType: 'code',
+          _sendTelemetry: false,
+          state: '123abc'
+        });
+      });
+
+      afterEach(function() {
+        request.post.restore();
+      });
+
+      it('should post state and returns the image/type', function(done) {
+        sinon.stub(request, 'post').callsFake(function(url) {
+          expect(url).to.be('https://me.auth0.com/usernamepassword/challenge');
+          return new RequestMock({
+            body: {
+              state: '123abc'
+            },
+            headers: {
+              'Content-Type': 'application/json'
+            },
+            cb: function(cb) {
+              cb(null, {
+                body: {
+                  image: 'svg+yadayada',
+                  type: 'code'
+                }
+              });
+            }
+          });
+        });
+
+        this.auth0.getChallenge((err, challenge) => {
+          expect(err).to.not.be.ok();
+          expect(challenge.image).to.be('svg+yadayada');
+          expect(challenge.type).to.be('code');
+          done();
+        });
+      });
+
+      it('should return the error if network fails', function(done) {
+        sinon.stub(request, 'post').callsFake(function(url) {
+          expect(url).to.be('https://me.auth0.com/usernamepassword/challenge');
+          return new RequestMock({
+            body: {
+              state: '123abc'
+            },
+            headers: {
+              'Content-Type': 'application/json'
+            },
+            cb: function(cb) {
+              cb(new Error('error error error'));
+            }
+          });
+        });
+
+        this.auth0.getChallenge((err, challenge) => {
+          expect(err.original.message).to.equal('error error error');
+          done();
+        });
+      });
+    });
+  });
+
   context('oauthToken', function() {
     before(function() {
       this.auth0 = new Authentication(this.webAuthSpy, {