Revived API POST /api/v1/captcha.
Captcha validation has been adjusted.
This commit is contained in:
@@ -3,10 +3,17 @@
|
||||
namespace App\Services\Api\V1;
|
||||
|
||||
use App\Dto\Request\Api\V1\Captcha\CaptchaPublicToken;
|
||||
use App\Dto\Request\Api\V1\Captcha\CheckingDto;
|
||||
use App\Enums\CaptchaLogType;
|
||||
use App\Repositories\CaptchaLogRepository;
|
||||
use App\Repositories\CaptchaRepository;
|
||||
use App\Repositories\DataCaptchaRepository;
|
||||
use App\ServiceResults\Api\V1\CaptchaService\Captcha;
|
||||
use App\ServiceResults\Api\V1\CaptchaService\CaptchaVerifiedResult;
|
||||
use App\ServiceResults\ServiceResultError;
|
||||
use App\Services\Captcha\CaptchaHandler;
|
||||
use App\Services\Captcha\CheckingCommand;
|
||||
use App\Services\CaptchaLog\CaptchaLogHandler;
|
||||
use App\Services\Service;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
@@ -16,7 +23,11 @@ final class CaptchaService extends Service
|
||||
public function __construct(
|
||||
private readonly CaptchaGenerateService $captchaGenerateService,
|
||||
private readonly CaptchaHandler $captchaHandler,
|
||||
private readonly CaptchaLogHandler $captchaLogHandler,
|
||||
private readonly DataCaptchaRepository $dataCaptchaRepository,
|
||||
private readonly CaptchaLogRepository $captchaLogRepository,
|
||||
private readonly CaptchaRepository $captchaRepository,
|
||||
private readonly CheckingCommand $checkingCommand,
|
||||
) { }
|
||||
|
||||
public function createKeyWithCaptcha(CaptchaPublicToken $captchaPublicToken, Carbon $expires): ServiceResultError | Captcha
|
||||
@@ -43,4 +54,40 @@ final class CaptchaService extends Service
|
||||
key: $captchaKey
|
||||
);
|
||||
}
|
||||
|
||||
public function checking(CheckingDto $checkingDto, int $maxCountError): ServiceResultError | CaptchaVerifiedResult
|
||||
{
|
||||
try {
|
||||
$captchaData = $this->dataCaptchaRepository->getByKey($checkingDto->getCaptchaKey());
|
||||
if (is_null($captchaData)) {
|
||||
return $this->errValidate(__('Captcha not found or verification period has expired. Please try to refresh the captcha.'));
|
||||
}
|
||||
if (!$this->checkingCommand->execute($captchaData->getCoordinators(), $checkingDto->getCoordinators())) {
|
||||
$this->captchaLogHandler->handleStore($captchaData->getCaptchaId(), CaptchaLogType::Error, $checkingDto->getCaptchaPublicToken()->getHttpUserData());
|
||||
return $this->errValidate(__('CAPTCHA validation failed. Please try again.'));
|
||||
}
|
||||
$result = DB::transaction(function () use ($checkingDto, $captchaData, $maxCountError) {
|
||||
$errorCount = $this->captchaLogRepository->countByType(CaptchaLogType::Error, $captchaData->getCaptchaId());
|
||||
if ($errorCount > $maxCountError) {
|
||||
$this->dataCaptchaRepository->destroy($checkingDto->getCaptchaKey());
|
||||
return $this->errValidate(__('You have exceeded the number of attempts. Please refresh the captcha.'));
|
||||
}
|
||||
|
||||
$this->captchaLogHandler->handleStore($captchaData->getCaptchaId(), CaptchaLogType::Verified, $checkingDto->getCaptchaPublicToken()->getHttpUserData());
|
||||
$this->dataCaptchaRepository->destroy($checkingDto->getCaptchaKey());
|
||||
|
||||
$modelCaptcha = $this->captchaRepository->getCaptchaById($captchaData->getCaptchaId());
|
||||
if (is_null($modelCaptcha)) {
|
||||
return $this->errValidate(__('Captcha not found or verification period has expired. Please try to refresh the captcha.'));
|
||||
}
|
||||
|
||||
return new CaptchaVerifiedResult(key: $modelCaptcha->uuid);
|
||||
});
|
||||
} catch (\Throwable $e) {
|
||||
report($e);
|
||||
return $this->errService('Captcha service error!');
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
@@ -21,7 +21,7 @@ final readonly class CaptchaHandler
|
||||
$captcha = $captchaToken->captchas()->create([
|
||||
'uuid' => $this->uuidCommand->unique($captchaToken->captchas()->getQuery(), 'uuid')
|
||||
]);
|
||||
$this->captchaLogHandler->handleStore($captcha, CaptchaLogType::Created, $httpUserData);
|
||||
$this->captchaLogHandler->handleStore($captcha->id, CaptchaLogType::Created, $httpUserData);
|
||||
|
||||
return $captcha;
|
||||
}
|
||||
|
53
app/Services/Captcha/CheckingCommand.php
Normal file
53
app/Services/Captcha/CheckingCommand.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
namespace App\Services\Captcha;
|
||||
|
||||
use App\Captcha\Dto\Coordinators;
|
||||
|
||||
final readonly class CheckingCommand
|
||||
{
|
||||
|
||||
/**
|
||||
* @param array $captchaCoordinators
|
||||
* @param array $userCoordinators
|
||||
* @return bool
|
||||
*/
|
||||
public function execute(array $captchaCoordinators, array $userCoordinators): bool
|
||||
{
|
||||
foreach ($captchaCoordinators as $index => $coordinators) {
|
||||
/**
|
||||
* @var Coordinators $coordinators
|
||||
*/
|
||||
|
||||
if (empty($userCoordinators[$index]) || !isset($userCoordinators[$index]['x']) || !isset($userCoordinators[$index]['y'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->isInside($coordinators, $userCoordinators[$index])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function isInside(Coordinators $captchaCoordinators, array $userCoordinators): bool
|
||||
{
|
||||
$xMin = min($captchaCoordinators->getX1(), $captchaCoordinators->getX2(), $captchaCoordinators->getX3(), $captchaCoordinators->getX4());
|
||||
$xMax = max($captchaCoordinators->getX1(), $captchaCoordinators->getX2(), $captchaCoordinators->getX3(), $captchaCoordinators->getX4());
|
||||
|
||||
$yMin = min($captchaCoordinators->getY1(), $captchaCoordinators->getY2(), $captchaCoordinators->getY3(), $captchaCoordinators->getY4());
|
||||
$yMax = max($captchaCoordinators->getY1(), $captchaCoordinators->getY2(), $captchaCoordinators->getY3(), $captchaCoordinators->getY4());
|
||||
|
||||
if (
|
||||
$xMin > $userCoordinators['x']
|
||||
|| $xMax < $userCoordinators['x']
|
||||
|| $yMin > $userCoordinators['y']
|
||||
|| $yMax < $userCoordinators['y']
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -10,13 +10,24 @@ use Illuminate\Support\Str;
|
||||
|
||||
final readonly class CaptchaLogHandler
|
||||
{
|
||||
public function handleStore(Captcha $captcha, CaptchaLogType $captchaLogType, HttpUserData $httpUserData): CaptchaLog
|
||||
public function handleStore(int $captchaId, CaptchaLogType $captchaLogType, HttpUserData $httpUserData): CaptchaLog
|
||||
{
|
||||
return $captcha->captchaLogs()->create([
|
||||
$userAgent = $httpUserData->getUserAgent();
|
||||
if (!is_null($userAgent)) {
|
||||
$userAgent = Str::limit($userAgent, 255, '');
|
||||
}
|
||||
|
||||
$referer = $httpUserData->getReferer();
|
||||
if (!is_null($referer)) {
|
||||
$referer = Str::limit($referer, 10000, '');
|
||||
}
|
||||
|
||||
return CaptchaLog::create([
|
||||
'captcha_id' => $captchaId,
|
||||
'type' => $captchaLogType,
|
||||
'ip' => $httpUserData->getClientIp(),
|
||||
'user_agent' => Str::limit($httpUserData->getUserAgent(), 255, ''),
|
||||
'referer' => Str::limit($httpUserData->getReferer(), 10000, ''),
|
||||
'user_agent' => $userAgent,
|
||||
'referer' => $referer,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user