Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion library/Tiger/View/Helper/FormRecaptcha.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();});'
Expand Down
1 change: 1 addition & 0 deletions modules/signup/languages/en/signup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.',
Expand Down
6 changes: 6 additions & 0 deletions modules/signup/services/Signup.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
20 changes: 18 additions & 2 deletions modules/signup/views/scripts/index/index.phtml
Original file line number Diff line number Diff line change
Expand Up @@ -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() : '';
?>
<div class="tiger-auth-brand">
<span class="tiger-auth-mark"><i class="fa-solid fa-paw"></i></span>
Expand All @@ -19,8 +22,10 @@ $el = function ($name) use ($form) { return $form->getElement($name); };
<div id="signup-feedback"></div>
<div id="signup-done"></div>

<form id="signup-form" data-tiger-validate data-module="signup" data-form="Signup" onsubmit="return false;" novalidate>
<form id="signup-form" data-tiger-validate data-module="signup" data-form="Signup" onsubmit="return false;" novalidate data-recaptcha-site="<?= $this->escape($rcSite) ?>">
<?= $el('_csrf') ?>
<?php /* Core reCAPTCHA (loads api.js + the badge/legal notice + a hidden g-recaptcha-response); the token is minted in JS before this AJAX submit. */ ?>
<?= $this->formRecaptcha('g-recaptcha-response', null, ['action' => 'signup']) ?>

<div class="row g-3">
<div class="col-sm-6" data-field>
Expand Down Expand Up @@ -116,11 +121,21 @@ $el = function ($name) use ($form) { return $form->getElement($name); };
document.addEventListener('DOMContentLoaded', function () {
var form = document.getElementById('signup-form');
var fb = document.getElementById('signup-feedback');
var rcSite = form.getAttribute('data-recaptcha-site') || ''; // v3 site key (empty = off/v2 → skip)
function rcToken(action) {
if (!rcSite || !window.grecaptcha) { return Promise.resolve(''); }
return new Promise(function (resolve) {
try { grecaptcha.ready(function () { grecaptcha.execute(rcSite, { action: action }).then(resolve, function () { resolve(''); }); }); }
catch (e) { resolve(''); }
});
}

document.getElementById('signup-submit').addEventListener('click', function () {
var btn = this;
form.querySelectorAll('.is-invalid').forEach(function (e) { e.classList.remove('is-invalid'); });

rcToken('signup').then(function (token) {
var hid = form.querySelector('input.g-recaptcha-response');
if (hid && token) { hid.value = token; }
var fd = new URLSearchParams(new FormData(form));
fd.set('module', 'signup'); fd.set('service', 'signup'); fd.set('method', 'create');

Expand Down Expand Up @@ -156,6 +171,7 @@ document.addEventListener('DOMContentLoaded', function () {
}).catch(function () {
TigerDOM.notify(fb, 'Network error — please try again.', { type: 'error' });
});
});
});
});
</script>
Loading