diff --git a/library/Tiger/View/Helper/FormRecaptcha.php b/library/Tiger/View/Helper/FormRecaptcha.php index 840f2bb..6fc1ea2 100644 --- a/library/Tiger/View/Helper/FormRecaptcha.php +++ b/library/Tiger/View/Helper/FormRecaptcha.php @@ -72,7 +72,12 @@ protected function _v3($site, $siteEsc, array $attribs) . 'document.querySelectorAll("input.g-recaptcha-response").forEach(function(inp){' . 'var form=inp.form; if(!form||form.__grcBound)return; form.__grcBound=true;' . 'form.addEventListener("submit",function(e){' - . 'if(form.__grcOk)return; e.preventDefault();' + // Token ready (this is the re-submit): re-arm for the NEXT submit so retries mint a fresh, + // unused token — a v3 token is single-use — then let the form's own handler proceed. + . 'if(form.__grcOk){form.__grcOk=false;return;}' + // First submit: HOLD the form's own submit handler (Tiger forms AJAX on submit and would + // otherwise fire with an empty token) until the async token exists, then re-dispatch. + . 'e.preventDefault(); e.stopImmediatePropagation();' . 'grecaptcha.execute(' . json_encode($site) . ',{action:' . json_encode($action) . '}).then(function(t){' . 'inp.value=t; form.__grcOk=true;' . 'if(typeof form.requestSubmit==="function")form.requestSubmit();else form.submit();});' diff --git a/modules/signup/languages/en/signup.php b/modules/signup/languages/en/signup.php index a66558b..fe05709 100644 --- a/modules/signup/languages/en/signup.php +++ b/modules/signup/languages/en/signup.php @@ -7,6 +7,7 @@ */ return [ 'signup.disabled' => 'Public signup is currently turned off.', + 'signup.error.recaptcha' => "Couldn't verify you're human — please try again.", 'signup.check_email' => 'Account created — check your email to verify it, then sign in.', 'signup.verified' => 'Your email is verified and your account is active.', 'signup.invalid_link' => 'This verification link is invalid or has expired.', diff --git a/modules/signup/services/Signup.php b/modules/signup/services/Signup.php index c35cf99..623307d 100644 --- a/modules/signup/services/Signup.php +++ b/modules/signup/services/Signup.php @@ -41,6 +41,12 @@ public function create(array $params): void if (self::isPublicDisabled()) { $this->_error('signup.disabled'); return; } $form = new Signup_Form_Signup(); if (!$form->isValid($params)) { $this->_formErrors($form); return; } + // Bot gate on this PUBLIC, account-creating endpoint — the same core reCAPTCHA validator the auth + // flow uses (Tiger_Validate_Recaptcha). A no-op when reCAPTCHA is disabled (the validator passes), + // so nothing changes on installs without keys; fail_open covers a Google outage. + if (!(new Tiger_Validate_Recaptcha(['action' => 'signup']))->isValid(null, $params)) { + $this->_error('signup.error.recaptcha'); return; + } $v = $form->getValues(); try { diff --git a/modules/signup/views/scripts/index/index.phtml b/modules/signup/views/scripts/index/index.phtml index ed30b76..96c6651 100644 --- a/modules/signup/views/scripts/index/index.phtml +++ b/modules/signup/views/scripts/index/index.phtml @@ -9,6 +9,9 @@ */ $form = $this->form; $el = function ($name) use ($form) { return $form->getElement($name); }; +// v3 site key for the JS to mint a token before this button-click AJAX submit (empty when off / v2). +$rcSite = (class_exists('Tiger_Recaptcha') && Tiger_Recaptcha::isEnabled() && Tiger_Recaptcha::version() === 'v3') + ? (string) Tiger_Recaptcha::siteKey() : ''; ?>