Skip to content

Commit

Permalink
Merge pull request #3676 from Automattic/hotfix/v2-recaptcha-woo-chec…
Browse files Browse the repository at this point in the history
…kout--on-submit

fix(recaptcha): validate on form submit, not button click
  • Loading branch information
dkoo authored Jan 14, 2025
2 parents 1855689 + f53c1fa commit fa41ce4
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 79 deletions.
4 changes: 2 additions & 2 deletions includes/class-recaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ function refreshToken() {
*/
public static function verify_recaptcha_on_checkout() {
$url = \home_url( \add_query_arg( null, null ) );
$should_verify_captcha = apply_filters( 'newspack_recaptcha_verify_captcha', self::can_use_captcha(), $url );
$should_verify_captcha = apply_filters( 'newspack_recaptcha_verify_captcha', self::can_use_captcha(), $url, 'checkout' );
$version = self::get_setting( 'version' );

if ( ! $should_verify_captcha ) {
Expand All @@ -522,7 +522,7 @@ public static function verify_recaptcha_on_checkout() {
*/
public static function verify_recaptcha_on_add_payment_method( $is_valid ) {
$url = \home_url( \add_query_arg( null, null ) );
$should_verify_captcha = apply_filters( 'newspack_recaptcha_verify_captcha', self::can_use_captcha(), $url );
$should_verify_captcha = apply_filters( 'newspack_recaptcha_verify_captcha', self::can_use_captcha(), $url, 'add_payment_method' );
if ( ! $should_verify_captcha ) {
return $is_valid;
}
Expand Down
3 changes: 2 additions & 1 deletion includes/reader-activation/class-reader-activation.php
Original file line number Diff line number Diff line change
Expand Up @@ -1830,7 +1830,8 @@ public static function process_auth_form() {
}

// reCAPTCHA test on account registration only.
if ( 'register' === $action && Recaptcha::can_use_captcha( 'v3' ) ) {
$should_verify_captcha = apply_filters( 'newspack_recaptcha_verify_captcha', Recaptcha::can_use_captcha(), $current_page_url, 'auth_modal' );
if ( 'register' === $action && $should_verify_captcha ) {
$captcha_result = Recaptcha::verify_captcha();
if ( \is_wp_error( $captcha_result ) ) {
return self::send_auth_form_response( $captcha_result );
Expand Down
6 changes: 5 additions & 1 deletion src/blocks/reader-registration/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,11 @@ function process_form() {
}

// reCAPTCHA test.
if ( Recaptcha::can_use_captcha( 'v3' ) ) {
$current_page_url = \wp_parse_url( \wp_get_raw_referer() );
if ( ! empty( $current_page_url['path'] ) ) {
$current_page_url = \esc_url( \home_url( $current_page_url['path'] ) );
}
if ( apply_filters( 'newspack_recaptcha_verify_captcha', Recaptcha::can_use_captcha(), $current_page_url, 'registration_block' ) ) {
$captcha_result = Recaptcha::verify_captcha();
if ( \is_wp_error( $captcha_result ) ) {
return send_form_response( $captcha_result );
Expand Down
144 changes: 71 additions & 73 deletions src/other-scripts/recaptcha/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,95 +30,93 @@ const isInvisible = 'v2_invisible' === newspack_recaptcha_data.version;
* @param {Function|null} onError Callback to handle errors. Optional.
*/
function renderV2Widget( form, onSuccess = null, onError = null ) {
form.removeAttribute( 'data-recaptcha-validated' );
// Common render options for reCAPTCHA v2 widget. See https://developers.google.com/recaptcha/docs/invisible#render_param for supported params.
const options = {
sitekey: siteKey,
size: isInvisible ? 'invisible' : 'normal',
isolated: true,
};

const submitButtons = [
...form.querySelectorAll( 'input[type="submit"], button[type="submit"]' )
];
submitButtons.forEach( button => {
// Don't render widget if the button has a data-skip-recaptcha attribute.
if ( button.hasAttribute( 'data-skip-recaptcha' ) ) {
return;
// Callback when reCAPTCHA passes validation or skip flag is present.
const successCallback = token => {
onSuccess?.();
// Ensure the token gets submitted with the form submission.
let hiddenField = form.querySelector( '[name="g-recaptcha-response"]' );
if ( ! hiddenField ) {
hiddenField = document.createElement( 'input' );
hiddenField.type = 'hidden';
hiddenField.name = 'g-recaptcha-response';
form.appendChild( hiddenField );
}
// Callback when reCAPTCHA passes validation or skip flag is present.
const successCallback = token => {
onSuccess?.();
// Ensure the token gets submitted with the form submission.
let hiddenField = form.querySelector( '[name="g-recaptcha-response"]' );
if ( ! hiddenField ) {
hiddenField = document.createElement( 'input' );
hiddenField.type = 'hidden';
hiddenField.name = 'g-recaptcha-response';
form.appendChild( hiddenField );
}
hiddenField.value = token;
form.requestSubmit( button );
refreshV2Widget( button );
};
// Callback when reCAPTCHA rendering fails or expires.
const errorCallback = () => {
const retryCount = parseInt( button.getAttribute( 'data-recaptcha-retry-count' ) ) || 0;
if ( retryCount < 3 ) {
refreshV2Widget( button );
grecaptcha.execute( button.getAttribute( 'data-recaptcha-widget-id' ) );
button.setAttribute( 'data-recaptcha-retry-count', retryCount + 1 );
hiddenField.value = token;
const buttons = [
...form.querySelectorAll( 'input[type="submit"], button[type="submit"]' )
];
form.setAttribute( 'data-recaptcha-validated', '1' );
form.requestSubmit( buttons[ buttons.length - 1 ] );
refreshV2Widget( form );
};
// Callback when reCAPTCHA rendering fails or expires.
const errorCallback = () => {
form.removeAttribute( 'data-recaptcha-validated' );
const retryCount = parseInt( form.getAttribute( 'data-recaptcha-retry-count' ) ) || 0;
if ( retryCount < 3 ) {
refreshV2Widget( form );
grecaptcha.execute( form.getAttribute( 'data-recaptcha-widget-id' ) );
form.setAttribute( 'data-recaptcha-retry-count', retryCount + 1 );
} else {
const message = wp.i18n.__( 'There was an error connecting with reCAPTCHA. Please reload the page and try again.', 'newspack-plugin' );
if ( onError ) {
onError( message );
} else {
const message = wp.i18n.__( 'There was an error connecting with reCAPTCHA. Please reload the page and try again.', 'newspack-plugin' );
if ( onError ) {
onError( message );
} else {
addErrorMessage( form, message );
}
addErrorMessage( form, message );
}
}
// Attach widget to form events.
const attachListeners = () => {
getIntersectionObserver( () => renderV2Widget( form, onSuccess, onError ) ).observe( form, { attributes: true } );
button.addEventListener( 'click', e => {
};

// Attach widget to form events.
const attachListeners = () => {
getIntersectionObserver( () => renderV2Widget( form, onSuccess, onError ) ).observe( form, { attributes: true } );
form.addEventListener( 'submit', e => {
if ( ! form.hasAttribute( 'data-recaptcha-validated' ) && ! form.hasAttribute( 'data-skip-recaptcha' ) ) {
e.preventDefault();
e.stopImmediatePropagation();
// Empty error messages if present.
removeErrorMessages( form );
// Skip reCAPTCHA verification if the button has a data-skip-recaptcha attribute.
if ( button.hasAttribute( 'data-skip-recaptcha' ) ) {
successCallback();
} else {
grecaptcha.execute( widgetId ).then( () => {
// If we are in an iframe scroll to top.
if ( window?.location !== window?.parent?.location ) {
document.body.scrollIntoView( { behavior: 'smooth' } );
}
} );
}
} );
}
// Refresh reCAPTCHA widgets on Woo checkout update and error.
if ( jQuery ) {
jQuery( document ).on( 'updated_checkout', () => attachListeners );
jQuery( document.body ).on( 'checkout_error', () => attachListeners );
}
// Refresh widget if it already exists.
if ( button.hasAttribute( 'data-recaptcha-widget-id' ) ) {
refreshV2Widget( button );
return;
}
const container = document.createElement( 'div' );
container.classList.add( 'grecaptcha-container' );
document.body.append( container );
const widgetId = grecaptcha.render( container, {
...options,
callback: successCallback,
'error-callback': errorCallback,
'expired-callback': errorCallback,
} );
button.setAttribute( 'data-recaptcha-widget-id', widgetId );
attachListeners();

grecaptcha.execute( widgetId ).then( () => {
// If we are in an iframe scroll to top.
if ( window?.location !== window?.parent?.location ) {
document.body.scrollIntoView( { behavior: 'smooth' } );
}
} );
} else {
form.removeAttribute( 'data-recaptcha-validated' );
}
}, true );
}
// Refresh reCAPTCHA widgets on Woo checkout update and error.
if ( jQuery ) {
jQuery( document ).on( 'updated_checkout', () => renderV2Widget( form, onSuccess, onError ) );
jQuery( document.body ).on( 'checkout_error', () => renderV2Widget( form, onSuccess, onError ) );
}
// Refresh widget if it already exists.
if ( form.hasAttribute( 'data-recaptcha-widget-id' ) ) {
refreshV2Widget( form );
return;
}
const container = document.createElement( 'div' );
container.classList.add( 'grecaptcha-container' );
document.body.append( container );
const widgetId = grecaptcha.render( container, {
...options,
callback: successCallback,
'error-callback': errorCallback,
'expired-callback': errorCallback,
} );
form.setAttribute( 'data-recaptcha-widget-id', widgetId );
attachListeners();
}

/**
Expand Down
4 changes: 2 additions & 2 deletions src/reader-activation-auth/auth-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,10 @@ window.newspackRAS.push( function ( readerActivation ) {
const newspack_grecaptcha = window.newspack_grecaptcha || {};
if ( 'v2_invisible' === newspack_grecaptcha?.version ) {
if ( 'register' === action ) {
submitButtons.forEach( button => button.removeAttribute( 'data-skip-recaptcha' ) );
form.removeAttribute( 'data-skip-recaptcha' );
newspack_grecaptcha.render( [ form ], ( error ) => form.setMessageContent( error, true ) );
} else {
submitButtons.forEach( button => button.setAttribute( 'data-skip-recaptcha', '' ) );
form.setAttribute( 'data-skip-recaptcha', '1' );
}
}
if ( 'otp' === action ) {
Expand Down

0 comments on commit fa41ce4

Please sign in to comment.