147 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
		
			6.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php declare(strict_types=1);
 | 
						|
 | 
						|
namespace App\Services\Api\V1;
 | 
						|
 | 
						|
use App\Dto\Request\Api\V1\Captcha\CaptchaPublicToken;
 | 
						|
use App\Dto\Request\Api\V1\Captcha\CheckingDto;
 | 
						|
use App\Dto\Request\Api\V1\Captcha\VerificationInformationDto;
 | 
						|
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\CaptchaVerificationInformationResult;
 | 
						|
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;
 | 
						|
use Illuminate\Support\Str;
 | 
						|
 | 
						|
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
 | 
						|
    {
 | 
						|
        try {
 | 
						|
            $captcha = $this->captchaGenerateService->generate();
 | 
						|
            if ($captcha->isError()) {
 | 
						|
                return $captcha;
 | 
						|
            }
 | 
						|
 | 
						|
            $modelCaptcha = DB::transaction(function () use ($captchaPublicToken) {
 | 
						|
                return $this->captchaHandler->handleStore($captchaPublicToken->getCaptchaToken(), $captchaPublicToken->getHttpUserData());
 | 
						|
            });
 | 
						|
 | 
						|
            $captchaKey = $this->dataCaptchaRepository->store($modelCaptcha, $captcha->getImageBody()->getCoordinators(), $expires);
 | 
						|
        } catch (\Throwable $e) {
 | 
						|
            report($e);
 | 
						|
            return $this->errService('Captcha service error!');
 | 
						|
        }
 | 
						|
 | 
						|
        return new Captcha(
 | 
						|
            imageHead: $captcha->getImageHead()->getImage(),
 | 
						|
            imageBody: $captcha->getImageBody()->getImage(),
 | 
						|
            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;
 | 
						|
    }
 | 
						|
 | 
						|
    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'),
 | 
						|
        );
 | 
						|
    }
 | 
						|
}
 |