diff --git a/src/__tests__/engine/classic/__snapshots__/sign_up_pane.test.jsx.snap b/src/__tests__/engine/classic/__snapshots__/sign_up_pane.test.jsx.snap
index 6c2c15ed9..a24a007d7 100644
--- a/src/__tests__/engine/classic/__snapshots__/sign_up_pane.test.jsx.snap
+++ b/src/__tests__/engine/classic/__snapshots__/sign_up_pane.test.jsx.snap
@@ -214,6 +214,7 @@ exports[`SignUpPane shows the Captcha pane 1`] = `
/>
mockComponent('email_pane'));
jest.mock('field/password/password_pane', () => mockComponent('password_pane'));
@@ -8,7 +9,7 @@ jest.mock('field/username/username_pane', () => mockComponent('username_pane'));
jest.mock('field/custom_input', () => mockComponent('custom_input'));
jest.mock('core/index', () => ({
- captcha: jest.fn()
+ signupCaptcha: jest.fn()
}));
jest.mock('engine/classic', () => ({
@@ -38,6 +39,7 @@ describe('SignUpPane', () => {
str: (...keys) => keys.join(','),
html: (...keys) => keys.join(',')
},
+ flow: Flow.SIGNUP,
model: 'model',
emailInputPlaceholder: 'emailInputPlaceholder',
onlyEmail: true,
@@ -58,7 +60,7 @@ describe('SignUpPane', () => {
});
it('shows the Captcha pane', () => {
- require('core/index').captcha.mockReturnValue({
+ require('core/index').signupCaptcha.mockReturnValue({
get() {
return true;
}
@@ -72,7 +74,7 @@ describe('SignUpPane', () => {
});
it('hides the Captcha pane for SSO connections', () => {
- require('core/index').captcha.mockReturnValue({
+ require('core/index').signupCaptcha.mockReturnValue({
get() {
return true;
}
@@ -86,7 +88,7 @@ describe('SignUpPane', () => {
});
it('shows the Captcha pane for SSO (ADFS) connections', () => {
- require('core/index').captcha.mockReturnValue({
+ require('core/index').signupCaptcha.mockReturnValue({
get() {
return true;
}
diff --git a/src/connection/captcha.js b/src/connection/captcha.js
index 3d9e74d9a..c4880f02c 100644
--- a/src/connection/captcha.js
+++ b/src/connection/captcha.js
@@ -6,13 +6,14 @@ import webApi from '../core/web_api';
export const Flow = Object.freeze({
DEFAULT: 'default',
+ SIGNUP: 'signup',
PASSWORDLESS: 'passwordless',
PASSWORD_RESET: 'password_reset',
});
/**
* Return the captcha config object based on the type of flow.
- *
+ *
* @param {Object} m model
* @param {Flow} flow Which flow the captcha is being rendered in
*/
@@ -21,6 +22,8 @@ export function getCaptchaConfig(m, flow) {
return l.passwordResetCaptcha(m);
} else if (flow === Flow.PASSWORDLESS) {
return l.passwordlessCaptcha(m);
+ } else if (flow === Flow.SIGNUP) {
+ return l.signupCaptcha(m);
} else {
return l.captcha(m);
}
@@ -42,7 +45,7 @@ export function showMissingCaptcha(m, id, flow = Flow.DEFAULT) {
captchaConfig.get('provider') === 'hcaptcha' ||
captchaConfig.get('provider') === 'auth0_v2' ||
captchaConfig.get('provider') === 'friendly_captcha' ||
- captchaConfig.get('provider') === 'arkose'
+ captchaConfig.get('provider') === 'arkose'
) ? 'invalid_recaptcha' : 'invalid_captcha';
const errorMessage = i18n.html(m, ['error', 'login', captchaError]);
@@ -110,6 +113,15 @@ export function swapCaptcha(id, flow, wasInvalid, next) {
next();
}
});
+ } else if (flow === Flow.SIGNUP) {
+ return webApi.getSignupChallenge(id, (err, newCaptcha) => {
+ if (!err && newCaptcha) {
+ swap(updateEntity, 'lock', id, l.setSignupChallenge, newCaptcha, wasInvalid);
+ }
+ if (next) {
+ next();
+ }
+ });
} else {
return webApi.getChallenge(id, (err, newCaptcha) => {
if (!err && newCaptcha) {
diff --git a/src/connection/database/actions.js b/src/connection/database/actions.js
index e42948e35..a7a7965ef 100644
--- a/src/connection/database/actions.js
+++ b/src/connection/database/actions.js
@@ -88,9 +88,9 @@ export function signUp(id) {
autoLogin: shouldAutoLogin(m)
};
- const isCaptchaValid = setCaptchaParams(m, params, Flow.DEFAULT, fields);
+ const isCaptchaValid = setCaptchaParams(m, params, Flow.SIGNUP, fields);
if (!isCaptchaValid) {
- return showMissingCaptcha(m, id);
+ return showMissingCaptcha(m, id, Flow.SIGNUP);
}
if (databaseConnectionRequiresUsername(m)) {
@@ -131,7 +131,7 @@ export function signUp(id) {
const wasInvalidCaptcha = error && error.code === 'invalid_captcha';
- swapCaptcha(id, Flow.DEFAULT, wasInvalidCaptcha, () => {
+ swapCaptcha(id, Flow.SIGNUP, wasInvalidCaptcha, () => {
setTimeout(() => signUpError(id, error), 250);
});
};
@@ -290,7 +290,7 @@ export function resetPasswordSuccess(id) {
function resetPasswordError(id, error) {
const m = read(getEntity, 'lock', id);
let key = error.code;
-
+
if (error.code === 'invalid_captcha') {
const captchaConfig = l.passwordResetCaptcha(m);
key = (
@@ -302,7 +302,7 @@ function resetPasswordError(id, error) {
const errorMessage =
i18n.html(m, ['error', 'forgotPassword', key]) ||
i18n.html(m, ['error', 'forgotPassword', 'lock.fallback']);
-
+
swapCaptcha(id, Flow.PASSWORD_RESET, error.code === 'invalid_captcha', () => {
swap(updateEntity, 'lock', id, l.setSubmitting, false, errorMessage);
});
@@ -322,11 +322,11 @@ export function showLoginActivity(id, fields = ['password']) {
export function showSignUpActivity(id, fields = ['password']) {
const m = read(getEntity, 'lock', id);
- const captchaConfig = l.captcha(m);
+ const captchaConfig = l.signupCaptcha(m);
if (captchaConfig && captchaConfig.get('provider') === 'arkose') {
swap(updateEntity, 'lock', id, setScreen, 'signUp', fields);
} else {
- swapCaptcha(id, 'login', false, () => {
+ swapCaptcha(id, Flow.SIGNUP, false, () => {
swap(updateEntity, 'lock', id, setScreen, 'signUp', fields);
});
}
@@ -338,7 +338,7 @@ export function showResetPasswordActivity(id, fields = ['password']) {
if (captchaConfig && captchaConfig.get('provider') === 'arkose') {
swap(updateEntity, 'lock', id, setScreen, 'forgotPassword', fields);
} else {
- swapCaptcha(id, 'login', false, () => {
+ swapCaptcha(id, Flow.PASSWORD_RESET, false, () => {
swap(updateEntity, 'lock', id, setScreen, 'forgotPassword', fields);
});
}
diff --git a/src/core/index.js b/src/core/index.js
index 817cf9c91..4afd08749 100644
--- a/src/core/index.js
+++ b/src/core/index.js
@@ -421,6 +421,11 @@ export function setCaptcha(m, value, wasInvalid) {
return set(m, 'captcha', Immutable.fromJS(value));
}
+export function setSignupChallenge(m, value, wasInvalid) {
+ m = captchaField.reset(m, wasInvalid);
+ return set(m, 'signupCaptcha', Immutable.fromJS(value));
+}
+
export function setPasswordlessCaptcha(m, value, wasInvalid) {
m = captchaField.reset(m, wasInvalid);
return set(m, 'passwordlessCaptcha', Immutable.fromJS(value));
@@ -435,6 +440,10 @@ export function captcha(m) {
return get(m, 'captcha');
}
+export function signupCaptcha(m) {
+ return get(m, 'signupCaptcha');
+}
+
export function passwordlessCaptcha(m) {
return get(m, 'passwordlessCaptcha');
}
diff --git a/src/core/web_api.js b/src/core/web_api.js
index 4000d7d7b..e1a4300dd 100644
--- a/src/core/web_api.js
+++ b/src/core/web_api.js
@@ -60,6 +60,10 @@ class Auth0WebAPI {
return this.clients[lockID].getChallenge(callback);
}
+ getSignupChallenge(lockID, callback) {
+ return this.clients[lockID].getSignupChallenge(callback);
+ }
+
getPasswordlessChallenge(lockID, callback) {
return this.clients[lockID].getPasswordlessChallenge(callback);
}
diff --git a/src/core/web_api/p2_api.js b/src/core/web_api/p2_api.js
index 8cc5f1484..7efd5a926 100644
--- a/src/core/web_api/p2_api.js
+++ b/src/core/web_api/p2_api.js
@@ -195,6 +195,10 @@ class Auth0APIClient {
return this.client.client.getChallenge(...params);
}
+ getSignupChallenge(...params) {
+ return this.client.client.dbConnection.getSignupChallenge(...params);
+ }
+
getPasswordlessChallenge(...params) {
return this.client.client.passwordless.getChallenge(...params);
}
diff --git a/src/engine/classic/sign_up_pane.jsx b/src/engine/classic/sign_up_pane.jsx
index 621afdd9e..1911778fd 100644
--- a/src/engine/classic/sign_up_pane.jsx
+++ b/src/engine/classic/sign_up_pane.jsx
@@ -64,10 +64,10 @@ export default class SignUpPane extends React.Component {
));
const captchaPane =
- l.captcha(model) &&
- l.captcha(model).get('required') &&
+ l.signupCaptcha(model) &&
+ l.signupCaptcha(model).get('required') &&
(isHRDDomain(model, databaseUsernameValue(model)) || !sso) ? (
- swapCaptcha(l.id(model), Flow.DEFAULT, false)} />
+ swapCaptcha(l.id(model), Flow.SIGNUP, false)} />
) : null;
const passwordPane = !onlyEmail && (
diff --git a/test/captcha_signup.test.js b/test/captcha_signup.test.js
index fcbcf9bac..c1f4afb76 100644
--- a/test/captcha_signup.test.js
+++ b/test/captcha_signup.test.js
@@ -4,8 +4,7 @@ import en from '../src/i18n/en';
const lockOpts = {
allowedConnections: ['db'],
- rememberLastLogin: false,
- initialScreen: 'signUp'
+ rememberLastLogin: false
};
const svgCaptchaRequiredResponse1 = {
@@ -33,22 +32,26 @@ describe('captcha on signup', function () {
describe('svg-captcha', () => {
describe('when the api returns a new challenge', function () {
beforeEach(function (done) {
- this.stub = h.stubGetChallenge([svgCaptchaRequiredResponse1, svgCaptchaRequiredResponse2]);
- this.lock = h.displayLock('', lockOpts, done);
+ this.stub = h.stubGetChallenge({ required: false });
+ this.stub = h.stubGetSignupChallenge([svgCaptchaRequiredResponse1, svgCaptchaRequiredResponse2]);
+ this.lock = h.displayLock('', lockOpts, () => {
+ h.clickSignUpTab();
+ h.waitUntilExists(this.lock, '.auth0-lock-with-terms', () => {
+ done();
+ });
+ });
});
afterEach(function () {
this.lock.hide();
});
- it('sign-up tab should be active', function (done) {
- h.waitUntilExists(this.lock, '.auth0-lock-tabs-current', () => {
- expect(h.isSignUpTabCurrent(this.lock)).to.be.ok();
- done();
- });
+ it('sign-up tab should be active', function () {
+ expect(h.isSignUpTabCurrent(this.lock)).to.be.ok();
});
it('should show the captcha input', function (done) {
+ expect(h.isSignUpTabCurrent(this.lock)).to.be.ok();
setTimeout(() => {
expect(h.qInput(this.lock, 'captcha', false)).to.be.ok();
done();
@@ -56,16 +59,13 @@ describe('captcha on signup', function () {
});
it('should require another challenge when clicking the refresh button', function (done) {
- h.waitUntilExists(this.lock, '.auth0-lock-captcha-refresh', () => {
- h.clickRefreshCaptchaButton(this.lock);
-
- setTimeout(() => {
- expect(h.q(this.lock, '.auth0-lock-captcha-image').style.backgroundImage).to.equal(
- `url("${svgCaptchaRequiredResponse2.image}")`
- );
- done();
- }, 200);
- });
+ h.clickRefreshCaptchaButton(this.lock);
+ setTimeout(() => {
+ expect(h.q(this.lock, '.auth0-lock-captcha-image').style.backgroundImage).to.equal(
+ `url("${svgCaptchaRequiredResponse2.image}")`
+ );
+ done();
+ }, 200);
});
it('should submit the captcha provided by the user', function (done) {
@@ -86,12 +86,16 @@ describe('captcha on signup', function () {
});
});
- describe('when the challenge api returns required: false', function () {
+ describe('when the challenge api returns required: false for signup', function () {
beforeEach(function (done) {
- h.stubGetChallenge({
- required: false
+ h.stubGetChallenge([svgCaptchaRequiredResponse1, svgCaptchaRequiredResponse2]);
+ h.stubGetSignupChallenge({ required: false });
+ this.lock = h.displayLock('', lockOpts, () => {
+ h.clickSignUpTab();
+ h.waitUntilExists(this.lock, '.auth0-lock-with-terms', () => {
+ done();
+ });
});
- this.lock = h.displayLock('', lockOpts, done);
});
afterEach(function () {
@@ -110,7 +114,7 @@ describe('captcha on signup', function () {
});
h.waitForEmailAndPasswordInput(this.lock, () => {
- h.stubGetChallenge(svgCaptchaRequiredResponse1);
+ h.stubGetSignupChallenge(svgCaptchaRequiredResponse1);
h.fillEmailInput(this.lock, 'someone@example.com');
h.fillComplexPassword(this.lock);
h.submitForm(this.lock);
@@ -127,8 +131,14 @@ describe('captcha on signup', function () {
describe('recaptcha', () => {
describe('when the api returns a new challenge', function () {
beforeEach(function (done) {
- this.stub = h.stubGetChallenge([recaptchav2Response]);
- this.lock = h.displayLock('', lockOpts, done);
+ this.stub = h.stubGetChallenge({ required: false });
+ this.stub = h.stubGetSignupChallenge([recaptchav2Response]);
+ this.lock = h.displayLock('', lockOpts, () => {
+ h.clickSignUpTab();
+ h.waitUntilExists(this.lock, '.auth0-lock-with-terms', () => {
+ done();
+ });
+ });
});
afterEach(function () {
@@ -156,12 +166,17 @@ describe('captcha on signup', function () {
});
describe('when the challenge api returns required: false', function () {
- let notRequiredStub;
+ let notRequiredStub
+ let loginGetChallengeStub;
beforeEach(function (done) {
- notRequiredStub = h.stubGetChallenge({
- required: false
+ loginGetChallengeStub = h.stubGetChallenge([recaptchav2Response]);
+ notRequiredStub = h.stubGetSignupChallenge({ required: false });
+ this.lock = h.displayLock('', lockOpts, () => {
+ h.clickSignUpTab();
+ h.waitUntilExists(this.lock, '.auth0-lock-with-terms', () => {
+ done();
+ });
});
- this.lock = h.displayLock('', lockOpts, done);
});
afterEach(function () {
@@ -181,7 +196,7 @@ describe('captcha on signup', function () {
setTimeout(done, 260);
});
- challengeStub = h.stubGetChallenge(recaptchav2Response);
+ challengeStub = h.stubGetSignupChallenge([recaptchav2Response]);
h.fillEmailInput(this.lock, 'someone@example.com');
h.fillComplexPassword(this.lock);
h.submitForm(this.lock);
@@ -190,6 +205,7 @@ describe('captcha on signup', function () {
it('should call the challenge api again and show the input', function () {
expect(notRequiredStub.calledOnce).to.be.true;
expect(challengeStub.calledOnce).to.be.true;
+ expect(loginGetChallengeStub.calledOnce).to.be.false;
expect(h.q(this.lock, '.auth0-lock-recaptchav2')).to.be.ok();
});
});
diff --git a/test/helper/ui.js b/test/helper/ui.js
index 14d8519be..5f3f14891 100644
--- a/test/helper/ui.js
+++ b/test/helper/ui.js
@@ -33,6 +33,7 @@ export const stubWebApis = () => {
cb(null, ssoData);
});
stubGetChallenge();
+ stubGetSignupChallenge();
stubI18n();
};
@@ -79,6 +80,7 @@ export const restoreWebApis = () => {
webApi.signUp.restore();
}
webApi.getChallenge.restore();
+ webApi.getSignupChallenge.restore();
gravatarProvider.displayName.restore();
gravatarProvider.url.restore();
ClientSettings.fetchClientSettings.restore();
@@ -286,6 +288,13 @@ export const clickRefreshCaptchaButton = (lock, connection) =>
export const clickSocialConnectionButton = (lock, connection) =>
clickFn(lock, `.auth0-lock-social-button[data-provider='${connection}']`);
+
+export const clickSignUpTab = (lock) => {
+ // there is no id for the unselected tab (Login is selected by default)
+ const signUpTab = window.document['querySelector']('.auth0-lock-tabs > li:nth-child(2) > a');
+ Simulate.click(signUpTab, {});
+};
+
const fillInput = (lock, name, str) => {
Simulate.change(qInput(lock, name, true), { target: { value: str } });
};
@@ -469,3 +478,15 @@ export const stubGetChallenge = (result = { required: false }) => {
callback(null, result);
});
};
+
+export const stubGetSignupChallenge = (result = { required: false }) => {
+ if (typeof webApi.getSignupChallenge.restore === 'function') {
+ webApi.getSignupChallenge.restore();
+ }
+ return stub(webApi, 'getSignupChallenge', (lockID, callback) => {
+ if (Array.isArray(result)) {
+ return callback(null, result.shift());
+ }
+ callback(null, result);
+ });
+};