diff --git a/inc/Service/SecureimageCaptchaQuery.php b/inc/Service/SecureimageCaptchaQuery.php index f5023899..0b33c059 100644 --- a/inc/Service/SecureimageCaptchaQuery.php +++ b/inc/Service/SecureimageCaptchaQuery.php @@ -10,19 +10,16 @@ class SecureImageCaptchaQuery { private HttpDriver $http; private string $domain; private string $provider_check; - private string $extra; /** * @param HttpDriver $http The http client. * @param string $domain The server's domain. * @param string $provider_check Path to the endpoint. - * @param string $extra Extra http parameters. */ - function __construct(HttpDriver $http, string $domain, string $provider_check, string $extra) { + function __construct(HttpDriver $http, string $domain, string $provider_check) { $this->http = $http; $this->domain = $domain; $this->provider_check = $provider_check; - $this->extra = $extra; } /** @@ -37,11 +34,12 @@ class SecureImageCaptchaQuery { $data = [ 'mode' => 'check', 'text' => $user_text, - 'extra' => $this->extra, 'cookie' => $user_cookie ]; $ret = $this->http->requestGet($this->domain . '/' . $this->provider_check, $data); - return $ret === '1'; + $resp = \json_decode($ret, true, 16, \JSON_THROW_ON_ERROR); + + return isset($resp['success']) && $resp['success']; } } diff --git a/inc/config.php b/inc/config.php index f177f9e1..5f9b7130 100644 --- a/inc/config.php +++ b/inc/config.php @@ -384,13 +384,18 @@ // To enable the native captcha you need to change a couple of settings. Read more at: /inc/captcha/readme.md 'native' => [ // Custom captcha get provider path (if not working get the absolute path aka your url). - 'provider_get' => '../inc/captcha/entrypoint.php', + 'provider_get' => 'securimage.php', // Custom captcha check provider path - 'provider_check' => '../inc/captcha/entrypoint.php', + 'provider_check' => 'securimage.php', // Custom captcha extra field (eg. charset) 'extra' => 'abcdefghijklmnopqrstuvwxyz', // New thread captcha. Require solving a captcha to post a thread. - 'new_thread_capt' => false + 'new_thread_capt' => false, + // Securimage customization options + // https://github.com/dapphp/securimage/blob/nextgen/examples/securimage_show_example.php#L49 + 'securimage_options' => ['send_headers' => false, 'no_exit' => true], + // Captcha expires (in seconds) + 'expires_in' => 320 ] ]; diff --git a/inc/context.php b/inc/context.php index d1f74551..63557227 100644 --- a/inc/context.php +++ b/inc/context.php @@ -86,8 +86,7 @@ function build_context(array $config): Context { return new SecureImageCaptchaQuery( $c->get(HttpDriver::class), $config['domain'], - $config['captcha']['native']['provider_check'], - $config['captcha']['native']['extra'] + $config['captcha']['native']['provider_check'] ); }, CacheDriver::class => function($c) { diff --git a/js/captcha.js b/js/captcha.js index 5e0bcf53..b0589d24 100644 --- a/js/captcha.js +++ b/js/captcha.js @@ -1,27 +1,27 @@ var tout; -function redo_events(provider, extra) { - $('.captcha .captcha_text, textarea[id="body"]').off("focus").one("focus", function() { actually_load_captcha(provider, extra); }); +function redo_events(provider) { + $('.captcha .captcha_text, textarea[id="body"]').off("focus").one("focus", function() { actually_load_captcha(provider); }); } -function actually_load_captcha(provider, extra) { +function actually_load_captcha(provider) { $('.captcha .captcha_text, textarea[id="body"]').off("focus"); if (tout !== undefined) { clearTimeout(tout); } - $.getJSON(provider, {mode: 'get', extra: extra}, function(json) { + $.getJSON(provider, {mode: 'get'}, function(json) { $(".captcha .captcha_cookie").val(json.cookie); $(".captcha .captcha_html").html(json.captchahtml); setTimeout(function() { - redo_events(provider, extra); + redo_events(provider); }, json.expires_in * 1000); }); } -function load_captcha(provider, extra) { +function load_captcha(provider) { $(function() { $(".captcha>td").html(""+ ""+ @@ -29,15 +29,15 @@ function load_captcha(provider, extra) { $("#quick-reply .captcha .captcha_text").prop("placeholder", _("Verification")); - $(".captcha .captcha_html").on("click", function() { actually_load_captcha(provider, extra); }); - $(document).on("ajax_after_post", function() { actually_load_captcha(provider, extra); }); - redo_events(provider, extra); + $(".captcha .captcha_html").on("click", function() { actually_load_captcha(provider); }); + $(document).on("ajax_after_post", function() { actually_load_captcha(provider); }); + redo_events(provider); $(window).on("quick-reply", function() { - redo_events(provider, extra); + redo_events(provider); $("#quick-reply .captcha .captcha_html").html($("form:not(#quick-reply) .captcha .captcha_html").html()); $("#quick-reply .captcha .captcha_cookie").val($("form:not(#quick-reply) .captcha .captcha_cookie").html()); - $("#quick-reply .captcha .captcha_html").on("click", function() { actually_load_captcha(provider, extra); }); + $("#quick-reply .captcha .captcha_html").on("click", function() { actually_load_captcha(provider); }); }); }); } diff --git a/post.php b/post.php index 2dcfe294..17ee95c5 100644 --- a/post.php +++ b/post.php @@ -517,12 +517,7 @@ if (isset($_POST['delete'])) { } try { - $query = new SecureImageCaptchaQuery( - $context->get(HttpDriver::class), - $config['domain'], - $config['captcha']['provider_check'], - $config['captcha']['extra'] - ); + $query = $context->get(SecureImageCaptchaQuery::class); $success = $query->verify( $_POST['captcha_text'], $_POST['captcha_cookie'] @@ -632,22 +627,22 @@ if (isset($_POST['delete'])) { $dynamic = $config['captcha']['dynamic']; // With our custom captcha provider - if (($provider === 'native' && !$new_thread_capt) - || ($provider === 'native' && $new_thread_capt && $post['op'])) { - $query = $context->get(SecureImageCaptchaQuery::class); - $success = $query->verify($_POST['captcha_text'], $_POST['captcha_cookie']); + if ($provider === 'native') { + if ((!$new_thread_capt && !$post['op']) || ($new_thread_capt && $post['op'])) { + $query = $context->get(SecureImageCaptchaQuery::class); + $success = $query->verify($_POST['captcha_text'], $_POST['captcha_cookie']); - if (!$success) { - error( - "{$config['error']['captcha']} - " - ); + if (!$success) { + error( + "{$config['error']['captcha']} + " + ); + } } } // Remote 3rd party captchas. diff --git a/securimage.php b/securimage.php index 324ffe44..8ebfa154 100644 --- a/securimage.php +++ b/securimage.php @@ -1,73 +1,88 @@ execute([time() - $expires_in]); } +function handleGetRequestCaptcha(array $config): void { + $extra = $config['captcha']['native']['extra']; + $cookie = rand_string(20, $extra); -$mode = @$_GET['mode']; -switch ($mode) { + $securimage = new Securimage($config['captcha']['native']['securimage_options']); + $securimage->createCode(); + + ob_start(); + $securimage->show(); + $rawImage = ob_get_clean(); + + $base64Image = 'data:image/png;base64,' . base64_encode($rawImage); + $html = ''; + $captchaCode = $securimage->getCode(); + + prepare("INSERT INTO `captchas` (`cookie`, `extra`, `text`, `created_at`) VALUES (?, ?, ?, ?)") + ->execute([$cookie, $extra, $captchaCode->code_display, $captchaCode->creationTime]); + + if (isset($_GET['raw'])) { + $_SESSION['captcha_cookie'] = $cookie; + header('Content-Type: image/png'); + echo $rawImage; + } else { + header("Content-Type: application/json"); + echo json_encode([ + "cookie" => $cookie, + "captchahtml" => $html, + "expires_in" => $config['captcha']['native']['expires_in'], + ]); + } +} + +function handleCheckRequestCaptcha(int $expires_in): void { + cleanup($expires_in); + + $cookie = $_GET['cookie'] ?? null; + $text = $_GET['text'] ?? null; + + if (!$cookie || !$text) { + echo json_encode(["success" => false]); + return; + } + + $query = prepare("SELECT * FROM `captchas` WHERE `cookie` = ?"); + $query->execute([$cookie]); + $captchaData = $query->fetchAll(); + + if (!$captchaData) { + echo json_encode(["success" => false]); + return; + } + + prepare("DELETE FROM `captchas` WHERE `cookie` = ?")->execute([$cookie]); + + $isSuccessful = $captchaData[0]['text'] === $text; + echo json_encode(["success" => $isSuccessful]); +} + +$mode = $_GET['mode'] ?? null; + +switch($mode) { case 'get': - if (!isset ($_GET['extra'])) { - $_GET['extra'] = $config['captcha']['extra']; - } - - header("Content-type: application/json"); - $extra = $_GET['extra']; - $cookie = rand_string(20, "abcdefghijklmnopqrstuvwxyz"); - $i = new Securimage(['send_headers' => false, 'no_exit' => true]); - $i->createCode(); - ob_start(); - $i->show(); - $rawimg = ob_get_contents(); - $b64img = 'data:image/png;base64,'.base64_encode($rawimg); - $html = ''; - ob_end_clean(); - $cdata = $i->getCode(); - $query = prepare("INSERT INTO `captchas` (`cookie`, `extra`, `text`, `created_at`) VALUES (?, ?, ?, ?)"); - $query->execute([$cookie, $extra, $cdata->code_display, $cdata->creationTime]); - if (isset($_GET['raw'])) { - $_SESSION['captcha_cookie'] = $cookie; - header('Content-Type: image/png'); - echo $rawimg; - } else { - echo json_encode(["cookie" => $cookie, "captchahtml" => $html, "expires_in" => $expires_in]); - } + handleGetRequestCaptcha($config); break; case 'check': - cleanup(); - if (!isset ($_GET['mode']) || !isset ($_GET['cookie']) || !isset ($_GET['extra']) || !isset ($_GET['text'])) { - die(); - } - - $query = prepare("SELECT * FROM `captchas` WHERE `cookie` = ? AND `extra` = ?"); - $query->execute([$_GET['cookie'], $_GET['extra']]); - - $ary = $query->fetchAll(); - - if (!$ary) { // captcha expired - echo "0"; - break; - } else { - $query = prepare("DELETE FROM `captchas` WHERE `cookie` = ? AND `extra` = ?"); - $query->execute([$_GET['cookie'], $_GET['extra']]); - } - - if ($ary[0]['text'] !== $_GET['text']) { - echo "0"; - } else { - echo "1"; - } + handleCheckRequestCaptcha($config['captcha']['native']['expires_in']); break; -} + case '': + default: + http_response_code(400); + echo json_encode(["success" => false, "error" => "Invalid mode"]); + break; +} \ No newline at end of file diff --git a/templates/post_form.html b/templates/post_form.html index b0377e03..1095b4ae 100644 --- a/templates/post_form.html +++ b/templates/post_form.html @@ -100,38 +100,22 @@ {% endif %} - {% if config.captcha.provider == 'native' %} - - - {% trans %}Verification{% endtrans %} - - - - - - - {% elseif config.captcha.native.new_thread_capt %} - {% if not id %} - - - {% trans %}Verification{% endtrans %} - - - - - - - {% endif %} + {% if (config.captcha.provider == 'native' and not config.captcha.native.new_thread_capt) or + (config.captcha.provider == 'native' and config.captcha.native.new_thread_capt and not id) %} + + + {% trans %}Verification{% endtrans %} + + + + + + {% endif %} {% if config.user_flag %}