mirror of
https://github.com/vichan-devel/vichan.git
synced 2025-01-31 12:23:48 +01:00
Merge pull request #793 from Zankaria/captcha-rework-vichan
Refactor (again) the captcha backend + add support for dynamic captchas
This commit is contained in:
commit
5ee48c5865
@ -351,39 +351,39 @@
|
|||||||
//);
|
//);
|
||||||
$config['simple_spam'] = false;
|
$config['simple_spam'] = false;
|
||||||
|
|
||||||
// Enable reCaptcha to make spam even harder. Rarely necessary.
|
$config['captcha'] = [
|
||||||
$config['recaptcha'] = false;
|
// Can be false, 'recaptcha', 'hcaptcha' or 'native'.
|
||||||
|
'provider' => false,
|
||||||
// Public and private key pair from https://www.google.com/recaptcha/admin/create
|
/*
|
||||||
$config['recaptcha_public'] = '6LcXTcUSAAAAAKBxyFWIt2SO8jwx4W7wcSMRoN3f';
|
* If not false, the captcha is dynamically injected on the client if the web server set the `captcha-required`
|
||||||
$config['recaptcha_private'] = '6LcXTcUSAAAAAOGVbVdhmEM1_SyRF4xTKe8jbzf_';
|
* cookie to 1. The configuration value should be set the IP for which the captcha should be verified.
|
||||||
|
*
|
||||||
// Enable hCaptcha as an alternative to reCAPTCHA.
|
* Example:
|
||||||
$config['hcaptcha'] = false;
|
*
|
||||||
|
* // Verify the captcha for users sending posts from the loopback address.
|
||||||
// Public and private key pair for using hCaptcha.
|
* $config['captcha']['dynamic'] = '127.0.0.1';
|
||||||
$config['hcaptcha_public'] = '7a4b21e0-dc53-46f2-a9f8-91d2e74b63a0';
|
*/
|
||||||
$config['hcaptcha_private'] = '0x4e9A01bE637b51dC41a7Ea9865C3fDe4aB72Cf17';
|
'dynamic' => false,
|
||||||
|
'recaptcha' => [
|
||||||
// Enable Custom Captcha you need to change a couple of settings
|
'sitekey' => '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI',
|
||||||
//Read more at: /inc/captcha/readme.md
|
'secret' => '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe',
|
||||||
$config['captcha'] = array();
|
],
|
||||||
|
'hcaptcha' => [
|
||||||
// Enable custom captcha provider
|
'sitekey' => '10000000-ffff-ffff-ffff-000000000001',
|
||||||
$config['captcha']['enabled'] = false;
|
'secret' => '0x0000000000000000000000000000000000000000',
|
||||||
|
],
|
||||||
//New thread captcha
|
// To enable the native captcha you need to change a couple of settings. Read more at: /inc/captcha/readme.md
|
||||||
//Require solving a captcha to post a thread.
|
'native' => [
|
||||||
//Default off.
|
// Custom captcha get provider path (if not working get the absolute path aka your url).
|
||||||
$config['new_thread_capt'] = false;
|
'provider_get' => '../inc/captcha/entrypoint.php',
|
||||||
|
// Custom captcha check provider path
|
||||||
// Custom captcha get provider path (if not working get the absolute path aka your url.)
|
'provider_check' => '../inc/captcha/entrypoint.php',
|
||||||
$config['captcha']['provider_get'] = '../inc/captcha/entrypoint.php';
|
// Custom captcha extra field (eg. charset)
|
||||||
// Custom captcha check provider path
|
'extra' => 'abcdefghijklmnopqrstuvwxyz',
|
||||||
$config['captcha']['provider_check'] = '../inc/captcha/entrypoint.php';
|
// New thread captcha. Require solving a captcha to post a thread.
|
||||||
|
'new_thread_capt' => false
|
||||||
// Custom captcha extra field (eg. charset)
|
]
|
||||||
$config['captcha']['extra'] = 'abcdefghijklmnopqrstuvwxyz';
|
];
|
||||||
|
|
||||||
// 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;
|
||||||
|
@ -3,6 +3,10 @@ namespace Vichan;
|
|||||||
|
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
use Vichan\Driver\{HttpDriver, HttpDrivers, Log, LogDrivers};
|
use Vichan\Driver\{HttpDriver, HttpDrivers, Log, LogDrivers};
|
||||||
|
use Vichan\Service\HCaptchaQuery;
|
||||||
|
use Vichan\Service\NativeCaptchaQuery;
|
||||||
|
use Vichan\Service\ReCaptchaQuery;
|
||||||
|
use Vichan\Service\RemoteCaptchaQuery;
|
||||||
|
|
||||||
defined('TINYBOARD') or exit;
|
defined('TINYBOARD') or exit;
|
||||||
|
|
||||||
@ -53,6 +57,34 @@ function build_context(array $config): Context {
|
|||||||
HttpDriver::class => function($c) {
|
HttpDriver::class => function($c) {
|
||||||
$config = $c->get('config');
|
$config = $c->get('config');
|
||||||
return HttpDrivers::getHttpDriver($config['upload_by_url_timeout'], $config['max_filesize']);
|
return HttpDrivers::getHttpDriver($config['upload_by_url_timeout'], $config['max_filesize']);
|
||||||
|
},
|
||||||
|
RemoteCaptchaQuery::class => function($c) {
|
||||||
|
$config = $c->get('config');
|
||||||
|
$http = $c->get(HttpDriver::class);
|
||||||
|
switch ($config['captcha']['provider']) {
|
||||||
|
case 'recaptcha':
|
||||||
|
return new ReCaptchaQuery($http, $config['captcha']['recaptcha']['secret']);
|
||||||
|
case 'hcaptcha':
|
||||||
|
return new HCaptchaQuery(
|
||||||
|
$http,
|
||||||
|
$config['captcha']['hcaptcha']['secret'],
|
||||||
|
$config['captcha']['hcaptcha']['sitekey']
|
||||||
|
);
|
||||||
|
default:
|
||||||
|
throw new RuntimeException('No remote captcha service available');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NativeCaptchaQuery::class => function($c) {
|
||||||
|
$config = $c->get('config');
|
||||||
|
if ($config['captcha']['provider'] !== 'native') {
|
||||||
|
throw new RuntimeException('No native captcha service available');
|
||||||
|
}
|
||||||
|
return new NativeCaptchaQuery(
|
||||||
|
$c->get(HttpDriver::class),
|
||||||
|
$config['domain'],
|
||||||
|
$config['captcha']['native']['provider_check'],
|
||||||
|
$config['captcha']['native']['extra']
|
||||||
|
);
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -6,95 +6,136 @@ use Vichan\Driver\HttpDriver;
|
|||||||
defined('TINYBOARD') or exit;
|
defined('TINYBOARD') or exit;
|
||||||
|
|
||||||
|
|
||||||
class RemoteCaptchaQuery {
|
class ReCaptchaQuery implements RemoteCaptchaQuery {
|
||||||
private HttpDriver $http;
|
private HttpDriver $http;
|
||||||
private string $secret;
|
private string $secret;
|
||||||
private string $endpoint;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new CaptchaRemoteQueries instance using the google recaptcha service.
|
* Creates a new ReCaptchaQuery using the google recaptcha service.
|
||||||
*
|
*
|
||||||
* @param HttpDriver $http The http client.
|
* @param HttpDriver $http The http client.
|
||||||
* @param string $secret Server side secret.
|
* @param string $secret Server side secret.
|
||||||
* @return CaptchaRemoteQueries A new captcha query instance.
|
* @return ReCaptchaQuery A new ReCaptchaQuery query instance.
|
||||||
*/
|
*/
|
||||||
public static function withRecaptcha(HttpDriver $http, string $secret): RemoteCaptchaQuery {
|
public function __construct(HttpDriver $http, string $secret) {
|
||||||
return new self($http, $secret, 'https://www.google.com/recaptcha/api/siteverify');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new CaptchaRemoteQueries instance using the hcaptcha service.
|
|
||||||
*
|
|
||||||
* @param HttpDriver $http The http client.
|
|
||||||
* @param string $secret Server side secret.
|
|
||||||
* @return CaptchaRemoteQueries A new captcha query instance.
|
|
||||||
*/
|
|
||||||
public static function withHCaptcha(HttpDriver $http, string $secret): RemoteCaptchaQuery {
|
|
||||||
return new self($http, $secret, 'https://hcaptcha.com/siteverify');
|
|
||||||
}
|
|
||||||
|
|
||||||
private function __construct(HttpDriver $http, string $secret, string $endpoint) {
|
|
||||||
$this->http = $http;
|
$this->http = $http;
|
||||||
$this->secret = $secret;
|
$this->secret = $secret;
|
||||||
$this->endpoint = $endpoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public function responseField(): string {
|
||||||
* Checks if the user at the remote ip passed the captcha.
|
return 'g-recaptcha-response';
|
||||||
*
|
}
|
||||||
* @param string $response User provided response.
|
|
||||||
* @param string $remote_ip User ip.
|
|
||||||
* @return bool Returns true if the user passed the captcha.
|
|
||||||
* @throws RuntimeException|JsonException Throws on IO errors or if it fails to decode the answer.
|
|
||||||
*/
|
|
||||||
public function verify(string $response, string $remote_ip): bool {
|
|
||||||
$data = array(
|
|
||||||
'secret' => $this->secret,
|
|
||||||
'response' => $response,
|
|
||||||
'remoteip' => $remote_ip
|
|
||||||
);
|
|
||||||
|
|
||||||
$ret = $this->http->requestGet($this->endpoint, $data);
|
public function verify(string $response, ?string $remote_ip): bool {
|
||||||
|
$data = [
|
||||||
|
'secret' => $this->secret,
|
||||||
|
'response' => $response
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($remote_ip !== null) {
|
||||||
|
$data['remoteip'] = $remote_ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = $this->http->requestGet('https://www.google.com/recaptcha/api/siteverify', $data);
|
||||||
$resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
|
$resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
return isset($resp['success']) && $resp['success'];
|
return isset($resp['success']) && $resp['success'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class HCaptchaQuery implements RemoteCaptchaQuery {
|
||||||
|
private HttpDriver $http;
|
||||||
|
private string $secret;
|
||||||
|
private string $sitekey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new HCaptchaQuery using the hCaptcha service.
|
||||||
|
*
|
||||||
|
* @param HttpDriver $http The http client.
|
||||||
|
* @param string $secret Server side secret.
|
||||||
|
* @return HCaptchaQuery A new hCaptcha query instance.
|
||||||
|
*/
|
||||||
|
public function __construct(HttpDriver $http, string $secret, string $sitekey) {
|
||||||
|
$this->http = $http;
|
||||||
|
$this->secret = $secret;
|
||||||
|
$this->sitekey = $sitekey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function responseField(): string {
|
||||||
|
return 'h-captcha-response';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function verify(string $response, ?string $remote_ip): bool {
|
||||||
|
$data = [
|
||||||
|
'secret' => $this->secret,
|
||||||
|
'response' => $response,
|
||||||
|
'sitekey' => $this->sitekey
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($remote_ip !== null) {
|
||||||
|
$data['remoteip'] = $remote_ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ret = $this->http->requestGet('https://hcaptcha.com/siteverify', $data);
|
||||||
|
$resp = json_decode($ret, true, 16, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
return isset($resp['success']) && $resp['success'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RemoteCaptchaQuery {
|
||||||
|
/**
|
||||||
|
* Name of the response field in the form data expected by the implementation.
|
||||||
|
*
|
||||||
|
* @return string The name of the field.
|
||||||
|
*/
|
||||||
|
public function responseField(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the user at the remote ip passed the captcha.
|
||||||
|
*
|
||||||
|
* @param string $response User provided response.
|
||||||
|
* @param ?string $remote_ip User ip. Leave to null to only check the response value.
|
||||||
|
* @return bool Returns true if the user passed the captcha.
|
||||||
|
* @throws RuntimeException|JsonException Throws on IO errors or if it fails to decode the answer.
|
||||||
|
*/
|
||||||
|
public function verify(string $response, ?string $remote_ip): bool;
|
||||||
|
}
|
||||||
|
|
||||||
class NativeCaptchaQuery {
|
class NativeCaptchaQuery {
|
||||||
private HttpDriver $http;
|
private HttpDriver $http;
|
||||||
private string $domain;
|
private string $domain;
|
||||||
private string $provider_check;
|
private string $provider_check;
|
||||||
|
private string $extra;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param HttpDriver $http The http client.
|
* @param HttpDriver $http The http client.
|
||||||
* @param string $domain The server's domain.
|
* @param string $domain The server's domain.
|
||||||
* @param string $provider_check Path to the endpoint.
|
* @param string $provider_check Path to the endpoint.
|
||||||
|
* @param string $extra Extra http parameters.
|
||||||
*/
|
*/
|
||||||
function __construct(HttpDriver $http, string $domain, string $provider_check) {
|
function __construct(HttpDriver $http, string $domain, string $provider_check, string $extra) {
|
||||||
$this->http = $http;
|
$this->http = $http;
|
||||||
$this->domain = $domain;
|
$this->domain = $domain;
|
||||||
$this->provider_check = $provider_check;
|
$this->provider_check = $provider_check;
|
||||||
|
$this->extra = $extra;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if the user at the remote ip passed the native vichan captcha.
|
* Checks if the user at the remote ip passed the native vichan captcha.
|
||||||
*
|
*
|
||||||
* @param string $extra Extra http parameters.
|
|
||||||
* @param string $user_text Remote user's text input.
|
* @param string $user_text Remote user's text input.
|
||||||
* @param string $user_cookie Remote user cookie.
|
* @param string $user_cookie Remote user cookie.
|
||||||
* @return bool Returns true if the user passed the check.
|
* @return bool Returns true if the user passed the check.
|
||||||
* @throws RuntimeException Throws on IO errors.
|
* @throws RuntimeException Throws on IO errors.
|
||||||
*/
|
*/
|
||||||
public function verify(string $extra, string $user_text, string $user_cookie): bool {
|
public function verify(string $user_text, string $user_cookie): bool {
|
||||||
$data = array(
|
$data = [
|
||||||
'mode' => 'check',
|
'mode' => 'check',
|
||||||
'text' => $user_text,
|
'text' => $user_text,
|
||||||
'extra' => $extra,
|
'extra' => $this->extra,
|
||||||
'cookie' => $user_cookie
|
'cookie' => $user_cookie
|
||||||
);
|
];
|
||||||
|
|
||||||
$ret = $this->http->requestGet($this->domain . '/' . $this->provider_check, $data);
|
$ret = $this->http->requestGet($this->domain . '/' . $this->provider_check, $data);
|
||||||
return $ret === '1';
|
return $ret === '1';
|
||||||
|
49
post.php
49
post.php
@ -629,10 +629,15 @@ if (isset($_POST['delete'])) {
|
|||||||
|
|
||||||
// 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.
|
||||||
try {
|
try {
|
||||||
|
$provider = $config['captcha']['provider'];
|
||||||
|
$new_thread_capt = $config['captcha']['native']['new_thread_capt'];
|
||||||
|
$dynamic = $config['captcha']['dynamic'];
|
||||||
|
|
||||||
// With our custom captcha provider
|
// With our custom captcha provider
|
||||||
if ($config['captcha']['enabled'] || ($post['op'] && $config['new_thread_capt'])) {
|
if (($provider === 'native' && !$new_thread_capt)
|
||||||
$query = new NativeCaptchaQuery($context->get(HttpDriver::class), $config['domain'], $config['captcha']['provider_check']);
|
|| ($provider === 'native' && $new_thread_capt && $post['op'])) {
|
||||||
$success = $query->verify($config['captcha']['extra'], $_POST['captcha_text'], $_POST['captcha_cookie']);
|
$query = $context->get(NativeCaptchaQuery::class);
|
||||||
|
$success = $query->verify($_POST['captcha_text'], $_POST['captcha_cookie']);
|
||||||
|
|
||||||
if (!$success) {
|
if (!$success) {
|
||||||
error(
|
error(
|
||||||
@ -648,29 +653,23 @@ if (isset($_POST['delete'])) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remote 3rd party captchas.
|
// Remote 3rd party captchas.
|
||||||
else {
|
elseif ($provider && (!$dynamic || $dynamic === $_SERVER['REMOTE_ADDR'])) {
|
||||||
// recaptcha
|
$query = $content->get(RemoteCaptchaQuery::class);
|
||||||
if ($config['recaptcha']) {
|
$field = $query->responseField();
|
||||||
if (!isset($_POST['g-recaptcha-response'])) {
|
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
|
||||||
$response = $_POST['g-recaptcha-response'];
|
|
||||||
$query = RemoteCaptchaQuery::withRecaptcha($context->get(HttpDriver::class), $config['recaptcha_private']);
|
|
||||||
}
|
|
||||||
// hCaptcha
|
|
||||||
elseif ($config['hcaptcha']) {
|
|
||||||
if (!isset($_POST['h-captcha-response'])) {
|
|
||||||
error($config['error']['bot']);
|
|
||||||
}
|
|
||||||
$response = $_POST['h-captcha-response'];
|
|
||||||
$query = RemoteCaptchaQuery::withHCaptcha($context->get(HttpDriver::class), $config['hcaptcha_private']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isset($query, $response)) {
|
if (!isset($_POST[$field])) {
|
||||||
$success = $query->verify($response, $_SERVER['REMOTE_ADDR']);
|
error($config['error']['bot']);
|
||||||
if (!$success) {
|
}
|
||||||
error($config['error']['captcha']);
|
$response = $_POST[$field];
|
||||||
}
|
/*
|
||||||
|
* Do not query with the IP if the mode is dynamic. This config is meant for proxies and internal
|
||||||
|
* loopback addresses.
|
||||||
|
*/
|
||||||
|
$ip = $dynamic ? null : $_SERVER['REMOTE_ADDR'];
|
||||||
|
|
||||||
|
$success = $query->verify($response, $ip);
|
||||||
|
if (!$success) {
|
||||||
|
error($config['error']['captcha']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
|
@ -1,55 +1,55 @@
|
|||||||
<link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}">
|
<link rel="stylesheet" media="screen" href="{{ config.url_stylesheet }}">
|
||||||
{% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %}
|
{% if config.url_favicon %}<link rel="shortcut icon" href="{{ config.url_favicon }}">{% endif %}
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
|
||||||
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}">{% endif %}
|
{% if config.meta_keywords %}<meta name="keywords" content="{{ config.meta_keywords }}">{% endif %}
|
||||||
{% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %}
|
{% if config.default_stylesheet.1 != '' %}<link rel="stylesheet" type="text/css" id="stylesheet" href="{{ config.uri_stylesheets }}{{ config.default_stylesheet.1 }}">{% endif %}
|
||||||
{% if config.font_awesome %}<link rel="stylesheet" href="{{ config.root }}{{ config.font_awesome_css }}">{% endif %}
|
{% if config.font_awesome %}<link rel="stylesheet" href="{{ config.root }}{{ config.font_awesome_css }}">{% endif %}
|
||||||
{% if config.country_flags_condensed %}<link rel="stylesheet" href="{{ config.root }}{{ config.country_flags_condensed_css }}">{% endif %}
|
{% if config.country_flags_condensed %}<link rel="stylesheet" href="{{ config.root }}{{ config.country_flags_condensed_css }}">{% endif %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var configRoot="{{ config.root }}";
|
var configRoot="{{ config.root }}";
|
||||||
var inMod = {% if mod %}true{% else %}false{% endif %};
|
var inMod = {% if mod %} true {% else %} false {% endif %};
|
||||||
var modRoot="{{ config.root }}"+(inMod ? "mod.php?/" : "");
|
var modRoot = "{{ config.root }}" + (inMod ? "mod.php?/" : "");
|
||||||
</script>
|
</script>
|
||||||
{% if not nojavascript %}
|
{% if not nojavascript %}
|
||||||
<script type="text/javascript" src="{{ config.url_javascript }}"></script>
|
<script type="text/javascript" src="{{ config.url_javascript }}"></script>
|
||||||
{% if not config.additional_javascript_compile %}
|
{% if not config.additional_javascript_compile %}
|
||||||
{% for javascript in config.additional_javascript %}<script type="text/javascript" src="{{ config.additional_javascript_url }}{{ javascript }}"></script>{% endfor %}
|
{% for javascript in config.additional_javascript %}<script type="text/javascript" src="{{ config.additional_javascript_url }}{{ javascript }}"></script>{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if mod %}
|
{% if mod %}
|
||||||
<script type="text/javascript" src="/js/mod/mod_snippets.js"></script>
|
<script type="text/javascript" src="/js/mod/mod_snippets.js"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.recaptcha %}<script src="//www.recaptcha.net/recaptcha/api.js"></script>
|
{% if config.captcha.provider == 'recaptcha' %}<script src="//www.recaptcha.net/recaptcha/api.js"></script>
|
||||||
<style type="text/css">{% verbatim %}
|
<style type="text/css">{% verbatim %}
|
||||||
#recaptcha_area {
|
#recaptcha_area {
|
||||||
float: none !important;
|
float: none !important;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
#recaptcha_logo, #recaptcha_privacy {
|
#recaptcha_logo, #recaptcha_privacy {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#recaptcha_table {
|
#recaptcha_table {
|
||||||
border: none !important;
|
border: none !important;
|
||||||
}
|
}
|
||||||
#recaptcha_table tr:first-child {
|
#recaptcha_table tr:first-child {
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
.recaptchatable img {
|
.recaptchatable img {
|
||||||
float: none !important;
|
float: none !important;
|
||||||
}
|
}
|
||||||
#recaptcha_response_field {
|
#recaptcha_response_field {
|
||||||
font-size: 10pt !important;
|
font-size: 10pt !important;
|
||||||
border: 1px solid #a9a9a9 !important;
|
border: 1px solid #a9a9a9 !important;
|
||||||
padding: 1px !important;
|
padding: 1px !important;
|
||||||
}
|
}
|
||||||
td.recaptcha_image_cell {
|
td.recaptcha_image_cell {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
}
|
}
|
||||||
.recaptchatable, #recaptcha_area tr, #recaptcha_area td, #recaptcha_area th {
|
.recaptchatable, #recaptcha_area tr, #recaptcha_area td, #recaptcha_area th {
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
}
|
}
|
||||||
{% endverbatim %}</style>{% endif %}
|
{% endverbatim %}</style>{% endif %}
|
||||||
{% if config.hcaptcha %}
|
{% if config.captcha.provider.hcaptcha %}
|
||||||
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
<script src="https://js.hcaptcha.com/1/api.js" async defer></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -222,6 +222,36 @@ function getCookie(cookie_name) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{% endraw %}
|
||||||
|
{% if config.captcha.dynamic %}
|
||||||
|
function is_dynamic_captcha_enabled() {
|
||||||
|
let cookie = get_cookie('require-captcha');
|
||||||
|
return cookie === '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_captcha_pub_key() {
|
||||||
|
{% if config.captcha.provider === 'recaptcha' %}
|
||||||
|
return "{{ config.captcha.recaptcha.sitekey }}";
|
||||||
|
{% else %}
|
||||||
|
return null;
|
||||||
|
{% endif %}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_dynamic_captcha() {
|
||||||
|
if (!is_dynamic_captcha_enabled()) {
|
||||||
|
let pub_key = get_captcha_pub_key();
|
||||||
|
if (!pub_key) {
|
||||||
|
console.error("Missing public captcha key!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let captcha_hook = document.getElementById('captcha');
|
||||||
|
captcha_hook.style = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
{% endif %}
|
||||||
|
{% raw %}
|
||||||
|
|
||||||
function highlightReply(id) {
|
function highlightReply(id) {
|
||||||
if (typeof window.event != "undefined" && event.which == 2) {
|
if (typeof window.event != "undefined" && event.which == 2) {
|
||||||
// don't highlight on middle click
|
// don't highlight on middle click
|
||||||
|
@ -72,26 +72,30 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% if config.recaptcha %}
|
{% if config.captcha.provider == 'recaptcha' %}
|
||||||
|
{% if config.captcha.dynamic %}
|
||||||
|
<tr id="captcha" style="display: none;">
|
||||||
|
{% else %}
|
||||||
<tr>
|
<tr>
|
||||||
|
{% endif %}
|
||||||
<th>
|
<th>
|
||||||
{% trans %}Verification{% endtrans %}
|
{% trans %}Verification{% endtrans %}
|
||||||
{{ antibot.html() }}
|
{{ antibot.html() }}
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="g-recaptcha" data-sitekey="{{ config.recaptcha_public }}"></div>
|
<div class="g-recaptcha" data-sitekey="{{ config.captcha.recaptcha.sitekey }}"></div>
|
||||||
{{ antibot.html() }}
|
{{ antibot.html() }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if config.hcaptcha %}
|
{% if config.captcha.provider == 'hcaptcha' %}
|
||||||
<tr>
|
<tr>
|
||||||
<th>
|
<th>
|
||||||
{% trans %}Verification{% endtrans %}
|
{% trans %}Verification{% endtrans %}
|
||||||
{{ antibot.html() }}
|
{{ antibot.html() }}
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<div class="h-captcha" data-sitekey="{{ config.hcaptcha_public }}"></div>
|
<div class="h-captcha" data-sitekey="{{ config.captcha.hcaptcha.sitekey }}"></div>
|
||||||
{{ antibot.html() }}
|
{{ antibot.html() }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -102,11 +106,11 @@
|
|||||||
{% trans %}Verification{% endtrans %}
|
{% trans %}Verification{% endtrans %}
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<script>load_captcha("{{ config.captcha.provider_get }}", "{{ config.captcha.extra }}");</script>
|
<script>load_captcha("{{ config.captcha.native.provider_get }}", "{{ config.native.captcha.extra }}");</script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<input class='captcha_text' type='text' name='captcha_text' size='32' maxlength='6' autocomplete='off'>
|
<input class='captcha_text' type='text' name='captcha_text' size='32' maxlength='6' autocomplete='off'>
|
||||||
<div class="captcha_html">
|
<div class="captcha_html">
|
||||||
<img src="/{{ config.captcha.provider_get }}?mode=get&raw=1">
|
<img src="/{{ config.captcha.native.provider_get }}?mode=get&raw=1">
|
||||||
</div>
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
</td>
|
</td>
|
||||||
@ -118,11 +122,11 @@
|
|||||||
{% trans %}Verification{% endtrans %}
|
{% trans %}Verification{% endtrans %}
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td>
|
||||||
<script>load_captcha("{{ config.captcha.provider_get }}", "{{ config.captcha.extra }}");</script>
|
<script>load_captcha("{{ config.captcha.native.provider_get }}", "{{ config.captcha.native.extra }}");</script>
|
||||||
<noscript>
|
<noscript>
|
||||||
<input class='captcha_text' type='text' name='captcha_text' size='32' maxlength='6' autocomplete='off'>
|
<input class='captcha_text' type='text' name='captcha_text' size='32' maxlength='6' autocomplete='off'>
|
||||||
<div class="captcha_html">
|
<div class="captcha_html">
|
||||||
<img src="/{{ config.captcha.provider_get }}?mode=get&raw=1">
|
<img src="/{{ config.captcha.native.provider_get }}?mode=get&raw=1">
|
||||||
</div>
|
</div>
|
||||||
</noscript>
|
</noscript>
|
||||||
</td>
|
</td>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user