feat(captcha): Added some new services 🤖 (#1771)

* feat(captcha): Added some new services

* Updated

* Updated

* Update GoogleCaptchaV2.php

* Updated

* Updated

* Create HCaptcha.php

* Update HCaptcha.php

* Update HCaptcha.php

* Create YandexSmartCaptcha.php

* Update YandexSmartCaptcha.php

* Create CloudflareTurnstileCaptcha.php

* Update CloudflareTurnstileCaptcha.php

* Update config.php

* Update functions.php

* Update functions.php

* Update functions.php

* Update GoogleCaptchaV3.php

* Update GoogleCaptchaV3.php

* Update HCaptcha.php

* Update YandexSmartCaptcha.php

* Update CloudflareTurnstileCaptcha.php

* Updated

* Updated

* Update functions.php

* Updated

* Updated

* Update HCaptcha.php

* Updated

* Updated

* Updated

* Update functions.php

* Update main.php

* Updated

* Update HCaptcha.php

* Update HCaptcha.php

* Update GoogleCaptchaV3.php

* Update GoogleCaptchaV3.php

* Updated

* Updated

* Update GoogleCaptchaV2.php

* Update GoogleCaptchaV2.php
This commit is contained in:
Roman Kelesidis 2025-01-26 12:38:47 +03:00 committed by GitHub
commit d413c71718
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 449 additions and 43 deletions

View file

@ -670,12 +670,12 @@ $bb_cfg['group_avatars'] = [
];
// Captcha
// Get a Google reCAPTCHA API Key: https://www.google.com/recaptcha/admin
$bb_cfg['captcha'] = [
'disabled' => true,
'service' => 'googleV3', // Available services: googleV2, googleV3, hCaptcha, yandex, cloudflare
'public_key' => '',
'secret_key' => '',
'theme' => 'light', // theming (available: light, dark)
'theme' => 'light', // theming (available: light, dark) (working only if supported by captcha service)
];
// Atom feed

View file

@ -2044,56 +2044,50 @@ function hash_search($hash)
}
/**
* Функция для получения и проверки правильности ответа от Google ReCaptcha.
*
* @param $mode
* @param string $callback
* Function for checking captcha answer
*
* @param string $mode
* @return bool|string
*/
function bb_captcha($mode, $callback = '')
function bb_captcha(string $mode): bool|string
{
global $bb_cfg, $lang;
$secret = $bb_cfg['captcha']['secret_key'];
$public = $bb_cfg['captcha']['public_key'];
$cp_theme = $bb_cfg['captcha']['theme'] ?? 'light';
$settings = $bb_cfg['captcha'];
$settings['language'] = $bb_cfg['default_lang'];
if (!$bb_cfg['captcha']['disabled'] && (!$public || !$secret)) {
// Checking captcha settings
if (!$settings['disabled']) {
if (empty($settings['public_key']) || empty($settings['secret_key'])) {
bb_die($lang['CAPTCHA_SETTINGS']);
}
}
$reCaptcha = new \ReCaptcha\ReCaptcha($secret);
// Selecting captcha service
$captchaClasses = [
'googleV2' => \TorrentPier\Captcha\GoogleCaptchaV2::class,
'googleV3' => \TorrentPier\Captcha\GoogleCaptchaV3::class,
'hCaptcha' => \TorrentPier\Captcha\HCaptcha::class,
'yandex' => \TorrentPier\Captcha\YandexSmartCaptcha::class,
'cloudflare' => \TorrentPier\Captcha\CloudflareTurnstileCaptcha::class,
];
if (!isset($captchaClasses[$settings['service']])) {
bb_die(sprintf('Captcha service (%s) not supported', $settings['service']));
}
$captchaClass = $captchaClasses[$settings['service']];
$captcha = new $captchaClass($settings);
// Selection mode
if (isset($captcha)) {
switch ($mode) {
case 'get':
return "
<script type=\"text/javascript\">
var onloadCallback = function() {
grecaptcha.render('tp-captcha', {
'sitekey' : '" . $public . "',
'theme' : '" . $cp_theme . "',
'callback' : '" . $callback . "'
});
};
</script>
<div id=\"tp-captcha\"></div>
<script src=\"https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit\" async defer></script>";
break;
case 'check':
$resp = $reCaptcha->verify(
request_var('g-recaptcha-response', ''),
$_SERVER["REMOTE_ADDR"]
);
if ($resp->isSuccess()) {
return true;
}
break;
return $captcha->$mode();
default:
bb_simple_die(__FUNCTION__ . ": invalid mode '$mode'");
bb_die(sprintf('Invalid mode: %s', $mode));
}
}
return false;
}

View file

@ -3080,7 +3080,7 @@ $lang['UPLOAD_ERRORS'] = [
// Captcha
$lang['CAPTCHA'] = 'Check that you are not a robot';
$lang['CAPTCHA_WRONG'] = 'You could not confirm that you are not a robot';
$lang['CAPTCHA_SETTINGS'] = '<h2>ReCaptcha not being fully configured</h2><p>If you haven\'t already generated the keys, you can do it on <a href="https://www.google.com/recaptcha/admin">https://www.google.com/recaptcha/admin</a>.<br />After you generate the keys, you need to put them at the file library/config.php.</p>';
$lang['CAPTCHA_SETTINGS'] = '<h2>Captcha is not fully configured</h2><p>Generate the keys using the dashboard of your captcha service, after you need to put them at the file library/config.php.</p>';
// Sending email
$lang['REPLY_TO'] = 'Reply to';

View file

@ -0,0 +1,38 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
namespace TorrentPier\Captcha;
/**
* Interface CaptchaInterface
* @package TorrentPier\Captcha
*/
interface CaptchaInterface
{
/**
* Constructor
*
* @param array $settings
*/
public function __construct(array $settings);
/**
* Returns captcha widget
*
* @return string
*/
public function get(): string;
/**
* Checking captcha answer
*
* @return bool
*/
public function check(): bool;
}

View file

@ -0,0 +1,75 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
namespace TorrentPier\Captcha;
/**
* Class CloudflareTurnstileCaptcha
* @package TorrentPier\Captcha
*/
class CloudflareTurnstileCaptcha implements CaptchaInterface
{
/**
* Captcha service settings
*
* @var array
*/
private array $settings;
/**
* Service verification endpoint
*
* @var string
*/
private string $verifyEndpoint = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
/**
* Constructor
*
* @param array $settings
*/
public function __construct(array $settings)
{
$this->settings = $settings;
}
/**
* Returns captcha widget
*
* @return string
*/
public function get(): string
{
return "
<script src='https://challenges.cloudflare.com/turnstile/v0/api.js' async defer></script>
<div class='cf-turnstile' data-sitekey='{$this->settings['public_key']}' data-language='{$this->settings['language']}' data-theme='" . ($this->settings['theme'] ?? 'light') . "'></div>
";
}
/**
* Checking captcha answer
*
* @return bool
*/
public function check(): bool
{
$turnstileResponse = $_POST['cf-turnstile-response'] ?? '';
$postFields = "secret={$this->settings['secret_key']}&response=$turnstileResponse";
$ch = curl_init($this->verifyEndpoint);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postFields);
$response = curl_exec($ch);
curl_close($ch);
$responseData = json_decode($response);
return $responseData->success;
}
}

View file

@ -0,0 +1,69 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
namespace TorrentPier\Captcha;
use ReCaptcha\ReCaptcha;
/**
* Class GoogleCaptchaV2
* @package TorrentPier\Captcha
*/
class GoogleCaptchaV2 implements CaptchaInterface
{
/**
* Captcha service settings
*
* @var array
*/
private array $settings;
/**
* Constructor
*
* @param array $settings
*/
public function __construct(array $settings)
{
$this->settings = $settings;
}
/**
* Returns captcha widget
*
* @return string
*/
public function get(): string
{
return "
<script type='text/javascript'>
var onloadCallback = function() {
grecaptcha.render('tp-captcha', {
'sitekey': '{$this->settings['public_key']}',
'theme': '" . ($this->settings['theme'] ?? 'light') . "'
});
};
</script>
<div id='tp-captcha'></div>
<script src='https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit&hl={$this->settings['language']}' async defer></script>";
}
/**
* Checking captcha answer
*
* @return bool
*/
public function check(): bool
{
$reCaptcha = new ReCaptcha($this->settings['secret_key']);
$resp = $reCaptcha->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
return $resp->isSuccess();
}
}

View file

@ -0,0 +1,70 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
namespace TorrentPier\Captcha;
use ReCaptcha\ReCaptcha;
/**
* Class GoogleCaptchaV3
* @package TorrentPier\Captcha
*/
class GoogleCaptchaV3 implements CaptchaInterface
{
/**
* Captcha service settings
*
* @var array
*/
private array $settings;
/**
* Constructor
*
* @param array $settings
*/
public function __construct(array $settings)
{
$this->settings = $settings;
}
/**
* Returns captcha widget
*
* @return string
*/
public function get(): string
{
return "
<script src='https://www.google.com/recaptcha/api.js?render={$this->settings['public_key']}&lang={$this->settings['language']}'></script>
<script>
grecaptcha.ready(function() {
grecaptcha.execute('{$this->settings['public_key']}', { action:'validate_captcha' }).then(function(token) {
document.getElementById('g-recaptcha-response').value = token;
});
});
</script>
<input type='hidden' id='g-recaptcha-response' name='g-recaptcha-response'>";
}
/**
* Checking captcha answer
*
* @return bool
*/
public function check(): bool
{
$reCaptcha = new ReCaptcha($this->settings['secret_key']);
$resp = $reCaptcha
->setScoreThreshold(0.5)
->verify($_POST['g-recaptcha-response'], $_SERVER['REMOTE_ADDR']);
return $resp->isSuccess();
}
}

76
src/Captcha/HCaptcha.php Normal file
View file

@ -0,0 +1,76 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
namespace TorrentPier\Captcha;
/**
* Class HCaptcha
* @package TorrentPier\Captcha
*/
class HCaptcha implements CaptchaInterface
{
/**
* Captcha service settings
*
* @var array
*/
private array $settings;
/**
* Service verification endpoint
*
* @var string
*/
private string $verifyEndpoint = 'https://hcaptcha.com/siteverify';
/**
* Constructor
*
* @param array $settings
*/
public function __construct(array $settings)
{
$this->settings = $settings;
}
/**
* Returns captcha widget
*
* @return string
*/
public function get(): string
{
return "
<div class='h-captcha' data-sitekey='{$this->settings['public_key']}' data-theme='" . ($this->settings['theme'] ?? 'light') . "'></div>
<script src='https://www.hCaptcha.com/1/api.js?hl={$this->settings['language']}' async defer></script>";
}
/**
* Checking captcha answer
*
* @return bool
*/
public function check(): bool
{
$data = [
'secret' => $this->settings['secret_key'],
'response' => $_POST['h-captcha-response'] ?? null,
];
$verify = curl_init();
curl_setopt($verify, CURLOPT_URL, $this->verifyEndpoint);
curl_setopt($verify, CURLOPT_POST, true);
curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($data));
curl_setopt($verify, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($verify);
$responseData = json_decode($response);
return $responseData->success;
}
}

View file

@ -0,0 +1,84 @@
<?php
/**
* TorrentPier Bull-powered BitTorrent tracker engine
*
* @copyright Copyright (c) 2005-2025 TorrentPier (https://torrentpier.com)
* @link https://github.com/torrentpier/torrentpier for the canonical source repository
* @license https://github.com/torrentpier/torrentpier/blob/master/LICENSE MIT License
*/
namespace TorrentPier\Captcha;
/**
* Class YandexSmartCaptcha
* @package TorrentPier\Captcha
*/
class YandexSmartCaptcha implements CaptchaInterface
{
/**
* Captcha service settings
*
* @var array
*/
private array $settings;
/**
* Service verification endpoint
*
* @var string
*/
private string $verifyEndpoint = 'https://smartcaptcha.yandexcloud.net/validate';
/**
* Constructor
*
* @param array $settings
*/
public function __construct(array $settings)
{
$this->settings = $settings;
}
/**
* Returns captcha widget
*
* @return string
*/
public function get(): string
{
return "
<script src='https://smartcaptcha.yandexcloud.net/captcha.js' defer></script>
<div id='captcha-container' style='width: 402px;' class='smart-captcha' data-sitekey='{$this->settings['public_key']}' data-hl='{$this->settings['language']}'></div>";
}
/**
* Checking captcha answer
*
* @return bool
*/
public function check(): bool
{
$ch = curl_init($this->verifyEndpoint);
$args = [
'secret' => $this->settings['secret_key'],
'token' => $_POST['smart-token'] ?? null,
'ip' => $_SERVER['REMOTE_ADDR'],
];
curl_setopt($ch, CURLOPT_TIMEOUT, 1);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($args));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$serverOutput = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
return true;
}
$resp = json_decode($serverOutput);
return ($resp->status === 'ok');
}
}