Merge pull request #2112

This commit is contained in:
Kijin Sung 2023-06-19 22:16:55 +09:00
commit eb79a5c66f
6 changed files with 150 additions and 11 deletions

View file

@ -1,6 +1,6 @@
<?php
class spamfilter_reCAPTCHA
class spamfilter_captcha
{
protected static $verify_url = 'https://www.google.com/recaptcha/api/siteverify';
protected static $config = null;
@ -35,17 +35,17 @@ class spamfilter_reCAPTCHA
throw new Rhymix\Framework\Exception('msg_recaptcha_connection_error');
}
$verify = @json_decode($verify_request->body, true);
$verify = @json_decode($verify_request->body, true);
if (!$verify || !$verify['success'])
{
throw new Rhymix\Framework\Exception('msg_recaptcha_server_error');
}
if ($verify && isset($verify['error-codes']) && in_array('invalid-input-response', $verify['error-codes']))
if ($verify && isset($verify['error-codes']) && in_array('invalid-input-response', $verify['error-codes']))
{
throw new Rhymix\Framework\Exception('msg_recaptcha_invalid_response');
}
}
$_SESSION['recaptcha_authenticated'] = true;
$_SESSION['recaptcha_authenticated'] = true;
}
public function addScripts()

View file

@ -0,0 +1,78 @@
<?php
class spamfilter_captcha
{
protected static $verify_url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
protected static $config = null;
protected static $scripts_added = false;
protected static $instances_inserted = 0;
protected static $sequence = 1;
protected $_target_actions = [];
public static function init($config)
{
self::$config = $config;
}
public static function check()
{
$response = Context::get('g-recaptcha-response');
if (!$response)
{
throw new Rhymix\Framework\Exception('msg_recaptcha_invalid_response');
}
try
{
$verify_request = \Requests::post(self::$verify_url, array(), array(
'secret' => self::$config->secret_key,
'response' => $response,
'remoteip' => \RX_CLIENT_IP,
));
}
catch (\Requests_Exception $e)
{
throw new Rhymix\Framework\Exception('msg_recaptcha_connection_error');
}
$verify = @json_decode($verify_request->body, true);
if (!$verify || !$verify['success'])
{
throw new Rhymix\Framework\Exception('msg_recaptcha_server_error');
}
if ($verify && isset($verify['error-codes']) && in_array('invalid-input-response', $verify['error-codes']))
{
throw new Rhymix\Framework\Exception('msg_recaptcha_invalid_response');
}
$_SESSION['recaptcha_authenticated'] = true;
}
public function addScripts()
{
if (!self::$scripts_added)
{
self::$scripts_added = true;
Context::loadFile(array('./modules/spamfilter/tpl/js/turnstile.js', 'body'));
Context::addHtmlFooter('<script src="https://challenges.cloudflare.com/turnstile/v0/api.js?compat=recaptcha&amp;render=explicit&amp;onload=turnstileCallback" async defer></script>');
$html = '<div id="recaptcha-config" data-sitekey="%s" data-theme="%s" data-size="%s" data-targets="%s"></div>';
$html = sprintf($html, escape(self::$config->site_key), self::$config->theme ?: 'auto', self::$config->size ?: 'normal', implode(',', array_keys($this->_target_actions)));
Context::addHtmlFooter($html);
}
}
public function setTargetActions(array $target_actions)
{
$this->_target_actions = $target_actions;
}
public function isTargetAction(string $action): bool
{
return isset($this->_target_actions[$action]);
}
public function __toString()
{
return sprintf('<div id="recaptcha-instance-%d" class="g-recaptcha"></div>', self::$instances_inserted++);
}
}

View file

@ -77,7 +77,7 @@ class spamfilterAdminController extends spamfilter
{
$config->captcha = new stdClass;
}
$config->captcha->type = $vars->captcha_type === 'recaptcha' ? 'recaptcha' : 'none';
$config->captcha->type = in_array($vars->captcha_type, ['recaptcha', 'turnstile']) ? $vars->captcha_type : 'none';
$config->captcha->site_key = escape(utf8_trim($vars->site_key));
$config->captcha->secret_key = escape(utf8_trim($vars->secret_key));
if ($config->captcha->type !== 'none' && (!$config->captcha->site_key || !$config->captcha->secret_key))

View file

@ -225,7 +225,7 @@ class spamfilterController extends spamfilter
function triggerCheckCaptcha(&$obj)
{
$config = ModuleModel::getModuleConfig('spamfilter');
if (!isset($config) || !isset($config->captcha) || $config->captcha->type !== 'recaptcha' || !$config->captcha->site_key || !$config->captcha->secret_key)
if (!isset($config) || !isset($config->captcha) || !in_array($config->captcha->type, ['recaptcha','turnstile']) || !$config->captcha->site_key || !$config->captcha->secret_key)
{
return;
}
@ -260,16 +260,16 @@ class spamfilterController extends spamfilter
if (count($target_actions))
{
include_once __DIR__ . '/spamfilter.lib.php';
spamfilter_reCAPTCHA::init($config->captcha);
include_once __DIR__ . '/captcha/' . $config->captcha->type . '.php';
spamfilter_captcha::init($config->captcha);
if (strncasecmp('proc', $obj->act, 4) === 0)
{
spamfilter_reCAPTCHA::check();
spamfilter_captcha::check();
}
else
{
$captcha = new spamfilter_reCAPTCHA();
$captcha = new spamfilter_captcha();
$captcha->setTargetActions($target_actions);
$captcha->addScripts();
Context::set('captcha', $captcha);

View file

@ -14,6 +14,7 @@
<select id="captcha_type" name="captcha_type">
<option value="none" selected="selected"|cond="$config->captcha->type === 'none'">{$lang->notuse}</option>
<option value="recaptcha" selected="selected"|cond="$config->captcha->type === 'recaptcha'">reCAPTCHA</option>
<option value="turnstile" selected="selected"|cond="$config->captcha->type === 'turnstile'">Turnstile</option>
</select>
<p class="x_help-block">{$lang->about_captcha_position}</p>
</div>

View file

@ -0,0 +1,60 @@
function turnstileCallback() {
var recaptcha_config = $("#recaptcha-config");
var recaptcha_instances = $(".g-recaptcha");
var recaptcha_instance_id = 1;
var recaptcha_targets = String(recaptcha_config.data("targets")).split(",");
if (recaptcha_instances.length === 0) {
var autoinsert_candidates = $("form").filter(function() {
var actinput = $("input[name='act']", this);
if (actinput.length && actinput.val()) {
var act = String(actinput.val());
if (act.match(/^procMemberInsert$/i) && recaptcha_targets.indexOf("signup") > -1) {
return true;
}
if (act.match(/^procMemberLogin$/i) && recaptcha_targets.indexOf("login") > -1) {
return true;
}
if (act.match(/^procMember(FindAccount|ResendAuthMail)$/i) && recaptcha_targets.indexOf("recovery") > -1) {
return true;
}
if (act.match(/^proc[A-Z][a-zA-Z0-9_]+InsertDocument$/i) && recaptcha_targets.indexOf("document") > -1) {
return true;
}
if (act.match(/^proc[A-Z][a-zA-Z0-9_]+InsertComment$/i) && recaptcha_targets.indexOf("comment") > -1) {
return true;
}
}
var procfilter = $(this).attr("onsubmit");
if (procfilter && procfilter.match(/procFilter\b.+\binsert/i) && (recaptcha_targets.indexOf("document") > -1 || recaptcha_targets.indexOf("comment") > -1)) {
return true;
}
return false;
});
autoinsert_candidates.each(function() {
var new_instance = $('<div class="g-recaptcha"></div>');
new_instance.attr("id", "recaptcha-instance-" + recaptcha_instance_id++);
var autoinsert_point = $(this).find("button[type='submit'],input[type='submit']").parent();
if (autoinsert_point.size()) {
new_instance.insertBefore(autoinsert_point);
} else {
new_instance.appendTo($(this));
}
});
var recaptcha_instances = $(".g-recaptcha");
}
recaptcha_instances.each(function() {
var instance = $(this);
var theme = recaptcha_config.data("theme");
if (theme === 'auto') {
theme = getColorScheme();
}
grecaptcha.render(`#${instance.attr("id")}`, {
sitekey: recaptcha_config.data("sitekey"),
size: recaptcha_config.data("size"),
theme: theme
});
});
}