Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[IAMRISK-3011] Auth0 V2 Captcha failOpen support #1382

Merged
31 changes: 31 additions & 0 deletions src/web-auth/captcha.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,23 @@ function injectCaptchaScript(element, opts, callback, setValue) {
window.arkose = arkose;
callback(arkose);
};
} else if (opts.provider === AUTH0_V2_CAPTCHA_PROVIDER) {
var a0RetryCount = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't have a separate variable for Auth0 V2, we can use the retryCount variable since when this script runs there will always be one provider so the counter will only represent the retries for that provider.

attributes['error-callback'] = function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is correct. Are you trying to retry the script loading? That should be done through the attribute 'onerror' like Arkose example above. Also let's try to combine the code since it's identical to Arkose.

https://developer.mozilla.org/en-US/docs/Web/API/HTMLScriptElement#examples

if (a0RetryCount < MAX_RETRY) {
removeScript(scriptSrc);
loadScript(scriptSrc, attributes);
a0RetryCount++;
return;
}
removeScript(scriptSrc);
// similar implementation to ARKOSE_PROVIDER failOpen
setValue('BYPASS_CAPTCHA');
};
window[callbackName] = function () {
delete window[callbackName]
callback();
};
} else {
window[callbackName] = function () {
delete window[callbackName];
Expand Down Expand Up @@ -309,9 +326,23 @@ function handleCaptchaProvider(element, options, challenge) {
},
sitekey: challenge.siteKey
};

if (challenge.provider === AUTH0_V2_CAPTCHA_PROVIDER) {
var a0RetryCount = 0;
renderParams.language = options.lang;
renderParams.theme = 'light';
renderParams.retry = 'never';
renderParams['error-callback'] = function () {
if (a0RetryCount < MAX_RETRY) {
setValue();
globalForCaptchaProvider(challenge.provider).reset(widgetId);
a0RetryCount++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to use the retryCount variable.

} else {
// similar implementation to ARKOSE_PROVIDER failOpen
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: no need for comment, let's remove

setValue('BYPASS_CAPTCHA');
}
return true;
};
}
widgetId = global.render(captchaDiv, renderParams);
element.setAttribute('data-wid', widgetId);
Expand Down
68 changes: 64 additions & 4 deletions test/web-auth/captcha.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -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('<body><div class="captcha" /></body>');
Expand All @@ -299,6 +299,7 @@ describe('captcha rendering', function () {
if (provider === FRIENDLY_CAPTCHA_PROVIDER) {
scriptOnLoadCallback = captchaScript.onload;
}
scriptErrorCallback = captchaScript['error-callback'];
});

afterEach(function () {
Expand Down Expand Up @@ -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';
Expand Down Expand Up @@ -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 = () => {
Expand Down Expand Up @@ -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('<body><div class="captcha" /></body>');
Expand All @@ -871,6 +900,7 @@ describe('passwordless captcha rendering', function () {
if (provider === FRIENDLY_CAPTCHA_PROVIDER) {
scriptOnLoadCallback = captchaScript.onload;
}
scriptErrorCallback = captchaScript['error-callback'];
});

afterEach(function () {
Expand Down Expand Up @@ -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';
Expand Down