From 165f9437d66d8e55b003889d4831f914dd506f63 Mon Sep 17 00:00:00 2001 From: alexkoumarianos-okta Date: Thu, 14 Dec 2023 13:08:28 -0500 Subject: [PATCH 1/3] add support for auth0 v2 captcha failOpen --- .../captcha/third_party_captcha.test.jsx | 31 ++++++++++++++- src/field/captcha/third_party_captcha.jsx | 38 +++++++++++++++++-- 2 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/__tests__/field/captcha/third_party_captcha.test.jsx b/src/__tests__/field/captcha/third_party_captcha.test.jsx index 31adbdbe3..f95c4114a 100644 --- a/src/__tests__/field/captcha/third_party_captcha.test.jsx +++ b/src/__tests__/field/captcha/third_party_captcha.test.jsx @@ -20,6 +20,7 @@ const createLockMock = ({ describe('ThirdPartyCaptcha', () => { let prevWindow; + let counter = 0; beforeAll(() => { prevWindow = global.window; global.window.grecaptcha = { @@ -37,7 +38,11 @@ describe('ThirdPartyCaptcha', () => { }) }; global.window.turnstile = { - render: jest.fn() + render: jest.fn(), + reset: () => { + global.window.turnstile.render(...global.window.turnstile.render.mock.calls[counter]); + counter++; + } }; }); afterAll(() => { @@ -179,6 +184,7 @@ describe('ThirdPartyCaptcha', () => { hl={'en'} isValid={true} value={undefined} + onChange={jest.fn()} /> ).instance(); act(() => { @@ -198,9 +204,30 @@ describe('ThirdPartyCaptcha', () => { 'expired-callback': expect.any(Function), 'error-callback': expect.any(Function), language: 'en', - theme: 'light' + theme: 'light', + retry: 'never' }); }); + + it('should retry 3 times on error and then set value to BYPASS_CAPTCHA dummy token for failOpen', () => { + const renderParams = global.window.turnstile.render.mock.calls[0][1]; + for (let i = 0; i < 3; i++) { + const renderParams = global.window.turnstile.render.mock.calls[i][1]; + act(() => { + renderParams['error-callback'](); + }); + const { retryCount } = wrapper.state; + const { value } = wrapper.props; + expect(retryCount).toBe(i + 1); + expect(value).toBe(undefined); + } + + act(() => renderParams['error-callback']()); + + const { onChange } = wrapper.props; + expect(onChange.mock.calls).toHaveLength(1); + expect(onChange.mock.calls[0][0]).toBe('BYPASS_CAPTCHA'); + }); }); describe('recaptcha enterprise', () => { diff --git a/src/field/captcha/third_party_captcha.jsx b/src/field/captcha/third_party_captcha.jsx index 6a5da9676..d359f00b6 100644 --- a/src/field/captcha/third_party_captcha.jsx +++ b/src/field/captcha/third_party_captcha.jsx @@ -148,7 +148,20 @@ export class ThirdPartyCaptcha extends React.Component { renderParams = { ...renderParams, language: this.props.hl, - theme: 'light' + theme: 'light', + retry: 'never', + 'error-callback': () => { + if (this.state.retryCount < MAX_RETRY) { + getCaptchaProvider(this.props.provider).reset(this.widgetId); + this.setState(prevState => ({ + retryCount: prevState.retryCount + 1 + })); + } else { + // similar implementation to ARKOSE_PROVIDER failOpen + this.changeHandler('BYPASS_CAPTCHA'); + } + return true; + } }; } return renderParams; @@ -169,7 +182,7 @@ export class ThirdPartyCaptcha extends React.Component { if (this.state.retryCount < MAX_RETRY) { removeScript(scriptUrl); loadScript(scriptUrl, attributes); - this.setState((prevState) => ({ + this.setState(prevState => ({ retryCount: prevState.retryCount + 1 })); return; @@ -177,15 +190,32 @@ export class ThirdPartyCaptcha extends React.Component { removeScript(scriptUrl); this.changeHandler('BYPASS_CAPTCHA'); }; - window[callbackName] = (arkose) => { + window[callbackName] = arkose => { callback(arkose); }; + } else if (provider === AUTH0_V2_CAPTCHA_PROVIDER) { + attributes['error-callback'] = () => { + if (this.state.retryCount < MAX_RETRY) { + removeScript(scriptUrl); + loadScript(scriptUrl, attributes); + this.setState(prevState => ({ + retryCount: prevState.retryCount + 1 + })); + return; + } + removeScript(scriptUrl); + this.changeHandler('BYPASS_CAPTCHA'); + }; + window[callbackName] = () => { + delete window[callbackName]; + callback(); + }; } else { window[callbackName] = () => { delete window[callbackName]; callback(); }; - + if (provider === FRIENDLY_CAPTCHA_PROVIDER) { attributes['onload'] = window[callbackName]; } From 71f489821e0c372b6fa61bb94664f8dcaea56dee Mon Sep 17 00:00:00 2001 From: alexkoumarianos-okta Date: Tue, 2 Jan 2024 09:27:56 -0500 Subject: [PATCH 2/3] reused arkose onerror code --- src/field/captcha/third_party_captcha.jsx | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/field/captcha/third_party_captcha.jsx b/src/field/captcha/third_party_captcha.jsx index d359f00b6..592d10759 100644 --- a/src/field/captcha/third_party_captcha.jsx +++ b/src/field/captcha/third_party_captcha.jsx @@ -176,7 +176,7 @@ export class ThirdPartyCaptcha extends React.Component { async: true, defer: true }; - if (provider === ARKOSE_PROVIDER) { + if (provider === ARKOSE_PROVIDER || provider === AUTH0_V2_CAPTCHA_PROVIDER) { attributes['data-callback'] = callbackName; attributes['onerror'] = () => { if (this.state.retryCount < MAX_RETRY) { @@ -193,23 +193,6 @@ export class ThirdPartyCaptcha extends React.Component { window[callbackName] = arkose => { callback(arkose); }; - } else if (provider === AUTH0_V2_CAPTCHA_PROVIDER) { - attributes['error-callback'] = () => { - if (this.state.retryCount < MAX_RETRY) { - removeScript(scriptUrl); - loadScript(scriptUrl, attributes); - this.setState(prevState => ({ - retryCount: prevState.retryCount + 1 - })); - return; - } - removeScript(scriptUrl); - this.changeHandler('BYPASS_CAPTCHA'); - }; - window[callbackName] = () => { - delete window[callbackName]; - callback(); - }; } else { window[callbackName] = () => { delete window[callbackName]; From 4048dc158a7134c35bd0dafcbc77df26f0998382 Mon Sep 17 00:00:00 2001 From: alexkoumarianos-okta Date: Wed, 3 Jan 2024 09:38:15 -0500 Subject: [PATCH 3/3] fix: omit cf response-field --- src/__tests__/field/captcha/third_party_captcha.test.jsx | 3 ++- src/field/captcha/third_party_captcha.jsx | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/__tests__/field/captcha/third_party_captcha.test.jsx b/src/__tests__/field/captcha/third_party_captcha.test.jsx index f95c4114a..fcff12d97 100644 --- a/src/__tests__/field/captcha/third_party_captcha.test.jsx +++ b/src/__tests__/field/captcha/third_party_captcha.test.jsx @@ -205,7 +205,8 @@ describe('ThirdPartyCaptcha', () => { 'error-callback': expect.any(Function), language: 'en', theme: 'light', - retry: 'never' + retry: 'never', + 'response-field': false }); }); diff --git a/src/field/captcha/third_party_captcha.jsx b/src/field/captcha/third_party_captcha.jsx index 592d10759..cfe3eee66 100644 --- a/src/field/captcha/third_party_captcha.jsx +++ b/src/field/captcha/third_party_captcha.jsx @@ -150,6 +150,7 @@ export class ThirdPartyCaptcha extends React.Component { language: this.props.hl, theme: 'light', retry: 'never', + 'response-field': false, 'error-callback': () => { if (this.state.retryCount < MAX_RETRY) { getCaptchaProvider(this.props.provider).reset(this.widgetId);