. */ declare(strict_types=1); namespace Fisharebest\Webtrees\Services; use Fisharebest\Webtrees\Session; use Fisharebest\Webtrees\Validator; use Psr\Http\Message\ServerRequestInterface; use Ramsey\Uuid\Uuid; 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 = Uuid::uuid4()->toString(); $y = Uuid::uuid4()->toString(); $z = Uuid::uuid4()->toString(); Session::put('captcha-t', microtime(true)); 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 microtime(true) < $t + self::MINIMUM_FORM_TIME; } }