CAPTCHA für öffentlichen Modus (Rechenaufgabe oder hCaptcha)

- includes/Captcha.php (math self-contained / hcaptcha via siteverify)
- index.php: Verifikation bei anonymer Erstellung + Anzeige in beiden Formularen
- Settings in admin/integrations.php (Modus, Site-Key, Secret)
This commit is contained in:
Claude 2026-06-06 05:42:06 +00:00
parent 546accc185
commit ce75215498
No known key found for this signature in database
3 changed files with 95 additions and 0 deletions

56
includes/Captcha.php Normal file
View file

@ -0,0 +1,56 @@
<?php
/**
* Captcha Schutz der öffentlichen (anonymen) Voucher-Erstellung.
*
* Modi (Setting captcha_mode):
* 'off' deaktiviert
* 'math' selbst-enthaltenes Rechen-Captcha (keine externen Dienste)
* 'hcaptcha' hCaptcha (Setting captcha_site_key / captcha_secret)
*
* Reine PHP-Standardlib + cURL (für hCaptcha-Verifizierung).
*/
class Captcha {
public static function mode($db) {
$m = (string)$db->getSetting('captcha_mode', 'off');
return in_array($m, ['off', 'math', 'hcaptcha'], true) ? $m : 'off';
}
/** Frage für das Math-Captcha erzeugen und Antwort in Session hinterlegen. */
public static function newMathChallenge() {
$a = random_int(1, 9);
$b = random_int(1, 9);
$_SESSION['captcha_answer'] = (string)($a + $b);
return "$a + $b";
}
/** Prüft die Captcha-Antwort des aktuellen Requests. */
public static function verify($db) {
$mode = self::mode($db);
if ($mode === 'off') {
return true;
}
if ($mode === 'math') {
$expected = $_SESSION['captcha_answer'] ?? null;
unset($_SESSION['captcha_answer']); // einmalig
$given = trim((string)($_POST['captcha'] ?? ''));
return $expected !== null && hash_equals((string)$expected, $given);
}
if ($mode === 'hcaptcha') {
$resp = $_POST['h-captcha-response'] ?? '';
if ($resp === '') return false;
$secret = (string)$db->getSetting('captcha_secret', '');
$ch = curl_init('https://hcaptcha.com/siteverify');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query(['secret' => $secret, 'response' => $resp]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 8,
]);
$out = curl_exec($ch);
curl_close($ch);
$data = json_decode((string)$out, true);
return is_array($data) && !empty($data['success']);
}
return true;
}
}