forked from leftypol/leftypol
Merge branch 'captcha-workaround' into 'config'
Captcha workaround See merge request leftypol/leftypol!9
This commit is contained in:
commit
aa8525aa86
4 changed files with 121 additions and 82 deletions
|
@ -301,9 +301,8 @@
|
||||||
'lock',
|
'lock',
|
||||||
'raw',
|
'raw',
|
||||||
'embed',
|
'embed',
|
||||||
'g-recaptcha-response',
|
'captcha-response',
|
||||||
'h-captcha-response',
|
'captcha-form-id',
|
||||||
'cf-turnstile-response',
|
|
||||||
'spoiler',
|
'spoiler',
|
||||||
'page',
|
'page',
|
||||||
'file_url',
|
'file_url',
|
||||||
|
@ -330,33 +329,40 @@
|
||||||
'answer' => '4'
|
'answer' => '4'
|
||||||
);
|
);
|
||||||
*/
|
*/
|
||||||
|
// Enable a captcha system to make spam even harder. Rarely necessary.
|
||||||
|
$config['captcha'] = [
|
||||||
|
/**
|
||||||
|
* Select the captcha backend, false to disable.
|
||||||
|
* Can be false, "recaptcha", "hcaptcha" or "turnstile".
|
||||||
|
*/
|
||||||
|
'mode' => false,
|
||||||
/**
|
/**
|
||||||
* The captcha is dynamically injected on the client if the server replies with the `captcha-required` cookie set
|
* The captcha is dynamically injected on the client if the server replies with the `captcha-required` cookie set
|
||||||
* to 1.
|
* to 1.
|
||||||
* Use false to disable this configuration, otherwise, set the IP that vichan should check for captcha responses.
|
* Use false to disable this configuration, otherwise, set the IP that vichan should check for captcha responses.
|
||||||
*/
|
*/
|
||||||
$config['dynamic_captcha'] = false;
|
'dynamic' => false,
|
||||||
|
// Require to be non-zero if you use js/ajax.js (preferably no more than a few seconds), otherwise weird errors might occur.
|
||||||
// Enable reCaptcha to make spam even harder. Rarely necessary.
|
'passthrough_timeout' => 0,
|
||||||
$config['recaptcha'] = false;
|
// Configure Google reCAPTCHA.
|
||||||
|
'recaptcha' => [
|
||||||
// Public and private key pair from https://www.google.com/recaptcha/admin/create
|
// Public and private key pair from https://www.google.com/recaptcha/admin/create
|
||||||
$config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f';
|
'public' => '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f',
|
||||||
$config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_';
|
'private' => '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_',
|
||||||
|
],
|
||||||
// Enable hCaptcha.
|
// Configure hCaptcha.
|
||||||
$config['hcaptcha'] = false;
|
'hcaptcha' => [
|
||||||
|
|
||||||
// Public and private key pair for using hCaptcha.
|
// Public and private key pair for using hCaptcha.
|
||||||
$config['hcaptcha_public'] = '7a4b21e0-dc53-46f2-a9f8-91d2e74b63a0';
|
'public' => '7a4b21e0-dc53-46f2-a9f8-91d2e74b63a0',
|
||||||
$config['hcaptcha_private'] = '0x4e9A01bE637b51dC41a7Ea9865C3fDe4aB72Cf17';
|
'private' => '0x4e9A01bE637b51dC41a7Ea9865C3fDe4aB72Cf17',
|
||||||
|
],
|
||||||
// Enable Cloudflare's Turnstile captcha.
|
// Configure Cloudflare Turnstile.
|
||||||
$config['turnstile'] = false;
|
'turnstile' => [
|
||||||
|
|
||||||
// Public and private key pair for turnstile.
|
// Public and private key pair for turnstile.
|
||||||
$config['turnstile_public'] = '';
|
'public' => '',
|
||||||
$config['turnstile_private'] = '';
|
'private' => '',
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
// Ability to lock a board for normal users and still allow mods to post. Could also be useful for making an archive board
|
// Ability to lock a board for normal users and still allow mods to post. Could also be useful for making an archive board
|
||||||
$config['board_locked'] = false;
|
$config['board_locked'] = false;
|
||||||
|
|
112
post.php
112
post.php
|
@ -167,6 +167,68 @@ function check_turnstile($secret, $response, $remote_ip, $expected_action)
|
||||||
return $json_ret['success'] === true && $json_ret['action'] === $expected_action;
|
return $json_ret['success'] === true && $json_ret['action'] === $expected_action;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A "sophisticated" workaround to js/ajax.js calling post.php multiple times on error/ban.
|
||||||
|
*/
|
||||||
|
function check_captcha(array $captcha_config, string $form_id, string $board_uri, string $response, string $remote_ip, string $expected_action) {
|
||||||
|
$dynamic = $captcha_config['dynamic'];
|
||||||
|
if ($dynamic !== false && $remote_ip !== $dynamic) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ($captcha_config['mode']) {
|
||||||
|
case 'recaptcha':
|
||||||
|
case 'hcaptcha':
|
||||||
|
case 'turnstile':
|
||||||
|
$mode = $captcha_config['mode'];
|
||||||
|
break;
|
||||||
|
case false:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
\error_log("Unknown captcha mode '{$captcha_config['mode']}'");
|
||||||
|
throw new \RuntimeException('Captcha configuration error');
|
||||||
|
}
|
||||||
|
|
||||||
|
$passthrough_timeout = $captcha_config['passthrough_timeout'];
|
||||||
|
|
||||||
|
if ($passthrough_timeout != 0) {
|
||||||
|
$pass = Cache::get("captcha_passthrough_{$remote_ip}_{$form_id}");
|
||||||
|
if ($pass !== false) {
|
||||||
|
$let_through = $pass['expires'] > time() && $pass['board_uri'] === $board_uri && $pass['captcha_response'] === $response;
|
||||||
|
if ($let_through) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$remote_ip_send = $dynamic !== false ? null : $remote_ip;
|
||||||
|
$private_key = $captcha_config[$mode]['private'];
|
||||||
|
$public_key = $captcha_config[$mode]['public'];
|
||||||
|
switch ($mode) {
|
||||||
|
case 'recaptcha':
|
||||||
|
$ret = check_recaptcha($private_key, $response, $remote_ip_send);
|
||||||
|
break;
|
||||||
|
case 'hcaptcha':
|
||||||
|
$ret = check_hcaptcha($private_key, $response, $remote_ip_send, $public_key);
|
||||||
|
break;
|
||||||
|
case 'turnstile':
|
||||||
|
$ret = check_turnstile($private_key, $response, $remote_ip_send, $expected_action);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($ret && $passthrough_timeout != 0) {
|
||||||
|
$pass = [
|
||||||
|
'expires' => time() + $passthrough_timeout,
|
||||||
|
'board_uri' => $board_uri,
|
||||||
|
'captcha_response' => $response
|
||||||
|
];
|
||||||
|
|
||||||
|
Cache::set("captcha_passthrough_{$remote_ip}_{$form_id}", $pass, $passthrough_timeout + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes the (single) captcha associated with the ip and code.
|
* Deletes the (single) captcha associated with the ip and code.
|
||||||
*
|
*
|
||||||
|
@ -765,58 +827,18 @@ function handle_post()
|
||||||
|
|
||||||
|
|
||||||
if (!$dropped_post) {
|
if (!$dropped_post) {
|
||||||
if ($config['dynamic_captcha'] !== false) {
|
|
||||||
if ($_SERVER['REMOTE_ADDR'] === $config['dynamic_captcha']) {
|
|
||||||
if ($config['recaptcha']) {
|
|
||||||
if (!isset($_POST['g-recaptcha-response'])) {
|
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
|
||||||
if (!check_recaptcha($config['recaptcha_private'], $_POST['g-recaptcha-response'], null)) {
|
|
||||||
error($config['error']['captcha']);
|
|
||||||
}
|
|
||||||
} elseif ($config['hcaptcha']) {
|
|
||||||
if (!isset($_POST['h-captcha-response'])) {
|
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
|
||||||
if (!check_hcaptcha($config['hcaptcha_private'], $_POST['h-captcha-response'], null, $config['hcaptcha_public'])) {
|
|
||||||
error($config['error']['captcha']);
|
|
||||||
}
|
|
||||||
} elseif ($config['turnstile']) {
|
|
||||||
if (!isset($_POST['cf-turnstile-response'])) {
|
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
|
||||||
$expected_action = $post['op'] ? 'post-thread' : 'post-reply';
|
|
||||||
if (!check_turnstile($config['turnstile_private'], $_POST['cf-turnstile-response'], null, $expected_action)) {
|
|
||||||
error($config['error']['captcha']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Check for CAPTCHA right after opening the board so the "return" link is in there.
|
// Check for CAPTCHA right after opening the board so the "return" link is in there.
|
||||||
if ($config['recaptcha']) {
|
if ($config['captcha']['mode'] !== false) {
|
||||||
if (!isset($_POST['g-recaptcha-response'])) {
|
if (!isset($_POST['captcha-response'], $_POST['captcha-form-id'])) {
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
|
||||||
if (!check_recaptcha($config['recaptcha_private'], $_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR'])) {
|
|
||||||
error($config['error']['captcha']);
|
|
||||||
}
|
|
||||||
} elseif ($config['hcaptcha']) {
|
|
||||||
if (!isset($_POST['h-captcha-response'])) {
|
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
|
||||||
if (!check_hcaptcha($config['hcaptcha_private'], $_POST['h-captcha-response'], $_SERVER['REMOTE_ADDR'], $config['hcaptcha_public'])) {
|
|
||||||
error($config['error']['captcha']);
|
|
||||||
}
|
|
||||||
} elseif ($config['turnstile']) {
|
|
||||||
if (!isset($_POST['cf-turnstile-response'])) {
|
|
||||||
error($config['error']['bot']);
|
error($config['error']['bot']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$expected_action = $post['op'] ? 'post-thread' : 'post-reply';
|
$expected_action = $post['op'] ? 'post-thread' : 'post-reply';
|
||||||
if (!check_turnstile($config['turnstile_private'], $_POST['cf-turnstile-response'], null, $expected_action)) {
|
$ret = check_captcha($config['captcha'], $_POST['captcha-form-id'], $post['board'], $_POST['captcha-response'], $_SERVER['REMOTE_ADDR'], $expected_action);
|
||||||
|
if (!$ret) {
|
||||||
error($config['error']['captcha']);
|
error($config['error']['captcha']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($config['simple_spam']) && $post['op']) {
|
if (isset($config['simple_spam']) && $post['op']) {
|
||||||
if (!isset($_POST['simple_spam']) || $config['simple_spam']['answer'] != $_POST['simple_spam']) {
|
if (!isset($_POST['simple_spam']) || $config['simple_spam']['answer'] != $_POST['simple_spam']) {
|
||||||
|
|
|
@ -273,8 +273,9 @@ var captcha_renderer = null;
|
||||||
function onCaptchaLoadHcaptcha() {
|
function onCaptchaLoadHcaptcha() {
|
||||||
if (captcha_renderer === null && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
|
if (captcha_renderer === null && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
|
||||||
let renderer = {
|
let renderer = {
|
||||||
renderOn: (container) => hcaptcha.render(container, {
|
applyOn: (container, params) => hcaptcha.render(container, {
|
||||||
sitekey: "{{ config.hcaptcha_public }}",
|
sitekey: "{{ config.hcaptcha_public }}",
|
||||||
|
callback: params['on-success'],
|
||||||
}),
|
}),
|
||||||
remove: (widgetId) => { /* Not supported */ },
|
remove: (widgetId) => { /* Not supported */ },
|
||||||
reset: (widgetId) => hcaptcha.reset(widgetId)
|
reset: (widgetId) => hcaptcha.reset(widgetId)
|
||||||
|
@ -300,10 +301,11 @@ window.onCaptchaLoadTurnstile_post_thread = function() {
|
||||||
function onCaptchaLoadTurnstile(action) {
|
function onCaptchaLoadTurnstile(action) {
|
||||||
if (captcha_renderer === null && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
|
if (captcha_renderer === null && (active_page === 'index' || active_page === 'catalog' || active_page === 'thread')) {
|
||||||
let renderer = {
|
let renderer = {
|
||||||
renderOn: function(container) {
|
applyOn: function(container, params) {
|
||||||
let widgetId = turnstile.render('#' + container, {
|
let widgetId = turnstile.render('#' + container, {
|
||||||
sitekey: "{{ config.turnstile_public }}",
|
sitekey: "{{ config.turnstile_public }}",
|
||||||
action: action,
|
action: action,
|
||||||
|
callback: params['on-success'],
|
||||||
});
|
});
|
||||||
if (widgetId === undefined) {
|
if (widgetId === undefined) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -320,9 +322,16 @@ function onCaptchaLoadTurnstile(action) {
|
||||||
{% endif %} // End if turnstile
|
{% endif %} // End if turnstile
|
||||||
|
|
||||||
function onCaptchaLoad(renderer) {
|
function onCaptchaLoad(renderer) {
|
||||||
|
// Initialize the form identifier with a random password.
|
||||||
|
document.getElementById('captcha-form-id').value = generatePassword();
|
||||||
|
|
||||||
captcha_renderer = renderer;
|
captcha_renderer = renderer;
|
||||||
|
|
||||||
let widgetId = renderer.renderOn('captcha-container');
|
let widgetId = renderer.applyOn('captcha-container', {
|
||||||
|
'on-success': function(token) {
|
||||||
|
document.getElementById('captcha-response').value = token;
|
||||||
|
}
|
||||||
|
});
|
||||||
if (widgetId === null) {
|
if (widgetId === null) {
|
||||||
console.error('Could not render captcha!');
|
console.error('Could not render captcha!');
|
||||||
}
|
}
|
||||||
|
|
|
@ -118,6 +118,8 @@
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<div id="captcha-container"></div>
|
<div id="captcha-container"></div>
|
||||||
|
<textarea id="captcha-response" name="captcha-response" style="display:none;"></textarea>
|
||||||
|
<textarea id="captcha-form-id" name="captcha-form-id" style="display:none;"></textarea>
|
||||||
{{ antibot.html() }}
|
{{ antibot.html() }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue