Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Entity/JWT/RefreshToken.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

use Doctrine\ORM\Mapping as ORM;
use Gesdinet\JWTRefreshTokenBundle\Model\AbstractRefreshToken;
use StfalconStudio\ApiBundle\Model\JWT\CreatedAtAwareRefreshTokenInterface;
use StfalconStudio\ApiBundle\Repository\JWT\RefreshTokenRepository;
use Symfony\Component\Validator\Constraints as Assert;

Expand All @@ -33,7 +34,7 @@
]
)]
#[ORM\Index(columns: ['valid'], name: 'idx_refresh_token_valid')]
class RefreshToken extends AbstractRefreshToken
class RefreshToken extends AbstractRefreshToken implements CreatedAtAwareRefreshTokenInterface
{
#[ORM\Id]
#[ORM\Column(name: 'id', type: 'integer')]
Expand Down
22 changes: 12 additions & 10 deletions EventListener/JWT/JwtRefreshSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
namespace StfalconStudio\ApiBundle\EventListener\JWT;

use Gesdinet\JWTRefreshTokenBundle\Event\RefreshEvent;
use StfalconStudio\ApiBundle\Entity\JWT\RefreshToken;
use StfalconStudio\ApiBundle\Exception\JWT\InvalidRefreshTokenException;
use StfalconStudio\ApiBundle\Model\Credentials\CredentialsInterface;
use StfalconStudio\ApiBundle\Model\JWT\CreatedAtAwareRefreshTokenInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

/**
Expand All @@ -41,18 +41,20 @@ public static function getSubscribedEvents(): iterable
public function processRefreshToken(RefreshEvent $event): void
{
$user = $event->getToken()->getUser();
$refreshToken = $event->getRefreshToken();

if ($user instanceof CredentialsInterface) {
$refreshToken = $event->getRefreshToken();
if (!$user instanceof CredentialsInterface || !$refreshToken instanceof CreatedAtAwareRefreshTokenInterface) {
return;
}

$userCredentialsLastChangedAt = $user->getCredentialsLastChangedAt();

if ($refreshToken instanceof RefreshToken) {
$userCredentialsLastChangedAt = $user->getCredentialsLastChangedAt();
$refreshTokenCreatedAt = $refreshToken->getCreatedAt()->getTimestamp();
if (!$userCredentialsLastChangedAt instanceof \DateTimeInterface) {
return;
}

if ($userCredentialsLastChangedAt instanceof \DateTimeInterface && $refreshTokenCreatedAt < $userCredentialsLastChangedAt->getTimestamp()) {
throw new InvalidRefreshTokenException();
}
}
if ($refreshToken->getCreatedAt()->getTimestamp() < $userCredentialsLastChangedAt->getTimestamp()) {
throw new InvalidRefreshTokenException();
}
}
}
32 changes: 32 additions & 0 deletions Model/JWT/CreatedAtAwareRefreshTokenInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

/*
* This file is part of the StfalconApiBundle.
*
* (c) Stfalcon LLC <stfalcon.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace StfalconStudio\ApiBundle\Model\JWT;

use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface;

/**
* CreatedAtAwareRefreshTokenInterface.
*
* A refresh token that knows when it was issued.
*
* The creation date should not be derived from RefreshTokenInterface::getValid(),
* because the expiration date is rewritten on every use when the `ttl_update` option is enabled
*/
interface CreatedAtAwareRefreshTokenInterface extends RefreshTokenInterface
{
/**
* @return \DateTimeInterface
*/
public function getCreatedAt(): \DateTimeInterface;
}
30 changes: 30 additions & 0 deletions Tests/EventListener/JWT/JwtRefreshSubscriberTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
namespace StfalconStudio\ApiBundle\Tests\EventListener\JWT;

use Gesdinet\JWTRefreshTokenBundle\Event\RefreshEvent;
use Gesdinet\JWTRefreshTokenBundle\Model\RefreshTokenInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use StfalconStudio\ApiBundle\Entity\JWT\RefreshToken;
Expand Down Expand Up @@ -133,4 +134,33 @@ public function testProcessRefreshTokenWithoutException(): void

$this->subscriber->processRefreshToken($this->refreshEvent);
}

public function testProcessRefreshTokenWithRefreshTokenWithoutCreatedAt(): void
{
$refreshTokenWithoutCreatedAt = $this->createMock(RefreshTokenInterface::class);

$this->refreshEvent
->expects(self::once())
->method('getToken')
->willReturn($this->token)
;
$this->refreshEvent
->expects(self::once())
->method('getRefreshToken')
->willReturn($refreshTokenWithoutCreatedAt)
;

$this->token
->expects(self::once())
->method('getUser')
->willReturn($this->user)
;

$this->user
->expects(self::never())
->method('getCredentialsLastChangedAt')
;

$this->subscriber->processRefreshToken($this->refreshEvent);
}
}
Loading