. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Services; use Fisharebest\Webtrees\Registry; use Fisharebest\Webtrees\Session; use Fisharebest\Webtrees\Validator; use Psr\Http\Message\ServerRequestInterface; use function assert; use function is_float; use function is_string; use function view; /** * Completely Automated Public Turing test to tell Computers and Humans Apart. */ class CaptchaService { // If the form is completed faster than this, then suspect a robot. private const MINIMUM_FORM_TIME = 3.0; /** * Create the captcha * * @return string */ public function createCaptcha(): string { $x = Registry::idFactory()->uuid(); $y = Registry::idFactory()->uuid(); $z = Registry::idFactory()->uuid(); Session::put('captcha-t', Registry::timeFactory()->now()); Session::put('captcha-x', $x); Session::put('captcha-y', $y); Session::put('captcha-z', $z); return view('captcha', [ 'x' => $x, 'y' => $y, 'z' => $z, ]); } /** * Check the user's response. * * @param ServerRequestInterface $request * * @return bool */ public function isRobot(ServerRequestInterface $request): bool { $t = Session::pull('captcha-t'); $x = Session::pull('captcha-x'); $y = Session::pull('captcha-y'); $z = Session::pull('captcha-z'); assert(is_float($t)); assert(is_string($x)); assert(is_string($y)); assert(is_string($z)); $value_x = Validator::parsedBody($request)->string($x, ''); $value_y = Validator::parsedBody($request)->string($y, ''); // The captcha uses javascript to copy value z from field y to field x. // Expect it in both fields. if ($value_x !== $z || $value_y !== $z) { return true; } // If the form was returned too quickly, then probably a robot. return Registry::timeFactory()->now() < $t + self::MINIMUM_FORM_TIME; } }