86 lines
2.5 KiB
PHP
86 lines
2.5 KiB
PHP
<?php
|
|
/*
|
|
* Google reCAPTCHA v2. The keys come from RECAPTCHA_SITE_KEY and
|
|
* RECAPTCHA_SECRET in .env.
|
|
*
|
|
* Without RECAPTCHA_SITE_KEY the captcha is off: the pages load nothing
|
|
* from Google and every check passes. With the site key but without the
|
|
* secret, or when Google cannot be reached, every check fails (and the
|
|
* reason is logged).
|
|
*/
|
|
require_once(__DIR__ . '/config.lib.php');
|
|
require_once(__DIR__ . '/html.lib.php');
|
|
|
|
define('CAPTCHA_PASS_SECONDS', 30 * 60);
|
|
|
|
function captcha_enabled() {
|
|
return (string)config('RECAPTCHA_SITE_KEY') !== '';
|
|
}
|
|
|
|
function captcha_script() {
|
|
if (!captcha_enabled())
|
|
return '';
|
|
return '<script src="https://www.google.com/recaptcha/api.js" async defer></script>';
|
|
}
|
|
|
|
function captcha_widget() {
|
|
if (!captcha_enabled())
|
|
return '';
|
|
return '<div class="g-recaptcha" data-sitekey="'.form_value(config('RECAPTCHA_SITE_KEY')).'"></div>';
|
|
}
|
|
|
|
// Ask Google whether the captcha in this POST was solved
|
|
function captcha_verify_response() {
|
|
if (!captcha_enabled())
|
|
return TRUE;
|
|
$response = $_POST['g-recaptcha-response'] ?? '';
|
|
if (!is_string($response) || $response === '')
|
|
return FALSE;
|
|
$secret = (string)config('RECAPTCHA_SECRET');
|
|
if ($secret === '') {
|
|
error_log('xxx: RECAPTCHA_SECRET is not set, so every captcha check fails');
|
|
return FALSE;
|
|
}
|
|
$context = stream_context_create(array('http' => array(
|
|
'method' => 'POST',
|
|
'header' => "Content-Type: application/x-www-form-urlencoded\r\n",
|
|
'content' => http_build_query(array(
|
|
'secret' => $secret,
|
|
'response' => $response,
|
|
'remoteip' => $_SERVER['REMOTE_ADDR'] ?? '',
|
|
)),
|
|
'timeout' => 10,
|
|
)));
|
|
$reply = @file_get_contents('https://www.google.com/recaptcha/api/siteverify', FALSE, $context);
|
|
if ($reply === FALSE) {
|
|
error_log('xxx: could not reach the reCAPTCHA server');
|
|
return FALSE;
|
|
}
|
|
$result = json_decode($reply);
|
|
return is_object($result) && !empty($result->success);
|
|
}
|
|
|
|
/*
|
|
* For story submissions: a solved captcha counts for CAPTCHA_PASS_SECONDS
|
|
* in this session, so the preview and the submit need only one captcha.
|
|
* captcha_used() ends the pass once a story is saved.
|
|
*/
|
|
function captcha_passed() {
|
|
return !empty($_SESSION['captcha_ok']) && $_SESSION['captcha_ok'] > time() - CAPTCHA_PASS_SECONDS;
|
|
}
|
|
|
|
function captcha_check() {
|
|
if (captcha_passed())
|
|
return TRUE;
|
|
if (captcha_verify_response()) {
|
|
$_SESSION['captcha_ok'] = time();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
function captcha_used() {
|
|
unset($_SESSION['captcha_ok']);
|
|
}
|
|
?>
|