<?php
declare(strict_types=1);
namespace App\System\Listener\User\Authentication;
use ApiPlatform\Core\Bridge\Symfony\Validator\Exception\ValidationException;
use App\Entities\User\Admin;
use App\Entities\User\UserInterface;
use App\Infrastructure\Interfaces\Logger\MessageDirectorInterface;
use App\Infrastructure\Interfaces\Logger\NetworkLoggerInterface;
use App\System\Infrastructure\Implementation\Connection\Live\Event\User\AccessTokenRefreshedEvent;
use App\System\Infrastructure\Implementation\Logger\User\Authentication\MessageBuilder;
use App\UseCase\User\Event\Authentication\SuccessEvent;
use App\UseCase\User\Read\TwoFactor\CodeValidationDto;
use App\UseCase\User\Read\TwoFactor\CodeValidationHandler;
use App\UseCase\User\Write\Authentication\SuccessHandler;
use App\UseCase\User\Write\TwoFactor\CreationDto;
use App\UseCase\User\Write\TwoFactor\CreationHandler;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationList;
class AuthenticationListener implements EventSubscriberInterface
{
public function __construct(
private readonly CreationHandler $twoFactorCreationHandler,
private readonly CodeValidationHandler $codeValidationHandler,
private readonly SuccessHandler $successHandler,
private readonly MessageDirectorInterface $messageDirector,
private readonly RequestStack $requestStack,
private readonly NetworkLoggerInterface $networkLogger,
private readonly MessageBusInterface $bus,
) {
}
public static function getSubscribedEvents(): array
{
return [
LoginSuccessEvent::class => [
[
'loginSuccess',
0,
],
[
'tokenCreated',
1,
],
],
SuccessEvent::class => 'authenticationSuccess',
];
}
/**
* @param LoginSuccessEvent $event
* @throws \App\UseCase\User\Write\TwoFactor\Exception\UserAlreadyHasTwoFactorAuthenticationException
*/
public function loginSuccess(LoginSuccessEvent $event): void
{
$request = $event->getRequest();
$routeName = $request->attributes->get('_route');
if ($routeName !== 'api_login_check') {
return;
}
$user = $event->getPassport()?->getUser();
if (!$user instanceof UserInterface) {
return;
}
if (!$user instanceof Admin) {
return;
}
if (!$user->hasTwoFactorKey()) {
$this->twoFactorCreationHandler->handle(new CreationDto($user));
$response = $event->getResponse();
$response->setStatusCode(400);
$response->setContent(
json_encode([
'qrcode' => $this->twoFactorCreationHandler->getQrCodeWithSecretBase64(),
'identifier' => $this->twoFactorCreationHandler->getUserIdentifier(),
])
);
} else {
$data = json_decode($request->getContent(), true);
if (isset($data['twoFaCode'])) {
$result = $this->codeValidationHandler->handle(
new CodeValidationDto($user, $data['twoFaCode'])
);
return;
if ($result->valid) {
$this->successHandler->handle(
new \App\UseCase\User\Write\Authentication\SuccessDto($user)
);
return;
}
}
$list = new ConstraintViolationList();
$list->add(new ConstraintViolation('Неверный код', null, [], null, 'twoFaCode', null));
throw new ValidationException($list);
}
}
public function authenticationSuccess(SuccessEvent $event): void
{
$request = $this->requestStack->getMainRequest();
if (!$request) {
return;
}
$builder = new MessageBuilder();
$builder
->setCountry('')
->setIp($request->getClientIp() ?? '')
->setUserAgent($request->headers->get('user-agent', ''));
$this->messageDirector->setMessageBuilder($builder);
$this->networkLogger->publish($this->messageDirector);
}
/**
* Доставляем событие обновления токена пользователя в сокетные соединения
*/
public function tokenCreated(LoginSuccessEvent $event): void
{
if (!$event->getResponse()) {
return;
}
$user = $event->getUser();
$response = json_decode($event->getResponse()->getContent(), true);
if (!$response || !$user instanceof UserInterface) {
return;
}
$this->bus->dispatch(
new AccessTokenRefreshedEvent(
$user->getUserIdentifier(),
$response['token'],
)
);
}
}