<?php
declare(strict_types=1);
namespace App\EventListener;
use App\Entity\MobileJwtToken;
use App\Entity\User\ShopUser;
use App\Repository\MobileJwtTokenRepository;
use Doctrine\ORM\EntityManagerInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
use Lexik\Bundle\JWTAuthenticationBundle\Event\AuthenticationSuccessEvent;
use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTAuthenticatedEvent;
use Safe\DateTime;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use function in_array;
use function is_array;
use function substr;
class JWTListener
{
public function __construct(private RequestStack $requestStack, private EntityManagerInterface $entityManagerInterface, private JWTEncoderInterface $jWTEncoderInterface, private MobileJwtTokenRepository $mobileJwtTokenRepository)
{
}
public function onJWTAuthenticated(JWTAuthenticatedEvent $event): void
{
//this is triggered when a protected route is accessed with a valid JWT token in the header,
//we need to check if this token exists in our mobile_jwt_token table first. Only if it exists, we consider this token valid
$payload = $event->getPayload();
if (isset($payload)) {
if (isset($payload['roles'])) {
if (in_array('ROLE_ADMINISTRATION_ACCESS', $payload['roles'])) {
return;
}
}
}
$currentRequest = $this->requestStack->getCurrentRequest();
if ($currentRequest === null) {
$event->stopPropagation();
throw new AuthenticationException('token invalid (failed to access request header)');
}
$authorizationInHeader = $currentRequest->headers->get('authorization');
if ($authorizationInHeader === null) {
$event->stopPropagation();
throw new AuthenticationException('token invalid (failed to access request header)');
}
$tokenString = substr($authorizationInHeader, 7);
/** @var ShopUser|null $shopUser */
$shopUser = $this->entityManagerInterface->getRepository(ShopUser::class)->findOneBy(['username' => $payload['username']]);
if ($shopUser === null) {
$event->stopPropagation();
throw new AuthenticationException('token invalid (shopUser was not found)');
}
$mobileJwtToken = $this->mobileJwtTokenRepository->findOneBy(['token' => $tokenString, 'shopUser' => $shopUser->getId()]);
if ($mobileJwtToken === null) {
$event->stopPropagation();
throw new AuthenticationException('token invalid (token not found in db)');
}
return;
}
public function onAuthenticationSuccess(AuthenticationSuccessEvent $event): void
{
//When a user logs in successfully from the API, a JWT is created and saved in the mobile_JWT_token table
$payload = $event->getData();
$userData = $event->getUser();
if ($userData !== null) {
$userRoles = $userData->getRoles();
if ($userRoles !== null && is_array($userRoles)) {
if (in_array('ROLE_ADMINISTRATION_ACCESS', $userRoles)) {
return;
}
}
}
$token = $payload['token'];
/** @var ShopUser $shopUser */
$shopUser = $event->getUser();
$error = false;
if ($token === null || empty($token)) {
$error = true;
}
$decodedToken = $this->jWTEncoderInterface->decode($token);
if (! isset($decodedToken['exp'])) {
$error = true;
} else {
if (empty($decodedToken['exp'])) {
$error = true;
}
}
if ($error) {
$event->stopPropagation();
$response = $event->getResponse();
if ($response instanceof JsonResponse) {
$responseData = ['code' => 401, 'message' => 'token invalid / failed to parse token'];
$response->setData($responseData);
$response->setStatusCode(401);
}
return;
}
$tokenExpiresOn = DateTime::createFromFormat('U', $decodedToken['exp']);
$currentDateTime = new DateTime();
$mobileJwtToken = new MobileJwtToken($currentDateTime);
$mobileJwtToken->setToken($token);
$mobileJwtToken->setExpiresOn($tokenExpiresOn);
$mobileJwtToken->setShopUser($shopUser);
$this->entityManagerInterface->persist($mobileJwtToken);
$this->entityManagerInterface->flush();
}
}