2023-06-28 17:29:56 +06:00
|
|
|
<?php declare(strict_types=1);
|
|
|
|
|
|
|
|
namespace App\Services\Api\V1;
|
|
|
|
|
2023-09-19 14:27:33 +06:00
|
|
|
use App\Dto\Request\Api\V1\Captcha\CaptchaPublicToken;
|
2023-11-26 15:09:42 +06:00
|
|
|
use App\Dto\Request\Api\V1\Captcha\CheckingDto;
|
2023-11-26 22:42:22 +06:00
|
|
|
use App\Dto\Request\Api\V1\Captcha\VerificationInformationDto;
|
2023-11-26 15:09:42 +06:00
|
|
|
use App\Enums\CaptchaLogType;
|
|
|
|
use App\Repositories\CaptchaLogRepository;
|
|
|
|
use App\Repositories\CaptchaRepository;
|
2023-09-19 14:27:33 +06:00
|
|
|
use App\Repositories\DataCaptchaRepository;
|
2023-06-28 17:29:56 +06:00
|
|
|
use App\ServiceResults\Api\V1\CaptchaService\Captcha;
|
2023-11-26 22:42:22 +06:00
|
|
|
use App\ServiceResults\Api\V1\CaptchaService\CaptchaVerificationInformationResult;
|
2023-11-26 15:09:42 +06:00
|
|
|
use App\ServiceResults\Api\V1\CaptchaService\CaptchaVerifiedResult;
|
2023-06-28 17:29:56 +06:00
|
|
|
use App\ServiceResults\ServiceResultError;
|
2023-09-19 14:27:33 +06:00
|
|
|
use App\Services\Captcha\CaptchaHandler;
|
2023-11-26 15:09:42 +06:00
|
|
|
use App\Services\Captcha\CheckingCommand;
|
|
|
|
use App\Services\CaptchaLog\CaptchaLogHandler;
|
2023-06-28 17:29:56 +06:00
|
|
|
use App\Services\Service;
|
2023-09-19 14:27:33 +06:00
|
|
|
use Carbon\Carbon;
|
|
|
|
use Illuminate\Support\Facades\DB;
|
2023-11-26 22:42:22 +06:00
|
|
|
use Illuminate\Support\Str;
|
2023-06-28 17:29:56 +06:00
|
|
|
|
|
|
|
final class CaptchaService extends Service
|
|
|
|
{
|
|
|
|
public function __construct(
|
2023-09-19 14:27:33 +06:00
|
|
|
private readonly CaptchaGenerateService $captchaGenerateService,
|
|
|
|
private readonly CaptchaHandler $captchaHandler,
|
2023-11-26 15:09:42 +06:00
|
|
|
private readonly CaptchaLogHandler $captchaLogHandler,
|
2023-09-19 14:27:33 +06:00
|
|
|
private readonly DataCaptchaRepository $dataCaptchaRepository,
|
2023-11-26 15:09:42 +06:00
|
|
|
private readonly CaptchaLogRepository $captchaLogRepository,
|
|
|
|
private readonly CaptchaRepository $captchaRepository,
|
|
|
|
private readonly CheckingCommand $checkingCommand,
|
2023-06-28 17:29:56 +06:00
|
|
|
) { }
|
|
|
|
|
2023-09-19 14:27:33 +06:00
|
|
|
public function createKeyWithCaptcha(CaptchaPublicToken $captchaPublicToken, Carbon $expires): ServiceResultError | Captcha
|
2023-06-28 17:29:56 +06:00
|
|
|
{
|
|
|
|
try {
|
2023-09-19 14:27:33 +06:00
|
|
|
$captcha = $this->captchaGenerateService->generate();
|
|
|
|
if ($captcha->isError()) {
|
|
|
|
return $captcha;
|
|
|
|
}
|
2023-06-28 17:29:56 +06:00
|
|
|
|
2023-09-19 14:27:33 +06:00
|
|
|
$modelCaptcha = DB::transaction(function () use ($captchaPublicToken) {
|
|
|
|
return $this->captchaHandler->handleStore($captchaPublicToken->getCaptchaToken(), $captchaPublicToken->getHttpUserData());
|
|
|
|
});
|
2023-06-28 17:29:56 +06:00
|
|
|
|
2023-09-19 14:27:33 +06:00
|
|
|
$captchaKey = $this->dataCaptchaRepository->store($modelCaptcha, $captcha->getImageBody()->getCoordinators(), $expires);
|
2023-06-28 17:29:56 +06:00
|
|
|
} catch (\Throwable $e) {
|
|
|
|
report($e);
|
|
|
|
return $this->errService('Captcha service error!');
|
|
|
|
}
|
|
|
|
|
|
|
|
return new Captcha(
|
2023-09-19 14:27:33 +06:00
|
|
|
imageHead: $captcha->getImageHead()->getImage(),
|
|
|
|
imageBody: $captcha->getImageBody()->getImage(),
|
|
|
|
key: $captchaKey
|
2023-06-28 17:29:56 +06:00
|
|
|
);
|
|
|
|
}
|
2023-11-26 15:09:42 +06:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2023-11-26 22:42:22 +06:00
|
|
|
|
|
|
|
public function verificationInformation(string $captchaUuid, VerificationInformationDto $verificationInformationDto, int $expiresMinutes, int $maxInfoDisplayCount = 1): ServiceResultError | CaptchaVerificationInformationResult
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
$captcha = $this->captchaRepository->getCaptchaByUuid($verificationInformationDto->getCaptchaToken(), $captchaUuid);
|
|
|
|
if (is_null($captcha)) {
|
|
|
|
return $this->errNotFound(__('Captcha not found'));
|
|
|
|
}
|
|
|
|
|
|
|
|
$captchaLogs = $this->captchaLogRepository->getCaptchaLogsByTypes([CaptchaLogType::Verified, CaptchaLogType::ReadVerified], $captcha->id);
|
|
|
|
$this->captchaLogHandler->handleStore($captcha->id, CaptchaLogType::ReadVerified, $verificationInformationDto->getHttpUserData());
|
|
|
|
|
|
|
|
$captchaVerificationLog = $captchaLogs->firstWhere('type', CaptchaLogType::Verified);
|
|
|
|
if (is_null($captchaVerificationLog)) {
|
|
|
|
return new CaptchaVerificationInformationResult(
|
|
|
|
status: false,
|
|
|
|
message: __('Captcha does not pass verification'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!is_null($verificationInformationDto->getUserAgent()) && Str::limit($verificationInformationDto->getUserAgent(), 255, '') !== $captchaVerificationLog->user_agent) {
|
|
|
|
return new CaptchaVerificationInformationResult(
|
|
|
|
status: false,
|
|
|
|
message: __('Captcha User Agent does not match'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (now()->diffInMinutes($captchaVerificationLog->created_at) > $expiresMinutes) {
|
|
|
|
return new CaptchaVerificationInformationResult(
|
|
|
|
status: false,
|
|
|
|
message: __('The time for captcha verification has passed'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($captchaLogs->where('type', CaptchaLogType::ReadVerified)->count() >= $maxInfoDisplayCount) {
|
|
|
|
return new CaptchaVerificationInformationResult(
|
|
|
|
status: false,
|
|
|
|
message: __('Captcha verification status view count exceeded'),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
} catch (\Throwable $e) {
|
|
|
|
report($e);
|
|
|
|
return $this->errService('Captcha service error!');
|
|
|
|
}
|
|
|
|
|
|
|
|
return new CaptchaVerificationInformationResult(
|
|
|
|
status: true,
|
|
|
|
message: __('Captcha Verified'),
|
|
|
|
);
|
|
|
|
}
|
2023-06-28 17:29:56 +06:00
|
|
|
}
|