hCaptcha Integration

This commit is contained in:
Roman 2024-04-23 14:05:29 +02:00
parent aea20b7a10
commit 51ee723dcb
22 changed files with 275 additions and 145 deletions

@ -16,7 +16,7 @@ class Info extends Request {
$settings = $this->context->getSettings();
$this->result["info"] = [
"registrationAllowed" => $settings->isRegistrationAllowed(),
"recaptchaEnabled" => $settings->isRecaptchaEnabled(),
"captchaEnabled" => $settings->isCaptchaEnabled(),
"version" => WEBBASE_VERSION,
"siteName" => $settings->getSiteName(),
];

@ -3,6 +3,8 @@
namespace Core\API {
use Core\API\Parameter\IntegerType;
use Core\API\Parameter\StringType;
use Core\Objects\Captcha\CaptchaProvider;
use Core\Objects\Context;
use Core\API\Parameter\ArrayType;
use Core\API\Parameter\Parameter;
@ -20,7 +22,7 @@ namespace Core\API {
"allowed_extensions" => new ArrayType("allowed_extensions", Parameter::TYPE_STRING),
"trusted_domains" => new ArrayType("trusted_domains", Parameter::TYPE_STRING),
"user_registration_enabled" => new Parameter("user_registration_enabled", Parameter::TYPE_BOOLEAN),
"recaptcha_enabled" => new Parameter("recaptcha_enabled", Parameter::TYPE_BOOLEAN),
"captcha_provider" => new StringType("captcha_provider", -1, true, "none", CaptchaProvider::PROVIDERS),
"mail_enabled" => new Parameter("mail_enabled", Parameter::TYPE_BOOLEAN),
"mail_port" => new IntegerType("mail_port", 1, 65535)
];

@ -10,28 +10,12 @@ use Core\Objects\Context;
class Stats extends Request {
private bool $mailConfigured;
private bool $recaptchaConfigured;
public function __construct(Context $context, $externalCall = false) {
parent::__construct($context, $externalCall, array());
}
private function checkSettings(): bool {
$req = new \Core\API\Settings\Get($this->context);
$this->success = $req->execute(array("key" => "^(mail_enabled|recaptcha_enabled)$"));
$this->lastError = $req->getLastError();
if ($this->success) {
$settings = $req->getResult()["settings"];
$this->mailConfigured = $settings["mail_enabled"];
$this->recaptchaConfigured = $settings["recaptcha_enabled"];
}
return $this->success;
}
public function _execute(): bool {
$settings = $this->context->getSettings();
$sql = $this->context->getSQL();
$userCount = User::count($sql);
$pageCount = Route::count($sql, new CondBool("active"));
@ -54,10 +38,6 @@ class Stats extends Request {
$loadAvg = sys_getloadavg();
}
if (!$this->checkSettings()) {
return false;
}
$this->result["data"] = [
"userCount" => $userCount,
"pageCount" => $pageCount,
@ -69,8 +49,8 @@ class Stats extends Request {
"memory_usage" => memory_get_usage(),
"load_avg" => $loadAvg,
"database" => $this->context->getSQL()->getStatus(),
"mail" => $this->mailConfigured,
"reCaptcha" => $this->recaptchaConfigured
"mail" => $settings->isMailEnabled(),
"captcha" => $settings->getCaptchaProvider()?->jsonSerialize()
],
];

@ -727,7 +727,7 @@ namespace Core\API\User {
);
$settings = $context->getSettings();
if ($settings->isRecaptchaEnabled()) {
if ($settings->isCaptchaEnabled()) {
$parameters["captcha"] = new StringType("captcha");
}
@ -747,7 +747,7 @@ namespace Core\API\User {
return $this->createError("User Registration is not enabled.");
}
if ($settings->isRecaptchaEnabled()) {
if ($settings->isCaptchaEnabled()) {
$captcha = $this->getParam("captcha");
$req = new VerifyCaptcha($this->context);
if (!$req->execute(array("captcha" => $captcha, "action" => "register"))) {
@ -1003,7 +1003,7 @@ namespace Core\API\User {
);
$settings = $context->getSettings();
if ($settings->isRecaptchaEnabled()) {
if ($settings->isCaptchaEnabled()) {
$parameters["captcha"] = new StringType("captcha");
}
@ -1021,7 +1021,7 @@ namespace Core\API\User {
return $this->createError("The mail service is not enabled, please contact the server administration.");
}
if ($settings->isRecaptchaEnabled()) {
if ($settings->isCaptchaEnabled()) {
$captcha = $this->getParam("captcha");
$req = new VerifyCaptcha($this->context);
if (!$req->execute(array("captcha" => $captcha, "action" => "resetPassword"))) {
@ -1096,7 +1096,7 @@ namespace Core\API\User {
);
$settings = $context->getSettings();
if ($settings->isRecaptchaEnabled()) {
if ($settings->isCaptchaEnabled()) {
$parameters["captcha"] = new StringType("captcha");
}
@ -1110,7 +1110,7 @@ namespace Core\API\User {
}
$settings = $this->context->getSettings();
if ($settings->isRecaptchaEnabled()) {
if ($settings->isCaptchaEnabled()) {
$captcha = $this->getParam("captcha");
$req = new VerifyCaptcha($this->context);
if (!$req->execute(array("captcha" => $captcha, "action" => "resendConfirmation"))) {

@ -18,45 +18,15 @@ class VerifyCaptcha extends Request {
public function _execute(): bool {
$settings = $this->context->getSettings();
if (!$settings->isRecaptchaEnabled()) {
return $this->createError("Google reCaptcha is not enabled.");
$captchaProvider = $settings->getCaptchaProvider();
if ($captchaProvider === null) {
return $this->createError("No Captcha configured.");
}
$url = "https://www.google.com/recaptcha/api/siteverify";
$secret = $settings->getRecaptchaSecretKey();
$captcha = $this->getParam("captcha");
$action = $this->getParam("action");
$params = array(
"secret" => $secret,
"response" => $captcha
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = @json_decode(curl_exec($ch), true);
curl_close($ch);
$this->success = false;
$this->lastError = "Could not verify captcha: No response from google received.";
if ($response) {
$this->success = $response["success"];
if (!$this->success) {
$this->lastError = "Could not verify captcha: " . implode(";", $response["error-codes"]);
} else {
$score = $response["score"];
if ($action !== $response["action"]) {
$this->createError("Could not verify captcha: Action does not match");
} else if ($score < 0.7) {
$this->createError("Could not verify captcha: Google ReCaptcha Score < 0.7 (Your score: $score), you are likely a bot");
}
}
}
$this->success = $captchaProvider->verify($captcha, $action);
$this->lastError = $captchaProvider->getError();
return $this->success;
}

@ -12,6 +12,9 @@ use Core\Driver\SQL\Condition\CondNot;
use Core\Driver\SQL\Condition\CondRegex;
use Core\Driver\SQL\Query\Insert;
use Core\Driver\SQL\SQL;
use Core\Objects\Captcha\CaptchaProvider;
use Core\Objects\Captcha\GoogleRecaptchaProvider;
use Core\Objects\Captcha\HCaptchaProvider;
use Core\Objects\Context;
class Settings {
@ -27,10 +30,10 @@ class Settings {
private array $allowedExtensions;
private string $timeZone;
// recaptcha
private bool $recaptchaEnabled;
private string $recaptchaPublicKey;
private string $recaptchaPrivateKey;
// captcha
private string $captchaProvider;
private string $captchaSiteKey;
private string $captchaSecretKey;
// mail
private bool $mailEnabled;
@ -98,10 +101,10 @@ class Settings {
$settings->registrationAllowed = false;
$settings->timeZone = date_default_timezone_get();
// Recaptcha
$settings->recaptchaEnabled = false;
$settings->recaptchaPublicKey = "";
$settings->recaptchaPrivateKey = "";
// captcha
$settings->captchaProvider = "none";
$settings->captchaSiteKey = "";
$settings->captchaSecretKey = "";
// Mail
$settings->mailEnabled = false;
@ -124,9 +127,9 @@ class Settings {
$this->registrationAllowed = $result["user_registration_enabled"] ?? $this->registrationAllowed;
$this->installationComplete = $result["installation_completed"] ?? $this->installationComplete;
$this->timeZone = $result["time_zone"] ?? $this->timeZone;
$this->recaptchaEnabled = $result["recaptcha_enabled"] ?? $this->recaptchaEnabled;
$this->recaptchaPublicKey = $result["recaptcha_public_key"] ?? $this->recaptchaPublicKey;
$this->recaptchaPrivateKey = $result["recaptcha_private_key"] ?? $this->recaptchaPrivateKey;
$this->captchaProvider = $result["captcha_provider"] ?? $this->captchaProvider;
$this->captchaSiteKey = $result["captcha_site_key"] ?? $this->captchaSiteKey;
$this->captchaSecretKey = $result["captcha_secret_key"] ?? $this->captchaSecretKey;
$this->mailEnabled = $result["mail_enabled"] ?? $this->mailEnabled;
$this->mailSender = $result["mail_from"] ?? $this->mailSender;
$this->mailFooter = $result["mail_footer"] ?? $this->mailFooter;
@ -146,9 +149,9 @@ class Settings {
->addRow("user_registration_enabled", json_encode($this->registrationAllowed), false, false)
->addRow("installation_completed", json_encode($this->installationComplete), true, true)
->addRow("time_zone", json_encode($this->timeZone), false, false)
->addRow("recaptcha_enabled", json_encode($this->recaptchaEnabled), false, false)
->addRow("recaptcha_public_key", json_encode($this->recaptchaPublicKey), false, false)
->addRow("recaptcha_private_key", json_encode($this->recaptchaPrivateKey), true, false)
->addRow("captcha_provider", json_encode($this->captchaProvider), false, false)
->addRow("captcha_site_key", json_encode($this->captchaSiteKey), false, false)
->addRow("captcha_secret_key", json_encode($this->captchaSecretKey), true, false)
->addRow("allowed_extensions", json_encode($this->allowedExtensions), false, false)
->addRow("mail_host", '""', false, false)
->addRow("mail_port", '587', false, false)
@ -176,16 +179,16 @@ class Settings {
return $this->baseUrl;
}
public function isRecaptchaEnabled(): bool {
return $this->recaptchaEnabled;
public function isCaptchaEnabled(): bool {
return CaptchaProvider::isValid($this->captchaProvider);
}
public function getRecaptchaSiteKey(): string {
return $this->recaptchaPublicKey;
}
public function getRecaptchaSecretKey(): string {
return $this->recaptchaPrivateKey;
public function getCaptchaProvider(): ?CaptchaProvider {
return match ($this->captchaProvider) {
CaptchaProvider::RECAPTCHA => new GoogleRecaptchaProvider($this->captchaSiteKey, $this->captchaSecretKey),
CaptchaProvider::HCAPTCHA => new HCaptchaProvider($this->captchaSiteKey, $this->captchaSecretKey),
default => null,
};
}
public function isRegistrationAllowed(): bool {

@ -5,6 +5,8 @@ namespace Core\Elements;
use Core\Configuration\Settings;
use Core\Driver\Logger\Logger;
use Core\Driver\SQL\SQL;
use Core\Objects\Captcha\GoogleRecaptchaProvider;
use Core\Objects\Captcha\HCaptchaProvider;
use Core\Objects\Context;
use Core\Objects\Router\DocumentRoute;
use Core\Objects\Router\Router;
@ -78,7 +80,7 @@ abstract class Document {
return $this->router;
}
public function addCSPWhitelist(string $path) {
public function addCSPWhitelist(string $path): void {
$urlParts = parse_url($path);
if (!$urlParts || !isset($urlParts["host"])) {
$this->cspWhitelist[] = getProtocol() . "://" . getCurrentHostName() . $path;
@ -89,7 +91,23 @@ abstract class Document {
public function sendHeaders(): void {
if ($this->cspEnabled) {
$frameSrc = [];
$captchaProvider = $this->getSettings()->getCaptchaProvider();
if ($captchaProvider instanceof GoogleRecaptchaProvider) {
$frameSrc[] = "https://www.google.com/recaptcha/";
$frameSrc[] = "https://recaptcha.google.com/recaptcha/";
$this->cspWhitelist[] = "https://www.google.com/recaptcha/";
$this->cspWhitelist[] = "https://www.gstatic.com/recaptcha/";
} else if ($captchaProvider instanceof HCaptchaProvider) {
$frameSrc[] = "https://hcaptcha.com";
$frameSrc[] = "https://*.hcaptcha.com";
$this->cspWhitelist[] = "https://hcaptcha.com";
$this->cspWhitelist[] = "https://*.hcaptcha.com";
}
$cspWhiteList = implode(" ", $this->cspWhitelist);
$frameSrc = implode(" ", $frameSrc);
$csp = [
"default-src $cspWhiteList 'self'",
"object-src 'none'",
@ -98,10 +116,8 @@ abstract class Document {
"img-src 'self' 'unsafe-inline' data: https:;",
"script-src $cspWhiteList 'nonce-$this->cspNonce'",
"frame-ancestors 'self'",
"frame-src $frameSrc 'self'",
];
if ($this->getSettings()->isRecaptchaEnabled()) {
$csp[] = "frame-src https://www.google.com/ 'self'";
}
$compiledCSP = implode("; ", $csp);
header("Content-Security-Policy: $compiledCSP;");

@ -28,7 +28,7 @@ abstract class Head extends View {
protected abstract function initRawFields(): array;
protected abstract function initTitle(): string;
protected function init() {
protected function init(): void {
$this->keywords = array();
$this->description = "";
$this->baseUrl = "";
@ -51,19 +51,15 @@ abstract class Head extends View {
public function addJS($url) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, $url, ""); }
public function addJSCode($code) { $this->sources[] = new Script(Script::MIME_TEXT_JAVASCRIPT, "", $code); }
public function loadFontawesome() {
public function loadFontawesome(): void {
$this->addCSS(Link::FONTAWESOME);
}
public function loadGoogleRecaptcha($siteKey) {
$this->addJS("https://www.google.com/recaptcha/api.js?render=$siteKey");
}
public function loadJQuery() {
public function loadJQuery(): void {
$this->addJS(Script::JQUERY);
}
public function loadBootstrap() {
public function loadBootstrap(): void {
$this->addCSS(Link::BOOTSTRAP);
$this->addJS(Script::BOOTSTRAP);
}

@ -60,7 +60,6 @@ class HtmlDocument extends Document {
return $code;
}
public function getTitle(): string {
if ($this->head !== null) {
return $this->head->getTitle();

@ -80,6 +80,7 @@ class TemplateDocument extends Document {
$session = $context->getSession();
$settings = $this->getSettings();
$language = $context->getLanguage();
$captchaProvider = $settings->getCaptchaProvider();
$urlParts = parse_url($this->getRouter()->getRequestedUri());
@ -102,9 +103,10 @@ class TemplateDocument extends Document {
"lastModified" => date(L('Y-m-d H:i:s'), @filemtime(self::getTemplatePath($name))),
"registrationEnabled" => $settings->isRegistrationAllowed(),
"title" => $this->title,
"recaptcha" => [
"key" => $settings->isRecaptchaEnabled() ? $settings->getRecaptchaSiteKey() : null,
"enabled" => $settings->isRecaptchaEnabled(),
"captcha" => [
"provider" => $captchaProvider?->getName(),
"site_key" => $captchaProvider?->getSiteKey(),
"enabled" => $captchaProvider !== null,
],
"csp" => [
"nonce" => $this->getCSPNonce(),

@ -17,7 +17,7 @@ return [
"value" => "Wert",
"general" => "Allgemein",
"mail" => "Mail",
"recaptcha" => "reCaptcha",
"captcha" => "Captcha",
"uncategorized" => "Unkategorisiert",
# general settings
@ -40,10 +40,10 @@ return [
"mail_address" => "E-Mail Adresse",
"send_test_email" => "Test E-Mail senden",
# recaptcha
"recaptcha_enabled" => "Aktiviere Google reCaptcha",
"recaptcha_public_key" => "reCaptcha öffentlicher Schlüssel",
"recaptcha_private_key" => "reCaptcha privater Schlüssel",
# captcha
"captcha_provider" => "Captcha Anbieter",
"captcha_site_key" => "Öffentlicher Captcha Schlüssel",
"captcha_secret_key" => "Geheimer Captcha Schlüssel",
# dialog
"fetch_settings_error" => "Fehler beim Holen der Einstellungen",

@ -17,7 +17,7 @@ return [
"value" => "Value",
"general" => "General",
"mail" => "Mail",
"recaptcha" => "reCaptcha",
"captcha" => "Captcha",
"uncategorized" => "Uncategorized",
# general settings
@ -40,10 +40,10 @@ return [
"mail_address" => "Mail address",
"send_test_email" => "Send test e-mail",
# recaptcha
"recaptcha_enabled" => "Enable Google reCaptcha",
"recaptcha_public_key" => "reCaptcha Public Key",
"recaptcha_private_key" => "reCaptcha Private Key",
# captcha
"captcha_provider" => "Captcha Provider",
"captcha_site_key" => "Captcha Site Key",
"captcha_secret_key" => "Secret Captcha Key",
# dialog
"fetch_settings_error" => "Error fetching settings",

@ -0,0 +1,64 @@
<?php
namespace Core\Objects\Captcha;
use Core\Objects\ApiObject;
abstract class CaptchaProvider extends ApiObject {
const NONE = "none";
const RECAPTCHA = "recaptcha";
const HCAPTCHA = "hcaptcha";
const PROVIDERS = [self::NONE, self::RECAPTCHA, self::HCAPTCHA];
private string $siteKey;
private string $secretKey;
protected string $error;
public function __construct(string $siteKey, string $secretKey) {
$this->siteKey = $siteKey;
$this->secretKey = $secretKey;
$this->error = "";
}
public function getSiteKey(): string {
return $this->siteKey;
}
public function getError(): string {
return $this->error;
}
public static function isValid(string $type): bool {
return in_array($type, [self::RECAPTCHA, self::HCAPTCHA]);
}
public abstract function verify(string $captcha, string $action): bool;
public abstract function getName(): string;
public function jsonSerialize(): array {
return [
"name" => $this->getName(),
"siteKey" => $this->getSiteKey(),
];
}
protected function performVerifyRequest(string $url, string $captcha) {
$params = [
"secret" => $this->secretKey,
"response" => $captcha
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = @json_decode(curl_exec($ch), true);
curl_close($ch);
return $response;
}
}

@ -0,0 +1,43 @@
<?php
namespace Core\Objects\Captcha;
class GoogleRecaptchaProvider extends CaptchaProvider {
public function __construct(string $siteKey, string $secretKey) {
parent::__construct($siteKey, $secretKey);
}
public function verify(string $captcha, string $action): bool {
$url = "https://www.google.com/recaptcha/api/siteverify";
$success = false;
$response = $this->performVerifyRequest($url, $captcha);
$this->error = "Could not verify captcha: Invalid response from Google received.";
if ($response) {
$success = $response["success"];
if (!$success) {
$this->error = "Could not verify captcha: " . implode(";", $response["error-codes"]);
} else {
$score = $response["score"];
if ($action !== $response["action"]) {
$success = false;
$this->error = "Could not verify captcha: Action does not match";
} else if ($score < 0.7) {
$success = false;
$this->error = "Could not verify captcha: Google ReCaptcha Score < 0.7 (Your score: $score), you are likely a bot";
} else {
$success = true;
$this->error = "";
}
}
}
return $success;
}
public function getName(): string {
return CaptchaProvider::RECAPTCHA;
}
}

@ -0,0 +1,30 @@
<?php
namespace Core\Objects\Captcha;
class HCaptchaProvider extends CaptchaProvider {
public function __construct(string $siteKey, string $secretKey) {
parent::__construct($siteKey, $secretKey);
}
public function verify(string $captcha, string $action): bool {
$success = true;
$url = "https://api.hcaptcha.com/siteverify";
$response = $this->performVerifyRequest($url, $captcha);
$this->error = "Could not verify captcha: Invalid response from hCaptcha received.";
if ($response) {
$success = $response["success"];
if (!$success) {
$this->error = "Captcha verification failed.";
}
}
return $success;
}
public function getName(): string {
return CaptchaProvider::HCAPTCHA;
}
}

@ -9,8 +9,15 @@
<link rel="stylesheet" href="/css/fontawesome.min.css" nonce="{{ site.csp.nonce }}">
<link rel="stylesheet" href="/css/account.css" nonce="{{ site.csp.nonce }}">
<title>{{ L("account.title")}} - {{ L(view_title) }}</title>
{% if site.recaptcha.enabled %}
<script src="https://www.google.com/recaptcha/api.js?render={{ site.recaptcha.key }}" nonce="{{ site.csp.nonce }}"></script>
{% if site.captcha.enabled %}
<script nonce="{{ site.csp.nonce }}">
window.captchaProvider = {{ site.captcha|json_encode()|raw }};
</script>
{% if site.captcha.provider == 'recaptcha' %}
<script src="https://www.google.com/recaptcha/api.js?render={{ site.captcha.site_key }}" nonce="{{ site.csp.nonce }}"></script>
{% elseif site.captcha.provider == 'hcaptcha' %}
<script src="https://js.hcaptcha.com/1/api.js" nonce="{{ site.csp.nonce }}"></script>
{% endif %}
{% endif %}
{% endblock %}
@ -30,7 +37,4 @@
</div>
</div>
</div>
{% if site.recaptcha.enabled %}
<input type='hidden' value='{{ site.recaptcha.key }}' id='siteKey' />
{% endif %}
{% endblock %}

@ -40,6 +40,9 @@
<input type="password" autocomplete='new-password' name='confirmPassword'
id='confirmPassword' class="form-control" placeholder="{{ L('account.password_confirm') }}">
</div>
{% if site.captcha.enabled and site.captcha.provider == 'hcaptcha' %}
<div class="h-captcha mt-2" data-sitekey="{{ site.captcha.site_key }}"></div>
{% endif %}
<div class="input-group mt-3">
<button type="button" class="btn btn-primary" id='btnRegister'>
{{ L('general.submit') }}

@ -53,6 +53,9 @@
class="form-control" type="email" maxlength="64" />
</div>
</div>
{% if site.captcha.enabled and site.captcha.provider == 'hcaptcha' %}
<div class="h-captcha mt-2" data-sitekey="{{ site.captcha.site_key }}"></div>
{% endif %}
<div class="input-group mt-2">
<button id='btnRequestPasswordReset' class='btn btn-primary'>
{{ L('general.submit') }}

@ -15,7 +15,8 @@ Web-Base is a php framework which provides basic web functionalities and a moder
- [Localization](#localization)
- [Command Line Interface (CLI)](#cli)
- [Account & User functions](#access-control)
- Admin Dashboard
- [Google reCaptcha](https://developers.google.com/recaptcha/) and [hCaptcha](https://docs.hcaptcha.com/) Integration
- modern ReactJS Admin Dashboard
- Docker Support
### Upcoming:

@ -14,7 +14,6 @@ $(document).ready(function () {
function hideAlert() {
$("#alertMessage").hide();
}
function submitForm(btn, method, params, onSuccess) {
let textBefore = btn.text();
btn.prop("disabled", true);
@ -27,6 +26,12 @@ $(document).ready(function () {
} else {
onSuccess();
}
// reset captcha
let captchaProvider = jsCore.getCaptchaProvider();
if (captchaProvider?.provider === "hcaptcha") {
hcaptcha.reset();
}
});
}
@ -75,11 +80,11 @@ $(document).ready(function () {
} else if(password !== confirmPassword) {
showAlert("danger", L("register.passwords_do_not_match"));
} else {
let captchaProvider = jsCore.getCaptchaProvider();
let params = { username: username, email: email, password: password, confirmPassword: confirmPassword };
if (jsCore.isRecaptchaEnabled()) {
let siteKey = $("#siteKey").val().trim();
if (captchaProvider?.provider === "recaptcha") {
grecaptcha.ready(function() {
grecaptcha.execute(siteKey, {action: 'register'}).then(function(captcha) {
grecaptcha.execute(captchaProvider.site_key, {action: 'register'}).then(function(captcha) {
params["captcha"] = captcha;
submitForm(btn, "user/register", params, () => {
showAlert("success", "Account successfully created, check your emails.");
@ -88,6 +93,10 @@ $(document).ready(function () {
});
});
} else {
if (captchaProvider?.provider === "hcaptcha") {
params.captcha = hcaptcha.getResponse();
}
submitForm(btn, "user/register", params, () => {
showAlert("success", "Account successfully created, check your emails.");
$("input:not([id='siteKey'])").val("");
@ -132,12 +141,11 @@ $(document).ready(function () {
let btn = $(this);
let email = $("#email").val();
let captchaProvider = jsCore.getCaptchaProvider();
let params = { email: email };
if (jsCore.isRecaptchaEnabled()) {
let siteKey = $("#siteKey").val().trim();
if (captchaProvider?.provider === "recaptcha") {
grecaptcha.ready(function() {
grecaptcha.execute(siteKey, {action: 'resetPassword'}).then(function(captcha) {
grecaptcha.execute(captchaProvider.site_key, {action: 'resetPassword'}).then(function(captcha) {
params["captcha"] = captcha;
submitForm(btn, "user/requestPasswordReset", params, () => {
showAlert("success", "If the e-mail address exists and is linked to a account, you will receive a password reset token.");
@ -146,6 +154,9 @@ $(document).ready(function () {
});
});
} else {
if (captchaProvider?.provider === "hcaptcha") {
params.captcha = hcaptcha.getResponse();
}
submitForm(btn, "user/requestPasswordReset", params, () => {
showAlert("success", "If the e-mail address exists and is linked to a account, you will receive a password reset token.");
$("input:not([id='siteKey'])").val("");
@ -191,11 +202,11 @@ $(document).ready(function () {
let btn = $(this);
let email = $("#email").val();
let captchaProvider = jsCore.getCaptchaProvider();
let params = { email: email };
if (jsCore.isRecaptchaEnabled()) {
let siteKey = $("#siteKey").val().trim();
if (captchaProvider?.provider === "recaptcha") {
grecaptcha.ready(function() {
grecaptcha.execute(siteKey, {action: 'resendConfirmation'}).then(function(captcha) {
grecaptcha.execute(captchaProvider.site_key, {action: 'resendConfirmation'}).then(function(captcha) {
params["captcha"] = captcha;
submitForm(btn, "user/resendConfirmEmail", params, () => {
showAlert("success", "If the e-mail address exists and is linked to a account, you will receive a new confirmation email.");
@ -204,6 +215,10 @@ $(document).ready(function () {
});
});
} else {
if (captchaProvider?.provider === "hcaptcha") {
params.captcha = hcaptcha.getResponse();
}
submitForm(btn, "user/resendConfirmEmail", params, () => {
showAlert("success", "\"If the e-mail address exists and is linked to a account, you will receive a new confirmation email.");
$("input:not([id='siteKey'])").val("");

@ -162,8 +162,8 @@ let Core = function () {
return this.getJsonDateTime(date).split(' ')[1];
};
this.isRecaptchaEnabled = function () {
return (typeof grecaptcha !== 'undefined');
this.getCaptchaProvider = function () {
return window.captchaProvider || null;
}
this.__construct();

@ -18,13 +18,12 @@ import {Link} from "react-router-dom";
import {
Add,
Delete,
Google,
LibraryBooks,
Mail,
RestartAlt,
Save,
Send,
SettingsApplications
SettingsApplications, SmartToy
} from "@mui/icons-material";
import TIME_ZONES from "shared/time-zones";
import ButtonBar from "../../elements/button-bar";
@ -63,10 +62,10 @@ export default function SettingsView(props) {
"mail_password",
"mail_async",
],
"recaptcha": [
"recaptcha_enabled",
"recaptcha_private_key",
"recaptcha_public_key",
"captcha": [
"captcha_provider",
"captcha_secret_key",
"captcha_site_key",
],
"hidden": ["installation_completed", "mail_last_sync"]
};
@ -280,11 +279,11 @@ export default function SettingsView(props) {
</FormControl>
</FormGroup>
];
} else if (selectedTab === "recaptcha") {
} else if (selectedTab === "captcha") {
return [
renderCheckBox("recaptcha_enabled"),
renderTextInput("recaptcha_public_key", !parseBool(settings.recaptcha_enabled)),
renderPasswordInput("recaptcha_private_key", !parseBool(settings.recaptcha_enabled)),
renderSelection("captcha_provider", ["none", "recaptcha", "hcaptcha"]),
renderTextInput("captcha_site_key", settings.captcha_provider === "none"),
renderPasswordInput("captcha_secret_key", settings.captcha_provider === "none"),
];
} else if (selectedTab === "uncategorized") {
return <TableContainer component={Paper}>
@ -364,8 +363,8 @@ export default function SettingsView(props) {
icon={<SettingsApplications />} iconPosition={"start"} />
<Tab value={"mail"} label={L("settings.mail")}
icon={<Mail />} iconPosition={"start"} />
<Tab value={"recaptcha"} label={L("settings.recaptcha")}
icon={<Google />} iconPosition={"start"} />
<Tab value={"captcha"} label={L("settings.captcha")}
icon={<SmartToy />} iconPosition={"start"} />
<Tab value={"uncategorized"} label={L("settings.uncategorized")}
icon={<LibraryBooks />} iconPosition={"start"} />
</Tabs>