Skip to content

Commit

Permalink
Merge pull request #1277 from DominickBattistini/passwordlessCaptcha
Browse files Browse the repository at this point in the history
[IAMRISK-1835] renderPasswordlessChallenge
  • Loading branch information
evansims authored Dec 12, 2022
2 parents 3c1b23d + a03f7f4 commit 05dbf4a
Show file tree
Hide file tree
Showing 6 changed files with 522 additions and 1 deletion.
23 changes: 23 additions & 0 deletions src/authentication/passwordless-authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,27 @@ PasswordlessAuthentication.prototype.verify = function(options, cb) {
.end(responseHandler(cb));
};

/**
* Makes a call to the `/passwordless/challenge` endpoint
* and returns the challenge (captcha) if necessary.
*
* @method getChallenge
* @param {callback} cb
* @memberof PasswordlessAuthentication.prototype
*/
PasswordlessAuthentication.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, 'passwordless', 'challenge');

return this.request
.post(url)
.send({ state: this.baseOptions.state })
.end(responseHandler(cb, { ignoreCasing: true }));
};

export default PasswordlessAuthentication;
61 changes: 60 additions & 1 deletion src/web-auth/captcha.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,4 +201,63 @@ function render(auth0Client, element, options, callback) {
};
}

export default { render: render };
/**
*
* Renders the passwordless captcha challenge in the provided element.
*
* @param {Authentication} auth0Client The challenge response from the authentication server
* @param {HTMLElement} element The element where the captcha needs to be rendered
* @param {Object} options The configuration options for the captcha
* @param {Object} [options.templates] An object containaing templates for each captcha provider
* @param {Function} [options.templates.auth0] template function receiving the challenge and returning an string
* @param {Function} [options.templates.recaptcha_v2] template function receiving the challenge and returning an string
* @param {Function} [options.templates.recaptcha_enterprise] template function receiving the challenge and returning an string
* @param {String} [options.lang=en] the ISO code of the language for recaptcha*
* @param {Function} [callback] an optional callback function
* @ignore
*/
function renderPasswordless(auth0Client, element, options, callback) {
options = object.merge(defaults).with(options || {});

function load(done) {
done = done || noop;
auth0Client.passwordless.getChallenge(function(err, challenge) {
if (err) {
element.innerHTML = options.templates.error(err);
return done(err);
}
if (!challenge.required) {
element.style.display = 'none';
element.innerHTML = '';
return;
}
element.style.display = '';
if (challenge.provider === AUTH0_PROVIDER) {
handleAuth0Provider(element, options, challenge, load);
} else if (
challenge.provider === RECAPTCHA_V2_PROVIDER ||
challenge.provider === RECAPTCHA_ENTERPRISE_PROVIDER
) {
handleRecaptchaProvider(element, options, challenge);
}
done();
});
}

function getValue() {
var captchaInput = element.querySelector('input[name="captcha"]');
if (!captchaInput) {
return;
}
return captchaInput.value;
}

load(callback);

return {
reload: load,
getValue: getValue,
};
}

export default { render: render, renderPasswordless: renderPasswordless };
21 changes: 21 additions & 0 deletions src/web-auth/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ WebAuth.prototype.changePassword = function (options, cb) {
* @param {String} options.send what will be sent via email which could be `link` or `code`. For SMS `code` is the only one valid
* @param {String} [options.phoneNumber] phone number where to send the `code`. This parameter is mutually exclusive with `email`
* @param {String} [options.email] email where to send the `code` or `link`. This parameter is mutually exclusive with `phoneNumber`
* @param {String} [options.captcha] the attempted solution for the captcha, if one was presented
* @param {String} options.connection name of the passwordless connection
* @param {Object} [options.authParams] additional Auth parameters when using `link`
* @param {Object} [options.xRequestLanguage] value for the X-Request-Language header. If not set, the language is detected using the client browser.
Expand Down Expand Up @@ -1123,4 +1124,24 @@ WebAuth.prototype.renderCaptcha = function (element, options, callback) {
return captcha.render(this.client, element, options, callback);
};

/**
*
* Renders the passwordless captcha challenge in the provided element.
* This function can only be used in the context of a Classic Universal Login Page.
*
* @method renderPasswordlessCaptcha
* @param {HTMLElement} element The element where the captcha needs to be rendered
* @param {Object} options The configuration options for the captcha
* @param {Object} [options.templates] An object containaing templates for each captcha provider
* @param {Function} [options.templates.auth0] template function receiving the challenge and returning an string
* @param {Function} [options.templates.recaptcha_v2] template function receiving the challenge and returning an string
* @param {Function} [options.templates.recaptcha_enterprise] template function receiving the challenge and returning an string
* @param {String} [options.lang=en] the ISO code of the language for recaptcha
* @param {Function} [callback] An optional completion callback
* @memberof WebAuth.prototype
*/
WebAuth.prototype.renderPasswordlessCaptcha = function (element, options, callback) {
return captcha.renderPasswordless(this.client, element, options, callback);
};

export default WebAuth;
90 changes: 90 additions & 0 deletions test/authentication/passwordless.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -492,4 +492,94 @@ describe('auth0.authentication', function() {
);
});
});

context('passwordless getChallenge', 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.passwordless.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/passwordless/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.passwordless.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/passwordless/challenge');
return new RequestMock({
body: {
state: '123abc'
},
headers: {
'Content-Type': 'application/json'
},
cb: function(cb) {
cb(new Error('error error error'));
}
});
});

this.auth0.passwordless.getChallenge((err, challenge) => {
expect(err.original.message).to.equal('error error error');
done();
});
});
});
});
});
Loading

0 comments on commit 05dbf4a

Please sign in to comment.