diff --git a/src/web-auth/captcha.js b/src/web-auth/captcha.js index c23537ab..8f5173f3 100644 --- a/src/web-auth/captcha.js +++ b/src/web-auth/captcha.js @@ -171,7 +171,10 @@ function injectCaptchaScript(element, opts, callback, setValue) { opts.clientSubdomain, opts.siteKey ); - if (opts.provider === ARKOSE_PROVIDER) { + if ( + opts.provider === ARKOSE_PROVIDER || + opts.provider === AUTH0_V2_CAPTCHA_PROVIDER + ) { var retryCount = 0; attributes['data-callback'] = callbackName; attributes['onerror'] = function () { @@ -182,7 +185,7 @@ function injectCaptchaScript(element, opts, callback, setValue) { return; } removeScript(scriptSrc); - // Optimzation to tell auth0 to fail open if Arkose is configured to fail open + // Optimzation to tell auth0 to fail open if Arkose/auth0_v2 is configured to fail open setValue('BYPASS_CAPTCHA'); }; window[callbackName] = function (arkose) { @@ -309,9 +312,23 @@ function handleCaptchaProvider(element, options, challenge) { }, sitekey: challenge.siteKey }; + if (challenge.provider === AUTH0_V2_CAPTCHA_PROVIDER) { + retryCount = 0; renderParams.language = options.lang; renderParams.theme = 'light'; + renderParams.retry = 'never'; + renderParams['response-field'] = false; + renderParams['error-callback'] = function () { + if (retryCount < MAX_RETRY) { + setValue(); + globalForCaptchaProvider(challenge.provider).reset(widgetId); + retryCount++; + } else { + setValue('BYPASS_CAPTCHA'); + } + return true; + }; } widgetId = global.render(captchaDiv, renderParams); element.setAttribute('data-wid', widgetId); diff --git a/test/web-auth/captcha.test.js b/test/web-auth/captcha.test.js index 78b19422..b7ef7eed 100644 --- a/test/web-auth/captcha.test.js +++ b/test/web-auth/captcha.test.js @@ -222,7 +222,7 @@ describe('captcha rendering', function () { [HCAPTCHA_PROVIDER]: 'hcaptcha.com', [FRIENDLY_CAPTCHA_PROVIDER]: 'jsdelivr.net', [AUTH0_V2_CAPTCHA_PROVIDER]: 'cloudflare.com' - } + }; return hosts[provider]; }; const getSubdomain = () => { @@ -279,7 +279,7 @@ describe('captcha rendering', function () { siteKey: 'blabla sitekey' }; - let c, captchaScript, scriptOnLoadCallback, element; + let c, captchaScript, scriptOnLoadCallback, scriptErrorCallback, element; beforeEach(() => { const { window } = new JSDOM('
'); @@ -299,6 +299,7 @@ describe('captcha rendering', function () { if (provider === FRIENDLY_CAPTCHA_PROVIDER) { scriptOnLoadCallback = captchaScript.onload; } + scriptErrorCallback = captchaScript['onerror']; }); afterEach(function () { @@ -387,6 +388,34 @@ describe('captcha rendering', function () { }); } + if (provider === AUTH0_V2_CAPTCHA_PROVIDER) { + it('should remove script and set bypass token after more than 3 errors', function () { + const input = element.querySelector('input[name="captcha"]'); + input.value = ''; + for (let i = 0; i < 4; i++) { + scriptErrorCallback(); + } + expect( + [...window.document.querySelectorAll('script')].find(s => + s.src.match('cloudflare') + ) + ).to.equal(undefined); + expect(input.value).to.equal('BYPASS_CAPTCHA'); + }); + + it('should clear token on error before until the third attempt and set bypass token after more than 3 errors', function () { + const input = element.querySelector('input[name="captcha"]'); + input.value = 'token'; + for (let i = 0; i < 3; i++) { + renderOptions['error-callback'](); + expect(input.value).to.equal(''); + } + const finalRetry = renderOptions['error-callback'](); + expect(finalRetry).to.equal(true); + expect(input.value).to.equal('BYPASS_CAPTCHA'); + }); + } + it('should clean the value and reset when reloading', function () { const input = element.querySelector('input[name="captcha"]'); input.value = 'old token'; @@ -792,7 +821,7 @@ describe('passwordless captcha rendering', function () { [HCAPTCHA_PROVIDER]: 'hcaptcha.com', [FRIENDLY_CAPTCHA_PROVIDER]: 'jsdelivr.net', [AUTH0_V2_CAPTCHA_PROVIDER]: 'cloudflare.com' - } + }; return hosts[provider]; }; const getSubdomain = () => { @@ -849,7 +878,7 @@ describe('passwordless captcha rendering', function () { siteKey: 'blabla sitekey' }; - let c, captchaScript, scriptOnLoadCallback, element; + let c, captchaScript, scriptOnLoadCallback, scriptErrorCallback, element; beforeEach(() => { const { window } = new JSDOM('
'); @@ -871,6 +900,7 @@ describe('passwordless captcha rendering', function () { if (provider === FRIENDLY_CAPTCHA_PROVIDER) { scriptOnLoadCallback = captchaScript.onload; } + scriptErrorCallback = captchaScript['onerror']; }); afterEach(function () { @@ -967,6 +997,36 @@ describe('passwordless captcha rendering', function () { expect(resetted).to.be.ok(); }); + if (provider === AUTH0_V2_CAPTCHA_PROVIDER) { + it('should remove script and set bypass token after more than 3 errors', function () { + const input = element.querySelector('input[name="captcha"]'); + input.value = ''; + for (let i = 0; i < 4; i++) { + scriptErrorCallback(); + } + + expect( + [...window.document.querySelectorAll('script')].find(s => + s.src.match('cloudflare') + ) + ).to.equal(undefined); + expect(input.value).to.equal('BYPASS_CAPTCHA'); + }); + + + it('should clear token on error before until the third attempt and set bypass token after more than 3 errors', function () { + const input = element.querySelector('input[name="captcha"]'); + input.value = 'token'; + for (let i = 0; i < 3; i++) { + renderOptions['error-callback'](); + expect(input.value).to.equal(''); + } + const finalRetry = renderOptions['error-callback'](); + expect(finalRetry).to.equal(true); + expect(input.value).to.equal('BYPASS_CAPTCHA'); + }); + } + it('should clean the value when there is an error', function () { const input = element.querySelector('input[name="captcha"]'); input.value = 'expired token';