Skip to content

Commit

Permalink
Support captchas in reset password flow
Browse files Browse the repository at this point in the history
  • Loading branch information
srijonsaha committed Apr 25, 2024
1 parent aaae03f commit f84445c
Show file tree
Hide file tree
Showing 15 changed files with 115 additions and 39 deletions.
61 changes: 46 additions & 15 deletions src/connection/captcha.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,33 @@ import * as i18n from '../i18n';
import { swap, updateEntity } from '../store/index';
import webApi from '../core/web_api';

/**
* Return the captcha config object based on the type of flow.
*
* @param {Object} m model
* @param {Boolean} isPasswordless Whether the captcha is being rendered in a passwordless flow
* @param {Boolean} isPasswordReset Whether the captcha is being rendered in a password reset flow
*/
export function getCaptchaConfig(m, isPasswordless, isPasswordReset) {
if (isPasswordReset) {
return l.resetPasswordCaptcha(m);
} else if (isPasswordless) {
return l.passwordlessCaptcha(m);
} else {
return l.captcha(m);
}
}

/**
* Display the error message of missing captcha in the header of lock.
*
* @param {Object} m model
* @param {Number} id
* @param {Boolean} isPasswordless Whether the captcha is being rendered in a passwordless flow
* @param {Boolean} isPasswordReset Whether the captcha is being rendered in a password reset flow
*/
export function showMissingCaptcha(m, id, isPasswordless = false) {
const captchaConfig = isPasswordless ? l.passwordlessCaptcha(m) : l.captcha(m);
export function showMissingCaptcha(m, id, isPasswordless = false, isPasswordReset = false) {
const captchaConfig = getCaptchaConfig(m, isPasswordless, isPasswordReset);

const captchaError = (
captchaConfig.get('provider') === 'recaptcha_v2' ||
Expand All @@ -38,19 +56,21 @@ export function showMissingCaptcha(m, id, isPasswordless = false) {
* @param {Object} m model
* @param {Object} params
* @param {Boolean} isPasswordless Whether the captcha is being rendered in a passwordless flow
* @param {Boolean} isPasswordReset Whether the captcha is being rendered in a password reset flow
* @param {Object} fields
*
* @returns {Boolean} returns true if is required and missing the response from the user
*/
export function setCaptchaParams(m, params, isPasswordless, fields) {
const captchaConfig = isPasswordless ? l.passwordlessCaptcha(m) : l.captcha(m);
export function setCaptchaParams(m, params, isPasswordless, isPasswordReset, fields) {
const captchaConfig = getCaptchaConfig(m, isPasswordless, isPasswordReset);
const isCaptchaRequired = captchaConfig && captchaConfig.get('required');

if (!isCaptchaRequired) {
return true;
}
const captcha = c.getFieldValue(m, 'captcha');
//captcha required and missing
console.log('captcha: ', captcha);
// captcha required and missing
if (!captcha) {
return false;
}
Expand All @@ -65,11 +85,21 @@ export function setCaptchaParams(m, params, isPasswordless, fields) {
*
* @param {number} id The id of the Lock instance.
* @param {Boolean} isPasswordless Whether the captcha is being rendered in a passwordless flow.
* @param {Boolean} isPasswordReset Whether the captcha is being rendered in a password reset flow.
* @param {boolean} wasInvalid A boolean indicating if the previous captcha was invalid.
* @param {Function} [next] A callback.
*/
export function swapCaptcha(id, isPasswordless, wasInvalid, next) {
if (isPasswordless) {
export function swapCaptcha(id, isPasswordless, isPasswordReset, wasInvalid, next) {
if (isPasswordReset) {
return webApi.getResetPasswordChallenge(id, (err, newCaptcha) => {
if (!err && newCaptcha) {
swap(updateEntity, 'lock', id, l.setResetPasswordCaptcha, newCaptcha, wasInvalid);
}
if (next) {
next();
}
});
} else if (isPasswordless) {
return webApi.getPasswordlessChallenge(id, (err, newCaptcha) => {
if (!err && newCaptcha) {
swap(updateEntity, 'lock', id, l.setPasswordlessCaptcha, newCaptcha, wasInvalid);
Expand All @@ -78,13 +108,14 @@ export function swapCaptcha(id, isPasswordless, wasInvalid, next) {
next();
}
});
} else {
return webApi.getChallenge(id, (err, newCaptcha) => {
if (!err && newCaptcha) {
swap(updateEntity, 'lock', id, l.setCaptcha, newCaptcha, wasInvalid);
}
if (next) {
next();
}
});
}
return webApi.getChallenge(id, (err, newCaptcha) => {
if (!err && newCaptcha) {
swap(updateEntity, 'lock', id, l.setCaptcha, newCaptcha, wasInvalid);
}
if (next) {
next();
}
});
}
23 changes: 15 additions & 8 deletions src/connection/database/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function logIn(id, needsMFA = false) {
};

const fields = [usernameField, 'password'];
const isCaptchaValid = setCaptchaParams(m, params, false, fields);
const isCaptchaValid = setCaptchaParams(m, params, false, false, fields);

if (!isCaptchaValid) {
return showMissingCaptcha(m, id);
Expand All @@ -53,7 +53,7 @@ export function logIn(id, needsMFA = false) {

if (error) {
const wasInvalid = error && error.code === 'invalid_captcha';
return swapCaptcha(id, false, wasInvalid, next);
return swapCaptcha(id, false, false, wasInvalid, next);
}

next();
Expand Down Expand Up @@ -88,7 +88,7 @@ export function signUp(id) {
autoLogin: shouldAutoLogin(m)
};

const isCaptchaValid = setCaptchaParams(m, params, false, fields);
const isCaptchaValid = setCaptchaParams(m, params, false, false, fields);
if (!isCaptchaValid) {
return showMissingCaptcha(m, id);
}
Expand Down Expand Up @@ -131,7 +131,7 @@ export function signUp(id) {

const wasInvalidCaptcha = error && error.code === 'invalid_captcha';

swapCaptcha(id, false, wasInvalidCaptcha, () => {
swapCaptcha(id, false, false, wasInvalidCaptcha, () => {
setTimeout(() => signUpError(id, error), 250);
});
};
Expand Down Expand Up @@ -218,7 +218,7 @@ export function signUpError(id, error) {

if (errorKey === 'invalid_captcha') {
errorMessage = i18n.html(m, ['error', 'login', errorKey]);
return swapCaptcha(id, false, true, () => {
return swapCaptcha(id, false, false, true, () => {
swap(updateEntity, 'lock', id, l.setSubmitting, false, errorMessage);
});
}
Expand All @@ -244,7 +244,12 @@ export function resetPassword(id) {
email: c.getFieldValue(m, 'email')
};

webApi.resetPassword(id, params, (error, ...args) => {
const isCaptchaValid = setCaptchaParams(m, params, false, true, ['email']);
if (!isCaptchaValid) {
return showMissingCaptcha(m, id, false, true);
}

webApi.resetPassword(id, params, error => {
if (error) {
setTimeout(() => resetPasswordError(id, error), 250);
} else {
Expand Down Expand Up @@ -284,8 +289,10 @@ function resetPasswordError(id, error) {
const errorMessage =
i18n.html(m, ['error', 'forgotPassword', error.code]) ||
i18n.html(m, ['error', 'forgotPassword', 'lock.fallback']);

swap(updateEntity, 'lock', id, l.setSubmitting, false, errorMessage);

swapCaptcha(id, false, true, error.code === 'invalid_captcha', () => {
swap(updateEntity, 'lock', id, l.setSubmitting, false, errorMessage);
});
}

export function showLoginActivity(id, fields = ['password']) {
Expand Down
2 changes: 1 addition & 1 deletion src/connection/database/login_pane.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export default class LoginPane extends React.Component {
l.captcha(lock) &&
l.captcha(lock).get('required') &&
(isHRDDomain(lock, databaseUsernameValue(lock)) || !sso) ? (
<CaptchaPane i18n={i18n} lock={lock} onReload={() => swapCaptcha(l.id(lock), false, false)} />
<CaptchaPane i18n={i18n} lock={lock} onReload={() => swapCaptcha(l.id(lock), false, false, false)} />
) : null;

const dontRememberPassword =
Expand Down
9 changes: 9 additions & 0 deletions src/connection/database/reset_password_pane.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import PropTypes from 'prop-types';
import React from 'react';
import EmailPane from '../../field/email/email_pane';
import * as l from '../../core/index';
import CaptchaPane from '../../field/captcha/captcha_pane';
import { swapCaptcha } from '../../connection/captcha';

export default class ResetPasswordPane extends React.Component {
static propTypes = {
Expand All @@ -12,6 +14,12 @@ export default class ResetPasswordPane extends React.Component {
render() {
const { emailInputPlaceholder, header, i18n, lock } = this.props;

const captchaPane =
l.resetPasswordCaptcha(lock) &&
l.resetPasswordCaptcha(lock).get('required') ? (
<CaptchaPane i18n={i18n} lock={lock} onReload={() => swapCaptcha(l.id(lock), false, true, false, null)} />
) : null;

return (
<div>
{header}
Expand All @@ -21,6 +29,7 @@ export default class ResetPasswordPane extends React.Component {
placeholder={emailInputPlaceholder}
strictValidation={false}
/>
{captchaPane}
</div>
);
}
Expand Down
4 changes: 2 additions & 2 deletions src/connection/enterprise/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export function logIn(id) {
return logInSSO(id, ssoConnection, params);
}

const isCaptchaValid = setCaptchaParams(m, params, false, fields);
const isCaptchaValid = setCaptchaParams(m, params, false, false, fields);

if (!isCaptchaValid && !ssoConnection) {
return showMissingCaptcha(m, id);
Expand Down Expand Up @@ -85,7 +85,7 @@ function logInActiveFlow(id, params) {
},
(id, error, fields, next) => {
const wasCaptchaInvalid = error && error.code === 'invalid captcha';
swapCaptcha(id, false, wasCaptchaInvalid, next);
swapCaptcha(id, false, false, wasCaptchaInvalid, next);
}
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/connection/enterprise/hrd_pane.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default class HRDPane extends React.Component {

const captchaPane =
l.captcha(model) && l.captcha(model).get('required') ? (
<CaptchaPane i18n={i18n} lock={model} onReload={() => swapCaptcha(l.id(model), false, false)} />
<CaptchaPane i18n={i18n} lock={model} onReload={() => swapCaptcha(l.id(model), false, false, false)} />
) : null;

return (
Expand Down
8 changes: 4 additions & 4 deletions src/connection/passwordless/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function getErrorMessage(m, id, error) {

function swapCaptchaAfterError(id, error){
const wasCaptchaInvalid = error && error.code === 'invalid_captcha';
swapCaptcha(id, true, wasCaptchaInvalid);
swapCaptcha(id, true, false, wasCaptchaInvalid);
}

export function requestPasswordlessEmail(id) {
Expand Down Expand Up @@ -102,7 +102,7 @@ function sendEmail(m, id, successFn, errorFn) {
if (isSendLink(m) && !l.auth.params(m).isEmpty()) {
params.authParams = l.auth.params(m).toJS();
}
const isCaptchaValid = setCaptchaParams(m, params, true, []);
const isCaptchaValid = setCaptchaParams(m, params, true, false, []);

if (!isCaptchaValid) {
return showMissingCaptcha(m, id, true);
Expand All @@ -124,7 +124,7 @@ export function sendSMS(id) {
phoneNumber: phoneNumberWithDiallingCode(m),
send: send(m)
};
const isCaptchaValid = setCaptchaParams(m, params, true, []);
const isCaptchaValid = setCaptchaParams(m, params, true, false, []);
if (!isCaptchaValid) {
return showMissingCaptcha(m, id, true);
}
Expand Down Expand Up @@ -187,7 +187,7 @@ export function logIn(id) {

export function restart(id) {
swap(updateEntity, 'lock', id, restartPasswordless);
swapCaptcha(id, true, false);
swapCaptcha(id, true, false, false);
}

export function toggleTermsAcceptance(id) {
Expand Down
11 changes: 10 additions & 1 deletion src/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,11 @@ export function setPasswordlessCaptcha(m, value, wasInvalid) {
return set(m, 'passwordlessCaptcha', Immutable.fromJS(value));
}

export function setResetPasswordCaptcha(m, value, wasInvalid) {
m = captchaField.reset(m, wasInvalid);
return set(m, 'resetPasswordCaptcha', Immutable.fromJS(value));
}

export function captcha(m) {
return get(m, 'captcha');
}
Expand All @@ -434,6 +439,10 @@ export function passwordlessCaptcha(m) {
return get(m, 'passwordlessCaptcha');
}

export function resetPasswordCaptcha(m) {
return get(m, 'resetPasswordCaptcha');
}

export function prefill(m) {
return get(m, 'prefill', {});
}
Expand Down Expand Up @@ -585,7 +594,7 @@ export function loginErrorMessage(m, error, type) {
currentCaptcha.get('provider') === 'recaptcha_enterprise' ||
currentCaptcha.get('provider') === 'hcaptcha' ||
currentCaptcha.get('provider') === 'auth0_v2' ||
captchaConfig.get('provider') === 'friendly_captcha'
currentCaptcha.get('provider') === 'friendly_captcha'
)) {
code = 'invalid_recaptcha';
}
Expand Down
11 changes: 10 additions & 1 deletion src/core/remote_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as l from './index';
import { isADEnabled } from '../connection/enterprise'; // shouldn't depend on this
import sync, { isSuccess } from '../sync';
import webApi from './web_api';
import { setCaptcha, setPasswordlessCaptcha } from '../core/index';
import { setCaptcha, setPasswordlessCaptcha, setResetPasswordCaptcha } from '../core/index';

export function syncRemoteData(m) {
if (l.useTenantInfo(m)) {
Expand Down Expand Up @@ -69,6 +69,15 @@ export function syncRemoteData(m) {
successFn: setPasswordlessCaptcha
});

m = sync(m, 'resetPasswordCaptcha', {
syncFn: (m, cb) => {
webApi.getResetPasswordChallenge(m.get('id'), (err, r) => {
cb(null, r);
});
},
successFn: setResetPasswordCaptcha
});


return m;
}
4 changes: 4 additions & 0 deletions src/core/web_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ class Auth0WebAPI {
return this.clients[lockID].getPasswordlessChallenge(callback);
}

getResetPasswordChallenge(lockID, callback) {
return this.clients[lockID].getResetPasswordChallenge(callback);
}

getSSOData(lockID, ...args) {
return this.clients[lockID].getSSOData(...args);
}
Expand Down
4 changes: 4 additions & 0 deletions src/core/web_api/p2_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ class Auth0APIClient {
return this.client.client.passwordless.getChallenge(...params);
}

getResetPasswordChallenge(...params) {
return this.client.client.dbConnection.getChallenge(...params);
}

getUserCountry(cb) {
return this.client.client.getUserCountry(cb);
}
Expand Down
2 changes: 1 addition & 1 deletion src/engine/classic/sign_up_pane.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export default class SignUpPane extends React.Component {
l.captcha(model) &&
l.captcha(model).get('required') &&
(isHRDDomain(model, databaseUsernameValue(model)) || !sso) ? (
<CaptchaPane i18n={i18n} lock={model} onReload={() => swapCaptcha(l.id(model), false, false)} />
<CaptchaPane i18n={i18n} lock={model} onReload={() => swapCaptcha(l.id(model), false, false, false)} />
) : null;

const passwordPane = !onlyEmail && (
Expand Down
2 changes: 1 addition & 1 deletion src/engine/passwordless/social_or_email_login_screen.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const Component = ({ i18n, model }) => {

const captchaPane = l.passwordlessCaptcha(model) && l.passwordlessCaptcha(model).get('required')
? (
<CaptchaPane i18n={i18n} lock={model} isPasswordless={true} onReload={() => swapCaptcha(l.id(model), true, false)} />
<CaptchaPane i18n={i18n} lock={model} isPasswordless={true} onReload={() => swapCaptcha(l.id(model), true, false, false)} />
) : null;

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const Component = ({ i18n, model }) => {

const captchaPane = l.passwordlessCaptcha(model) && l.passwordlessCaptcha(model).get('required')
? (
<CaptchaPane i18n={i18n} lock={model} isPasswordless={true} onReload={() => swapCaptcha(l.id(model), true, false)} />
<CaptchaPane i18n={i18n} lock={model} isPasswordless={true} onReload={() => swapCaptcha(l.id(model), true, false, false)} />
) : null;

const separator = social && phoneNumber ? <PaneSeparator /> : null;
Expand Down
Loading

0 comments on commit f84445c

Please sign in to comment.