mirror of
https://github.com/phpbb/phpbb.git
synced 2025-06-10 13:28:55 +00:00
[ticket/17413] Make turnstile captcha work on registration page
PHPBB-17413
This commit is contained in:
parent
01dd0b168a
commit
8290cdb7e7
7 changed files with 127 additions and 57 deletions
|
@ -1,7 +1,7 @@
|
||||||
<dl>
|
<dl>
|
||||||
<dt><div id="captcha_turnstile"></div></dt>
|
<dt><div id="captcha_turnstile"></div></dt>
|
||||||
</dl>
|
</dl>
|
||||||
{% INCLUDEJS 'https://challenges.cloudflare.com/turnstile/v0/api.js' %}
|
{% INCLUDEJS U_TURNSTILE_SCRIPT %}
|
||||||
<script>
|
<script>
|
||||||
function domReady(callBack) {
|
function domReady(callBack) {
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
|
|
|
@ -37,9 +37,12 @@ if (empty($lang) || !is_array($lang))
|
||||||
// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine
|
// in a url you again do not need to specify an order e.g., 'Click %sHERE%s' is fine
|
||||||
|
|
||||||
$lang = array_merge($lang, [
|
$lang = array_merge($lang, [
|
||||||
'CAPTCHA_TURNSTILE' => 'Turnstile',
|
'CAPTCHA_TURNSTILE' => 'Turnstile',
|
||||||
'CAPTCHA_TURNSTILE_SITEKEY' => 'Sitekey',
|
'CAPTCHA_TURNSTILE_INCORRECT' => 'The solution you provided was incorrect',
|
||||||
'CAPTCHA_TURNSTILE_SITEKEY_EXPLAIN' => 'Your Turnstile sitekey. The sitekey can be retrieved from your <a href="https://dash.cloudflare.com/?to=/:account/turnstile">Cloudflare dashboard</a>.',
|
'CAPTCHA_TURNSTILE_NOSCRIPT' => 'Please enable JavaScript in your browser to load the challenge.',
|
||||||
'CAPTCHA_TURNSTILE_SECRET' => 'Secret key',
|
'CAPTCHA_TURNSTILE_SECRET' => 'Secret key',
|
||||||
'CAPTCHA_TURNSTILE_SECRET_EXPLAIN' => 'Your Turnstile secret key. The secret key can be retrieved from your <a href="https://dash.cloudflare.com/?to=/:account/turnstile">Cloudflare dashboard</a>.',
|
'CAPTCHA_TURNSTILE_SECRET_EXPLAIN' => 'Your Turnstile secret key. The secret key can be retrieved from your <a href="https://dash.cloudflare.com/?to=/:account/turnstile">Cloudflare dashboard</a>.',
|
||||||
|
'CAPTCHA_TURNSTILE_SITEKEY' => 'Sitekey',
|
||||||
|
'CAPTCHA_TURNSTILE_SITEKEY_EXPLAIN' => 'Your Turnstile sitekey. The sitekey can be retrieved from your <a href="https://dash.cloudflare.com/?to=/:account/turnstile">Cloudflare dashboard</a>.',
|
||||||
|
'CAPTCHA_TURNSTILE_NOT_AVAILABLE' => 'In order to use Turnstile you must create a <a href="https://www.cloudflare.com/products/turnstile/">Cloudflare account</a>.',
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -19,6 +19,9 @@ abstract class base implements plugin_interface
|
||||||
/** @var string Confirm id hash */
|
/** @var string Confirm id hash */
|
||||||
protected string $confirm_id = '';
|
protected string $confirm_id = '';
|
||||||
|
|
||||||
|
/** @var string Last error message */
|
||||||
|
protected string $last_error = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for abstract captcha base class
|
* Constructor for abstract captcha base class
|
||||||
*
|
*
|
||||||
|
@ -29,6 +32,22 @@ abstract class base implements plugin_interface
|
||||||
$this->db = $db;
|
$this->db = $db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function is_solved(): bool
|
||||||
|
{
|
||||||
|
return $this->solved;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function get_error(): string
|
||||||
|
{
|
||||||
|
return $this->last_error;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -118,6 +118,14 @@ class legacy_wrapper implements plugin_interface
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
|
public function get_error(): string
|
||||||
|
{
|
||||||
|
return $this->last_error;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -73,6 +73,13 @@ interface plugin_interface
|
||||||
*/
|
*/
|
||||||
public function validate(): bool;
|
public function validate(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get error string from captcha
|
||||||
|
*
|
||||||
|
* @return string Error string, empty string if there is no error
|
||||||
|
*/
|
||||||
|
public function get_error(): string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return whether captcha was solved
|
* Return whether captcha was solved
|
||||||
*
|
*
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
namespace phpbb\captcha\plugins;
|
namespace phpbb\captcha\plugins;
|
||||||
|
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
use GuzzleHttp\Exception\GuzzleException;
|
||||||
use phpbb\config\config;
|
use phpbb\config\config;
|
||||||
use phpbb\db\driver\driver;
|
use phpbb\db\driver\driver;
|
||||||
use phpbb\db\driver\driver_interface;
|
use phpbb\db\driver\driver_interface;
|
||||||
|
@ -24,7 +26,11 @@ use phpbb\user;
|
||||||
|
|
||||||
class turnstile extends base
|
class turnstile extends base
|
||||||
{
|
{
|
||||||
private const API_ENDPOINT = 'https://api.cloudflare.com/client/v4/captcha/validate';
|
/** @var string URL to cloudflare turnstile API javascript */
|
||||||
|
private const SCRIPT_URL = 'https://challenges.cloudflare.com/turnstile/v0/api.js';
|
||||||
|
|
||||||
|
/** @var string API endpoint for turnstile verification */
|
||||||
|
private const VERIFY_ENDPOINT = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
|
||||||
|
|
||||||
/** @var config */
|
/** @var config */
|
||||||
protected config $config;
|
protected config $config;
|
||||||
|
@ -70,19 +76,28 @@ class turnstile extends base
|
||||||
$this->user = $user;
|
$this->user = $user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
public function is_available(): bool
|
public function is_available(): bool
|
||||||
{
|
{
|
||||||
$this->language->add_lang('captcha_turnstile');
|
$this->init(0);
|
||||||
|
|
||||||
return !empty($this->config->offsetGet('captcha_turnstile_sitekey'))
|
return !empty($this->config->offsetGet('captcha_turnstile_sitekey'))
|
||||||
&& !empty($this->config->offsetGet('captcha_turnstile_secret'));
|
&& !empty($this->config->offsetGet('captcha_turnstile_secret'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
public function has_config(): bool
|
public function has_config(): bool
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
public function get_name(): string
|
public function get_name(): string
|
||||||
{
|
{
|
||||||
return 'CAPTCHA_TURNSTILE';
|
return 'CAPTCHA_TURNSTILE';
|
||||||
|
@ -96,11 +111,17 @@ class turnstile extends base
|
||||||
$this->service_name = $name;
|
$this->service_name = $name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
public function init(int $type): void
|
public function init(int $type): void
|
||||||
{
|
{
|
||||||
$this->language->add_lang('captcha_turnstile');
|
$this->language->add_lang('captcha_turnstile');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
public function get_hidden_fields(): array
|
public function get_hidden_fields(): array
|
||||||
{
|
{
|
||||||
$hidden_fields = [];
|
$hidden_fields = [];
|
||||||
|
@ -114,70 +135,55 @@ class turnstile extends base
|
||||||
return $hidden_fields;
|
return $hidden_fields;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*/
|
||||||
public function validate(): bool
|
public function validate(): bool
|
||||||
{
|
{
|
||||||
// Implement server-side validation logic here
|
// Retrieve form data for verification
|
||||||
// Example: Validate the submitted CAPTCHA value using Cloudflare API
|
$form_data = [
|
||||||
|
'secret' => $this->config['captcha_turnstile_secret'],
|
||||||
// Your Cloudflare API credentials
|
'response' => $this->request->variable('cf-turnstile-response', ''),
|
||||||
$api_email = 'your_email@example.com';
|
'remoteip' => $this->request->header('CF-Connecting-IP'),
|
||||||
$api_key = 'your_api_key';
|
//'idempotency_key' => $this->confirm_id, // check if we need this
|
||||||
|
|
||||||
// Cloudflare API endpoint for CAPTCHA verification
|
|
||||||
$endpoint = 'https://api.cloudflare.com/client/v4/captcha/validate';
|
|
||||||
|
|
||||||
// CAPTCHA data to be sent in the request
|
|
||||||
$data = [
|
|
||||||
'email' => $api_email,
|
|
||||||
'key' => $api_key,
|
|
||||||
'response' => $this->confirm_code
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Initialize cURL session
|
// Create guzzle client
|
||||||
$ch = curl_init();
|
$client = new Client();
|
||||||
|
|
||||||
// Set cURL options
|
// Check captcha with turnstile API
|
||||||
curl_setopt_array($ch, [
|
try
|
||||||
CURLOPT_URL => $endpoint,
|
{
|
||||||
CURLOPT_POST => true,
|
$response = $client->request('POST', self::VERIFY_ENDPOINT, [
|
||||||
CURLOPT_POSTFIELDS => json_encode($data),
|
'form_params' => $form_data,
|
||||||
CURLOPT_HTTPHEADER => [
|
]);
|
||||||
'Content-Type: application/json',
|
}
|
||||||
'Accept: application/json'
|
catch (GuzzleException)
|
||||||
],
|
{
|
||||||
CURLOPT_RETURNTRANSFER => true
|
// Something went wrong during the request to Cloudflare, assume captcha was bad
|
||||||
]);
|
$this->solved = false;
|
||||||
|
|
||||||
// Execute the cURL request
|
|
||||||
$response = curl_exec($ch);
|
|
||||||
|
|
||||||
// Check for errors
|
|
||||||
if ($response === false) {
|
|
||||||
// Handle cURL error
|
|
||||||
curl_close($ch);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the JSON response
|
// Decode the JSON response
|
||||||
$result = json_decode($response, true);
|
$result = json_decode($response->getBody(), true);
|
||||||
|
|
||||||
// Check if the response indicates success
|
// Check if the response indicates success
|
||||||
if (isset($result['success']) && $result['success'] === true) {
|
if (isset($result['success']) && $result['success'] === true)
|
||||||
// CAPTCHA validation passed
|
{
|
||||||
curl_close($ch);
|
$this->solved = true;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
// CAPTCHA validation failed
|
else
|
||||||
curl_close($ch);
|
{
|
||||||
|
$this->last_error = $this->language->lang('CAPTCHA_TURNSTILE_INCORRECT');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function is_solved(): bool
|
/**
|
||||||
{
|
* {@inheritDoc}
|
||||||
return false;
|
*/
|
||||||
}
|
|
||||||
|
|
||||||
public function reset(): void
|
public function reset(): void
|
||||||
{
|
{
|
||||||
// TODO: Implement reset() method.
|
// TODO: Implement reset() method.
|
||||||
|
@ -191,11 +197,26 @@ class turnstile extends base
|
||||||
|
|
||||||
public function get_template(): string
|
public function get_template(): string
|
||||||
{
|
{
|
||||||
return 'custom_captcha.html'; // Template file for displaying the CAPTCHA
|
if ($this->is_solved())
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->template->assign_vars([
|
||||||
|
'S_TURNSTILE_AVAILABLE' => $this->is_available(),
|
||||||
|
'TURNSTILE_SITEKEY' => $this->config->offsetGet('captcha_turnstile_sitekey'),
|
||||||
|
'U_TURNSTILE_SCRIPT' => self::SCRIPT_URL,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return 'captcha_turnstile.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function get_demo_template(): string
|
public function get_demo_template(): string
|
||||||
{
|
{
|
||||||
|
$this->template->assign_vars([
|
||||||
|
'U_TURNSTILE_SCRIPT' => self::SCRIPT_URL,
|
||||||
|
]);
|
||||||
|
|
||||||
return 'captcha_turnstile_acp_demo.html';
|
return 'captcha_turnstile_acp_demo.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
phpBB/styles/prosilver/template/captcha_turnstile.html
Normal file
12
phpBB/styles/prosilver/template/captcha_turnstile.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{% if S_TURNSTILE_AVAILABLE %}
|
||||||
|
<noscript>
|
||||||
|
<div>{{ lang('CAPTCHA_TURNSTILE_NOSCRIPT') }}</div>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<script src="{{ U_TURNSTILE_SCRIPT }}" async defer></script>
|
||||||
|
|
||||||
|
{# The cf-turnstile class is used in JavaScript #}
|
||||||
|
<div class="cf-turnstile" data-sitekey="{{ TURNSTILE_SITEKEY }}"></div>
|
||||||
|
{% else %}
|
||||||
|
{{ lang('CAPTCHA_TURNSTILE_NOT_AVAILABLE') }}
|
||||||
|
{% endif %}
|
Loading…
Add table
Reference in a new issue