From d413c717188c9bd906f715e7137955dc9a42a003 Mon Sep 17 00:00:00 2001 From: Roman Kelesidis Date: Sun, 26 Jan 2025 12:38:47 +0300 Subject: [PATCH] =?UTF-8?q?feat(captcha):=20Added=20some=20new=20services?= =?UTF-8?q?=20=F0=9F=A4=96=20(#1771)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 --- library/config.php | 4 +- library/includes/functions.php | 74 +++++++++---------- library/language/source/main.php | 2 +- src/Captcha/CaptchaInterface.php | 38 ++++++++++ src/Captcha/CloudflareTurnstileCaptcha.php | 75 +++++++++++++++++++ src/Captcha/GoogleCaptchaV2.php | 69 ++++++++++++++++++ src/Captcha/GoogleCaptchaV3.php | 70 ++++++++++++++++++ src/Captcha/HCaptcha.php | 76 ++++++++++++++++++++ src/Captcha/YandexSmartCaptcha.php | 84 ++++++++++++++++++++++ 9 files changed, 449 insertions(+), 43 deletions(-) create mode 100644 src/Captcha/CaptchaInterface.php create mode 100644 src/Captcha/CloudflareTurnstileCaptcha.php create mode 100644 src/Captcha/GoogleCaptchaV2.php create mode 100644 src/Captcha/GoogleCaptchaV3.php create mode 100644 src/Captcha/HCaptcha.php create mode 100644 src/Captcha/YandexSmartCaptcha.php diff --git a/library/config.php b/library/config.php index 0390e3905..d9aabae98 100644 --- a/library/config.php +++ b/library/config.php @@ -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 diff --git a/library/includes/functions.php b/library/includes/functions.php index 2fc92f90a..edb43d26f 100644 --- a/library/includes/functions.php +++ b/library/includes/functions.php @@ -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)) { - bb_die($lang['CAPTCHA_SETTINGS']); + // 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); - - switch ($mode) { - case 'get': - return " - -
- "; - break; - - case 'check': - $resp = $reCaptcha->verify( - request_var('g-recaptcha-response', ''), - $_SERVER["REMOTE_ADDR"] - ); - if ($resp->isSuccess()) { - return true; - } - break; - - default: - bb_simple_die(__FUNCTION__ . ": invalid mode '$mode'"); + // 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': + case 'check': + return $captcha->$mode(); + default: + bb_die(sprintf('Invalid mode: %s', $mode)); + } + } + return false; } diff --git a/library/language/source/main.php b/library/language/source/main.php index 490968965..cd72aba40 100644 --- a/library/language/source/main.php +++ b/library/language/source/main.php @@ -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'] = '

ReCaptcha not being fully configured

If you haven\'t already generated the keys, you can do it on https://www.google.com/recaptcha/admin.
After you generate the keys, you need to put them at the file library/config.php.

'; +$lang['CAPTCHA_SETTINGS'] = '

Captcha is not fully configured

Generate the keys using the dashboard of your captcha service, after you need to put them at the file library/config.php.

'; // Sending email $lang['REPLY_TO'] = 'Reply to'; diff --git a/src/Captcha/CaptchaInterface.php b/src/Captcha/CaptchaInterface.php new file mode 100644 index 000000000..21a749182 --- /dev/null +++ b/src/Captcha/CaptchaInterface.php @@ -0,0 +1,38 @@ +settings = $settings; + } + + /** + * Returns captcha widget + * + * @return string + */ + public function get(): string + { + return " + +
+ "; + } + + /** + * 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; + } +} diff --git a/src/Captcha/GoogleCaptchaV2.php b/src/Captcha/GoogleCaptchaV2.php new file mode 100644 index 000000000..a9e01f6ba --- /dev/null +++ b/src/Captcha/GoogleCaptchaV2.php @@ -0,0 +1,69 @@ +settings = $settings; + } + + /** + * Returns captcha widget + * + * @return string + */ + public function get(): string + { + return " + +
+ "; + } + + /** + * 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(); + } +} diff --git a/src/Captcha/GoogleCaptchaV3.php b/src/Captcha/GoogleCaptchaV3.php new file mode 100644 index 000000000..4a8aed893 --- /dev/null +++ b/src/Captcha/GoogleCaptchaV3.php @@ -0,0 +1,70 @@ +settings = $settings; + } + + /** + * Returns captcha widget + * + * @return string + */ + public function get(): string + { + return " + + + "; + } + + /** + * 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(); + } +} diff --git a/src/Captcha/HCaptcha.php b/src/Captcha/HCaptcha.php new file mode 100644 index 000000000..7484a77ff --- /dev/null +++ b/src/Captcha/HCaptcha.php @@ -0,0 +1,76 @@ +settings = $settings; + } + + /** + * Returns captcha widget + * + * @return string + */ + public function get(): string + { + return " +
+ "; + } + + /** + * 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; + } +} diff --git a/src/Captcha/YandexSmartCaptcha.php b/src/Captcha/YandexSmartCaptcha.php new file mode 100644 index 000000000..e5c77fb5a --- /dev/null +++ b/src/Captcha/YandexSmartCaptcha.php @@ -0,0 +1,84 @@ +settings = $settings; + } + + /** + * Returns captcha widget + * + * @return string + */ + public function get(): string + { + return " + +
"; + } + + /** + * 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'); + } +}