Skip to content

Commit 0854d9a

Browse files
authored
[IAMRISK-3539] Use signup classic endpoint for captcha (#2587)
### Changes * Uses Signup Captcha enforcement Policy for Signup instead of Login/Default enforcement policy (new capability) * Uses reset_password enforcement Policy for reset_password (bug) * Isolates calls for passwordless and reset password enforcement policy to specific flows instead of on Lock load (bug) ### References https://auth0team.atlassian.net/browse/IAMRISK-4032 https://auth0team.atlassian.net/browse/IAMRISK-4161 ### Testing https://oktawiki.atlassian.net/wiki/spaces/IAMCA/pages/3113844770/Bot+Detection+Signup+Classic+UL+Testing+Documentation * [x] This change adds unit test coverage * [x] This change adds integration test coverage * [x] This change has been tested on the latest version of the platform/language ### 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 code quality tools/guidelines have been run/followed * [x] All relevant assets have been compiled
2 parents 1cf70cc + 05e6b67 commit 0854d9a

File tree

16 files changed

+161
-83
lines changed

16 files changed

+161
-83
lines changed

Diff for: package-lock.json

+12-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,11 @@
121121
"webpack-dev-server": "^4.11.1"
122122
},
123123
"dependencies": {
124-
"auth0-js": "^9.26.0",
124+
"auth0-js": "^9.27.0",
125125
"auth0-password-policies": "^1.0.2",
126126
"blueimp-md5": "^2.19.0",
127127
"classnames": "^2.3.2",
128-
"dompurify": "^2.3.12",
128+
"dompurify": "^2.5.4",
129129
"immutable": "^3.7.6",
130130
"jsonp": "^0.2.1",
131131
"password-sheriff": "^1.1.1",

Diff for: src/__tests__/connection/passwordless/__snapshots__/passwordless.test.js.snap

+13-2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ exports[`passwordless connection initPasswordless() calls initNS with showTerms
8989
`;
9090

9191
exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 1`] = `
92+
[
93+
undefined,
94+
"passwordlessCaptcha",
95+
{
96+
"successFn": undefined,
97+
"syncFn": [Function],
98+
},
99+
]
100+
`;
101+
102+
exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 2`] = `
92103
[
93104
undefined,
94105
"location",
@@ -100,14 +111,14 @@ exports[`passwordless connection initPasswordless() should call webAPI.getUserCo
100111
]
101112
`;
102113

103-
exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 2`] = `
114+
exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 3`] = `
104115
[
105116
"id",
106117
"cb",
107118
]
108119
`;
109120

110-
exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 3`] = `
121+
exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 4`] = `
111122
[
112123
"model",
113124
"en",

Diff for: src/__tests__/connection/passwordless/passwordless.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ describe('passwordless connection', () => {
8989
it('should call webAPI.getUserCountry when there is no default location', () => {
9090
initPasswordless(null, {});
9191
const sync = require('sync');
92-
expectMockToMatch(sync, 1);
92+
expectMockToMatch(sync, 2);
9393

94-
const { syncFn, successFn } = sync.mock.calls[0][2];
94+
const { syncFn, successFn } = sync.mock.calls[1][2];
9595
syncFn(null, 'cb');
9696
expectMockToMatch(require('core/web_api').getUserCountry, 1);
9797

Diff for: src/__tests__/engine/classic/__snapshots__/sign_up_pane.test.jsx.snap

+2
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ exports[`SignUpPane shows the Captcha pane 1`] = `
214214
/>
215215
<CaptchaPane
216216
error={false}
217+
flow="signup"
217218
i18n={
218219
{
219220
"html": [Function],
@@ -241,6 +242,7 @@ exports[`SignUpPane shows the Captcha pane for SSO (ADFS) connections 1`] = `
241242
/>
242243
<CaptchaPane
243244
error={false}
245+
flow="signup"
244246
i18n={
245247
{
246248
"html": [Function],

Diff for: src/__tests__/engine/classic/sign_up_pane.test.jsx

+6-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React from 'react';
22
import { expectComponent, mockComponent } from 'testUtils';
33
import { expectShallowComponent } from '../../testUtils';
4+
import { Flow } from '../../../connection/captcha';
45

56
jest.mock('field/email/email_pane', () => mockComponent('email_pane'));
67
jest.mock('field/password/password_pane', () => mockComponent('password_pane'));
78
jest.mock('field/username/username_pane', () => mockComponent('username_pane'));
89
jest.mock('field/custom_input', () => mockComponent('custom_input'));
910

1011
jest.mock('core/index', () => ({
11-
captcha: jest.fn()
12+
signupCaptcha: jest.fn()
1213
}));
1314

1415
jest.mock('engine/classic', () => ({
@@ -38,6 +39,7 @@ describe('SignUpPane', () => {
3839
str: (...keys) => keys.join(','),
3940
html: (...keys) => keys.join(',')
4041
},
42+
flow: Flow.SIGNUP,
4143
model: 'model',
4244
emailInputPlaceholder: 'emailInputPlaceholder',
4345
onlyEmail: true,
@@ -58,7 +60,7 @@ describe('SignUpPane', () => {
5860
});
5961

6062
it('shows the Captcha pane', () => {
61-
require('core/index').captcha.mockReturnValue({
63+
require('core/index').signupCaptcha.mockReturnValue({
6264
get() {
6365
return true;
6466
}
@@ -72,7 +74,7 @@ describe('SignUpPane', () => {
7274
});
7375

7476
it('hides the Captcha pane for SSO connections', () => {
75-
require('core/index').captcha.mockReturnValue({
77+
require('core/index').signupCaptcha.mockReturnValue({
7678
get() {
7779
return true;
7880
}
@@ -86,7 +88,7 @@ describe('SignUpPane', () => {
8688
});
8789

8890
it('shows the Captcha pane for SSO (ADFS) connections', () => {
89-
require('core/index').captcha.mockReturnValue({
91+
require('core/index').signupCaptcha.mockReturnValue({
9092
get() {
9193
return true;
9294
}

Diff for: src/connection/captcha.js

+14-2
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import webApi from '../core/web_api';
66

77
export const Flow = Object.freeze({
88
DEFAULT: 'default',
9+
SIGNUP: 'signup',
910
PASSWORDLESS: 'passwordless',
1011
PASSWORD_RESET: 'password_reset',
1112
});
1213

1314
/**
1415
* Return the captcha config object based on the type of flow.
15-
*
16+
*
1617
* @param {Object} m model
1718
* @param {Flow} flow Which flow the captcha is being rendered in
1819
*/
@@ -21,6 +22,8 @@ export function getCaptchaConfig(m, flow) {
2122
return l.passwordResetCaptcha(m);
2223
} else if (flow === Flow.PASSWORDLESS) {
2324
return l.passwordlessCaptcha(m);
25+
} else if (flow === Flow.SIGNUP) {
26+
return l.signupCaptcha(m);
2427
} else {
2528
return l.captcha(m);
2629
}
@@ -42,7 +45,7 @@ export function showMissingCaptcha(m, id, flow = Flow.DEFAULT) {
4245
captchaConfig.get('provider') === 'hcaptcha' ||
4346
captchaConfig.get('provider') === 'auth0_v2' ||
4447
captchaConfig.get('provider') === 'friendly_captcha' ||
45-
captchaConfig.get('provider') === 'arkose'
48+
captchaConfig.get('provider') === 'arkose'
4649
) ? 'invalid_recaptcha' : 'invalid_captcha';
4750

4851
const errorMessage = i18n.html(m, ['error', 'login', captchaError]);
@@ -110,6 +113,15 @@ export function swapCaptcha(id, flow, wasInvalid, next) {
110113
next();
111114
}
112115
});
116+
} else if (flow === Flow.SIGNUP) {
117+
return webApi.getSignupChallenge(id, (err, newCaptcha) => {
118+
if (!err && newCaptcha) {
119+
swap(updateEntity, 'lock', id, l.setSignupChallenge, newCaptcha, wasInvalid);
120+
}
121+
if (next) {
122+
next();
123+
}
124+
});
113125
} else {
114126
return webApi.getChallenge(id, (err, newCaptcha) => {
115127
if (!err && newCaptcha) {

Diff for: src/connection/database/actions.js

+8-8
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ export function signUp(id) {
8888
autoLogin: shouldAutoLogin(m)
8989
};
9090

91-
const isCaptchaValid = setCaptchaParams(m, params, Flow.DEFAULT, fields);
91+
const isCaptchaValid = setCaptchaParams(m, params, Flow.SIGNUP, fields);
9292
if (!isCaptchaValid) {
93-
return showMissingCaptcha(m, id);
93+
return showMissingCaptcha(m, id, Flow.SIGNUP);
9494
}
9595

9696
if (databaseConnectionRequiresUsername(m)) {
@@ -131,7 +131,7 @@ export function signUp(id) {
131131

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

134-
swapCaptcha(id, Flow.DEFAULT, wasInvalidCaptcha, () => {
134+
swapCaptcha(id, Flow.SIGNUP, wasInvalidCaptcha, () => {
135135
setTimeout(() => signUpError(id, error), 250);
136136
});
137137
};
@@ -290,7 +290,7 @@ export function resetPasswordSuccess(id) {
290290
function resetPasswordError(id, error) {
291291
const m = read(getEntity, 'lock', id);
292292
let key = error.code;
293-
293+
294294
if (error.code === 'invalid_captcha') {
295295
const captchaConfig = l.passwordResetCaptcha(m);
296296
key = (
@@ -302,7 +302,7 @@ function resetPasswordError(id, error) {
302302
const errorMessage =
303303
i18n.html(m, ['error', 'forgotPassword', key]) ||
304304
i18n.html(m, ['error', 'forgotPassword', 'lock.fallback']);
305-
305+
306306
swapCaptcha(id, Flow.PASSWORD_RESET, error.code === 'invalid_captcha', () => {
307307
swap(updateEntity, 'lock', id, l.setSubmitting, false, errorMessage);
308308
});
@@ -322,11 +322,11 @@ export function showLoginActivity(id, fields = ['password']) {
322322

323323
export function showSignUpActivity(id, fields = ['password']) {
324324
const m = read(getEntity, 'lock', id);
325-
const captchaConfig = l.captcha(m);
325+
const captchaConfig = l.signupCaptcha(m);
326326
if (captchaConfig && captchaConfig.get('provider') === 'arkose') {
327327
swap(updateEntity, 'lock', id, setScreen, 'signUp', fields);
328328
} else {
329-
swapCaptcha(id, 'login', false, () => {
329+
swapCaptcha(id, Flow.SIGNUP, false, () => {
330330
swap(updateEntity, 'lock', id, setScreen, 'signUp', fields);
331331
});
332332
}
@@ -338,7 +338,7 @@ export function showResetPasswordActivity(id, fields = ['password']) {
338338
if (captchaConfig && captchaConfig.get('provider') === 'arkose') {
339339
swap(updateEntity, 'lock', id, setScreen, 'forgotPassword', fields);
340340
} else {
341-
swapCaptcha(id, 'login', false, () => {
341+
swapCaptcha(id, Flow.PASSWORD_RESET, false, () => {
342342
swap(updateEntity, 'lock', id, setScreen, 'forgotPassword', fields);
343343
});
344344
}

Diff for: src/connection/passwordless/index.js

+12
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { dataFns } from '../../utils/data_utils';
66
const { get, initNS, tget, tremove, tset } = dataFns(['passwordless']);
77
import webAPI from '../../core/web_api';
88
import sync from '../../sync';
9+
import webApi from '../../core/web_api';
10+
import { setPasswordlessCaptcha } from '../../core/index';
911

1012
export function initPasswordless(m, opts) {
1113
// TODO: validate opts
@@ -14,6 +16,16 @@ export function initPasswordless(m, opts) {
1416
const showTerms = opts.showTerms === undefined ? true : !!opts.showTerms;
1517

1618
m = initNS(m, Map({ send, mustAcceptTerms, showTerms }));
19+
20+
m = sync(m, 'passwordlessCaptcha', {
21+
syncFn: (m, cb) => {
22+
webApi.getPasswordlessChallenge(m.get('id'), (err, r) => {
23+
cb(null, r);
24+
});
25+
},
26+
successFn: setPasswordlessCaptcha
27+
});
28+
1729
if (opts.defaultLocation && typeof opts.defaultLocation === 'string') {
1830
m = initLocation(m, opts.defaultLocation.toUpperCase());
1931
} else {

Diff for: src/core/index.js

+9
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,11 @@ export function setCaptcha(m, value, wasInvalid) {
421421
return set(m, 'captcha', Immutable.fromJS(value));
422422
}
423423

424+
export function setSignupChallenge(m, value, wasInvalid) {
425+
m = captchaField.reset(m, wasInvalid);
426+
return set(m, 'signupCaptcha', Immutable.fromJS(value));
427+
}
428+
424429
export function setPasswordlessCaptcha(m, value, wasInvalid) {
425430
m = captchaField.reset(m, wasInvalid);
426431
return set(m, 'passwordlessCaptcha', Immutable.fromJS(value));
@@ -435,6 +440,10 @@ export function captcha(m) {
435440
return get(m, 'captcha');
436441
}
437442

443+
export function signupCaptcha(m) {
444+
return get(m, 'signupCaptcha');
445+
}
446+
438447
export function passwordlessCaptcha(m) {
439448
return get(m, 'passwordlessCaptcha');
440449
}

Diff for: src/core/remote_data.js

-19
Original file line numberDiff line numberDiff line change
@@ -60,24 +60,5 @@ export function syncRemoteData(m) {
6060
successFn: setCaptcha
6161
});
6262

63-
m = sync(m, 'passwordlessCaptcha', {
64-
syncFn: (m, cb) => {
65-
webApi.getPasswordlessChallenge(m.get('id'), (err, r) => {
66-
cb(null, r);
67-
});
68-
},
69-
successFn: setPasswordlessCaptcha
70-
});
71-
72-
m = sync(m, 'passwordResetCaptcha', {
73-
syncFn: (m, cb) => {
74-
webApi.getPasswordResetChallenge(m.get('id'), (err, r) => {
75-
cb(null, r);
76-
});
77-
},
78-
successFn: setPasswordResetCaptcha
79-
});
80-
81-
8263
return m;
8364
}

Diff for: src/core/web_api.js

+4
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ class Auth0WebAPI {
6060
return this.clients[lockID].getChallenge(callback);
6161
}
6262

63+
getSignupChallenge(lockID, callback) {
64+
return this.clients[lockID].getSignupChallenge(callback);
65+
}
66+
6367
getPasswordlessChallenge(lockID, callback) {
6468
return this.clients[lockID].getPasswordlessChallenge(callback);
6569
}

Diff for: src/core/web_api/p2_api.js

+4
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ class Auth0APIClient {
195195
return this.client.client.getChallenge(...params);
196196
}
197197

198+
getSignupChallenge(...params) {
199+
return this.client.client.dbConnection.getSignupChallenge(...params);
200+
}
201+
198202
getPasswordlessChallenge(...params) {
199203
return this.client.client.passwordless.getChallenge(...params);
200204
}

0 commit comments

Comments
 (0)