Here is an example code from Google about how to safely initialize its recaptcha service https://developers.google.com/recaptcha/docs/loading
<script async src="https://www.google.com/recaptcha/api.js"></script>
<script>
if(typeof grecaptcha === 'undefined') {
grecaptcha = {};
}
grecaptcha.ready = function(cb){
//How is it possible for grecaptcha to be undefined here?
if(typeof grecaptcha === 'undefined') {
const c = '___grecaptcha_cfg';
window[c] = window[c] || {};
(window[c]['fns'] = window[c]['fns']||[]).push(cb);
} else {
cb();
}
}
grecaptcha.ready(function(){
grecaptcha.render("container", {
sitekey: "ABC-123"
});
});
</script>
As you can see grecaptcha.ready is a function and it again checks if(typeof grecaptcha === 'undefined'). But how can grecaptcha be undefined at the time when grecaptcha.ready is called? As I understand grecaptcha has already been initialized as a property of global object and it's value is {} hence it's type should be "object"
It'd have to be code elsewhere, but sure, it's a global object, so a malicious actor could potentially overwrite it, e.g. you could have something like:
var grecaptcha = {
start() {
var that = this;
return new Promise((resolve, reject) => {
setTimeout(() => {
if (that.ready) {
that.ready(that)
}
resolve(true);
}, 5000);
let remaining = 4;
let interval = setInterval(() => {
console.log(remaining--);
if (remaining == 0) {
clearInterval(interval);
}
}, 1000);
});
}
};
grecaptcha.start().then(() => console.log('But we still do stuff with it'));
if(typeof grecaptcha === 'undefined') {
grecaptcha = {};
}
grecaptcha.ready = function(cb){
console.log(typeof grecaptcha);
}
setTimeout(() => grecaptcha = undefined, 1000)
I think the code is wrong: https://jsfiddle.net/657b1gso/
Reported: https://github.com/google/recaptcha/issues/485
and works with a few small modifications: https://jsfiddle.net/p6ubwLme/
<script async src="https://www.google.com/recaptcha/api.js"></script>
<script>
// How this code snippet works:
// This logic overwrites the default behavior of `grecaptcha.ready()` to
// ensure that it can be safely called at any time. When `grecaptcha.ready()`
// is called before reCAPTCHA is loaded, the callback function that is passed
// by `grecaptcha.ready()` is enqueued for execution after reCAPTCHA is
// loaded.
if(typeof grecaptcha === 'undefined') {
grecaptcha = {};
}
grecaptcha.asyncready = function(cb){
if(typeof grecaptcha.render === 'undefined') {
// window.__grecaptcha_cfg is a global variable that stores reCAPTCHA's
// configuration. By default, any functions listed in its 'fns' property
// are automatically executed when reCAPTCHA loads.
const c = '___grecaptcha_cfg';
window[c] = window[c] || {};
(window[c]['fns'] = window[c]['fns']||[]).push(cb);
} else {
cb();
}
}
// Usage
grecaptcha.asyncready(function(){
grecaptcha.render("container", {
sitekey: "ABC-123"
});
});
</script>
<div id="container">
</div>