Add hcaptcha support (#166)

Co-authored-by: RealAngeleno <angeleno@screamer.wiki>
Reviewed-on: https://git.leftypol.org/leftypol/leftypol/pulls/166
Co-authored-by: Zankaria <zankaria.auxa@skiff.com>
Co-committed-by: Zankaria <zankaria.auxa@skiff.com>
This commit is contained in:
Zankaria 2024-08-10 19:58:20 +00:00 committed by Zankaria
parent 25089f5cbb
commit d8c5c600a8
5 changed files with 107 additions and 19 deletions

View file

@ -302,6 +302,7 @@
'raw', 'raw',
'embed', 'embed',
'g-recaptcha-response', 'g-recaptcha-response',
'h-captcha-response',
'cf-turnstile-response', 'cf-turnstile-response',
'spoiler', 'spoiler',
'page', 'page',
@ -343,10 +344,17 @@
$config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f'; $config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f';
$config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_'; $config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_';
// Enable hCaptcha.
$config['hcaptcha'] = false;
// Public and private key pair for using hCaptcha.
$config['hcaptcha_public'] = '7a4b21e0-dc53-46f2-a9f8-91d2e74b63a0';
$config['hcaptcha_private'] = '0x4e9A01bE637b51dC41a7Ea9865C3fDe4aB72Cf17';
// Enable Cloudflare's Turnstile captcha. // Enable Cloudflare's Turnstile captcha.
$config['turnstile'] = false; $config['turnstile'] = false;
// Public and private key pair. // Public and private key pair for turnstile.
$config['turnstile_public'] = ''; $config['turnstile_public'] = '';
$config['turnstile_private'] = ''; $config['turnstile_private'] = '';

View file

@ -92,6 +92,44 @@ function check_recaptcha($secret, $response, $remote_ip)
return !!$resp['success']; return !!$resp['success'];
} }
function check_hcaptcha($secret, $response, $remote_ip, $public_key)
{
$data = [
'secret' => $secret,
'response' => $response,
'sitekey' => $public_key,
];
if ($remote_ip !== null) {
$data['remoteip'] = $remote_ip;
}
$c = curl_init();
curl_setopt_array($c, [
CURLOPT_URL => 'https://api.hcaptcha.com/siteverify',
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true,
]);
$c_ret = curl_exec($c);
if ($c_ret === false) {
$err_no = curl_errno($c);
$err_str = curl_error($c);
curl_close($c);
error_log("hCaptcha call failed. Curl returned: $err_no ($err_str)");
return false;
}
curl_close($c);
$json_ret = json_decode($c_ret, true);
if ($json_ret === null) {
error_log("hCaptcha call failed. Malformed json: $c_ret");
return false;
}
return $json_ret['success'] === true;
}
function check_turnstile($secret, $response, $remote_ip, $expected_action) function check_turnstile($secret, $response, $remote_ip, $expected_action)
{ {
$data = [ $data = [
@ -736,6 +774,13 @@ function handle_post()
if (!check_recaptcha($config['recaptcha_private'], $_POST['g-recaptcha-response'], null)) { if (!check_recaptcha($config['recaptcha_private'], $_POST['g-recaptcha-response'], null)) {
error($config['error']['captcha']); 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']) { } elseif ($config['turnstile']) {
if (!isset($_POST['cf-turnstile-response'])) { if (!isset($_POST['cf-turnstile-response'])) {
error($config['error']['bot']); error($config['error']['bot']);
@ -755,6 +800,13 @@ function handle_post()
if (!check_recaptcha($config['recaptcha_private'], $_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR'])) { if (!check_recaptcha($config['recaptcha_private'], $_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR'])) {
error($config['error']['captcha']); 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']) { } elseif ($config['turnstile']) {
if (!isset($_POST['cf-turnstile-response'])) { if (!isset($_POST['cf-turnstile-response'])) {
error($config['error']['bot']); error($config['error']['bot']);

View file

@ -1,3 +1,6 @@
{% if config.hcaptcha %}
<script src="https://js.hcaptcha.com/1/api.js?recaptchacompat=off&render=explicit&onload=onCaptchaLoadHcaptcha" async defer></script>
{% endif %}
{% if config.turnstile %} {% if config.turnstile %}
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onCaptchaLoadTurnstile_{{ form_action_type }}" async defer></script> <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onCaptchaLoadTurnstile_{{ form_action_type }}" async defer></script>
{% endif %} {% endif %}

View file

@ -245,11 +245,39 @@ function getCookie(cookie_name) {
{% endraw %} {% endraw %}
/* BEGIN CAPTCHA REGION */ /* BEGIN CAPTCHA REGION */
{% if config.hcaptcha or config.turnstile %} // If any captcha
{% if config.turnstile %}
// Global captcha object. Assigned by `onCaptchaLoad()`. // Global captcha object. Assigned by `onCaptchaLoad()`.
var captcha_renderer = null; var captcha_renderer = null;
{% if config.hcaptcha %} // If hcaptcha
function onCaptchaLoadHcaptcha() {
if (captcha_renderer === null) {
let renderer = {
renderOn: (container) => hcaptcha.render(container, {
sitekey: "{{ config.hcaptcha_public }}",
}),
remove: (widgetId) => { /* Not supported */ },
reset: (widgetId) => hcaptcha.reset(widgetId)
};
onCaptchaLoad(renderer);
}
}
function initCaptchaImpl() {
/*
* hcaptcha is set but the captcha_renderer is null? hcaptcha loaded before main.js could set up the callbacks.
* Init now.
*/
if (hcaptcha && captcha_renderer === null) {
if (active_page === 'index' || active_page === 'catalog' || active_page === 'thread') {
onCaptchaLoadHcaptcha();
}
}
}
{% endif %} // End if hcaptcha
{% if config.turnstile %} // If turnstile
// Wrapper function to be called from thread.html // Wrapper function to be called from thread.html
window.onCaptchaLoadTurnstile_post_reply = function() { window.onCaptchaLoadTurnstile_post_reply = function() {
onCaptchaLoadTurnstile('post-reply'); onCaptchaLoadTurnstile('post-reply');
@ -265,7 +293,7 @@ function onCaptchaLoadTurnstile(action) {
if (captcha_renderer === null) { if (captcha_renderer === null) {
let renderer = { let renderer = {
renderOn: function(container) { renderOn: function(container) {
let widgetId = turnstile.render(container, { let widgetId = turnstile.render('#' + container, {
sitekey: "{{ config.turnstile_public }}", sitekey: "{{ config.turnstile_public }}",
action: action, action: action,
}); });
@ -274,12 +302,8 @@ function onCaptchaLoadTurnstile(action) {
} }
return widgetId; return widgetId;
}, },
remove: function(widgetId) { remove: (widgetId) => turnstile.remove(widgetId),
turnstile.remove(widgetId); reset: (widgetId) => turnstile.reset(widgetId)
},
reset: function(widgetId) {
turnstile.reset(widgetId);
}
}; };
onCaptchaLoad(renderer); onCaptchaLoad(renderer);
@ -299,12 +323,12 @@ function initCaptchaImpl() {
} }
} }
} }
{% endif %} {% endif %} // End if turnstile
function onCaptchaLoad(renderer) { function onCaptchaLoad(renderer) {
captcha_renderer = renderer; captcha_renderer = renderer;
let widgetId = renderer.renderOn('#captcha-container'); let widgetId = renderer.renderOn('captcha-container');
if (widgetId === null) { if (widgetId === null) {
console.error('Could not render captcha!'); console.error('Could not render captcha!');
} }
@ -314,23 +338,24 @@ function onCaptchaLoad(renderer) {
}); });
} }
{% if config.dynamic_captcha %} {% if config.dynamic_captcha %} // If dynamic captcha
function isDynamicCaptchaEnabled() { function isDynamicCaptchaEnabled() {
let cookie = getCookie('captcha-required'); let cookie = getCookie('captcha-required');
return cookie === '1'; return cookie === '1';
} }
function initDynamicCaptcha() { function initCaptcha() {
if (isDynamicCaptchaEnabled()) { if (isDynamicCaptchaEnabled()) {
let captcha_hook = document.getElementById('captcha'); let captcha_hook = document.getElementById('captcha');
captcha_hook.style = ""; captcha_hook.style = "";
initCaptchaImpl(); initCaptchaImpl();
} }
} }
{% else %} {% endif %} // End if dynamic captcha
{% else %} // Else if any captcha
// No-op for `init()`. // No-op for `init()`.
function initDynamicCaptcha() {} function initCaptcha() {}
{% endif %} {% endif %} // End if any captcha
/* END CAPTCHA REGION */ /* END CAPTCHA REGION */
@ -500,7 +525,7 @@ var script_settings = function(script_name) {
function init() { function init() {
initStyleChooser(); initStyleChooser();
initDynamicCaptcha(); initCaptcha();
{% endraw %} {% endraw %}
{% if config.allow_delete %} {% if config.allow_delete %}

View file

@ -106,7 +106,7 @@
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if config.turnstile %} {% if config.hcaptcha or config.turnstile %}
{% if config.dynamic_captcha %} {% if config.dynamic_captcha %}
<tr id="captcha" style="display: none;"> <tr id="captcha" style="display: none;">
{% else %} {% else %}