Skip to content

Commit

Permalink
[shopsys] immediate access token revocation (#3417)
Browse files Browse the repository at this point in the history
  • Loading branch information
grossmannmartin authored Sep 13, 2024
2 parents d6189d8 + ac7a21b commit 5699f2d
Show file tree
Hide file tree
Showing 6 changed files with 138 additions and 37 deletions.
3 changes: 2 additions & 1 deletion app/src/FrontendApi/Model/Token/TokenAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

/**
* @property \App\FrontendApi\Model\Token\TokenFacade $tokenFacade
* @method __construct(\App\FrontendApi\Model\Token\TokenFacade $tokenFacade, \Shopsys\FrontendApiBundle\Model\User\FrontendApiUserProvider $frontendApiUserProvider)
* @method __construct(\App\FrontendApi\Model\Token\TokenFacade $tokenFacade, \Shopsys\FrontendApiBundle\Model\User\FrontendApiUserProvider $frontendApiUserProvider, \App\Model\Customer\User\CustomerUserFacade $customerUserFacade)
* @property \App\Model\Customer\User\CustomerUserFacade $customerUserFacade
*/
class TokenAuthenticator extends BaseTokenAuthenticator
{
Expand Down
15 changes: 2 additions & 13 deletions app/src/Model/Customer/User/CustomerUserPasswordFacade.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,11 @@
* @property \Shopsys\FrameworkBundle\Model\Customer\User\CustomerUserRepository $customerUserRepository
* @property \App\Model\Customer\User\CustomerUserRefreshTokenChainFacade $customerUserRefreshTokenChainFacade
* @method __construct(\Doctrine\ORM\EntityManagerInterface $em, \Shopsys\FrameworkBundle\Model\Customer\User\CustomerUserRepository $customerUserRepository, \Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface $passwordHasherFactory, \Shopsys\FrameworkBundle\Model\Customer\Mail\ResetPasswordMailFacade $resetPasswordMailFacade, \Shopsys\FrameworkBundle\Component\String\HashGenerator $hashGenerator, \App\Model\Customer\User\CustomerUserRefreshTokenChainFacade $customerUserRefreshTokenChainFacade)
* @method changePassword(\App\Model\Customer\User\CustomerUser $customerUser, string $password)
* @method setPassword(\App\Model\Customer\User\CustomerUser $customerUser, string $password)
*/
class CustomerUserPasswordFacade extends BaseCustomerUserPasswordFacade
{
/**
* @param \App\Model\Customer\User\CustomerUser $customerUser
* @param string $password
*/
public function changePassword(CustomerUser $customerUser, string $password): void
{
if ($password !== '') {
parent::changePassword($customerUser, $password);
} else {
$customerUser->setPasswordHash('');
}
}

/**
* @param string $email
* @param int $domainId
Expand Down
44 changes: 21 additions & 23 deletions app/tests/FrontendApiBundle/Functional/Login/LoginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ class LoginTest extends GraphQlTestCase
public function testLoginMutation(): void
{
$graphQlType = 'Login';
$response = $this->getResponseContentForQuery(self::getLoginQuery());

$this->assertResponseContainsArrayOfDataForGraphQlType($response, $graphQlType);
$response = $this->getResponseContentForGql(
__DIR__ . '/graphql/LoginMutation.graphql',
$this->getDefaultCredentials(),
);
$responseData = $this->getResponseDataForGraphQlType($response, $graphQlType);

$this->assertArrayHasKey('tokens', $responseData);
Expand All @@ -32,15 +34,17 @@ public function testLoginMutation(): void

try {
$this->tokenFacade->getTokenByString($responseData['tokens']['accessToken']);
} catch (Throwable $throwable) {
} catch (Throwable) {
$this->fail('Token is not valid');
}

$clientOptions = ['HTTP_X-Auth-Token' => sprintf('Bearer %s', $responseData['tokens']['accessToken'])];
$this->configureCurrentClient(null, null, $clientOptions);
$authorizationResponse = $this->getResponseContentForQuery(self::getLoginQuery());

$this->assertResponseContainsArrayOfDataForGraphQlType($authorizationResponse, $graphQlType);
$authorizationResponse = $this->getResponseContentForGql(
__DIR__ . '/graphql/LoginMutation.graphql',
$this->getDefaultCredentials(),
);
$authorizationResponseData = $this->getResponseDataForGraphQlType($authorizationResponse, $graphQlType);

$this->assertArrayHasKey('tokens', $authorizationResponseData);
Expand All @@ -50,13 +54,13 @@ public function testLoginMutation(): void
$this->assertIsString($authorizationResponseData['tokens']['refreshToken']);
}

public function testInvalidTokenException()
public function testInvalidTokenException(): void
{
$this->expectException(InvalidTokenUserMessageException::class);
$this->tokenFacade->getTokenByString('abcd');
}

public function testInvalidTokenInHeader()
public function testInvalidTokenInHeader(): void
{
$expectedError = [
'errors' => [
Expand All @@ -71,28 +75,22 @@ public function testInvalidTokenInHeader()

$this->configureCurrentClient(null, null, ['HTTP_X-Auth-Token' => 'Bearer 123']);

$response = $this->getResponseContentForQuery(self::getLoginQuery());
$response = $this->getResponseContentForGql(
__DIR__ . '/graphql/LoginMutation.graphql',
$this->getDefaultCredentials(),
);

$this->assertSame($expectedError, $response);
}

/**
* @return string
* @return array<string, string>
*/
private static function getLoginQuery(): string
private function getDefaultCredentials(): array
{
return '
mutation {
Login(input: {
email: "no-reply@shopsys.com"
password: "user123"
}) {
tokens {
accessToken
refreshToken
}
}
}
';
return [
'email' => 'no-reply@shopsys.com',
'password' => 'user123',
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<?php

declare(strict_types=1);

namespace Tests\FrontendApiBundle\Functional\Login;

use Tests\FrontendApiBundle\Test\GraphQlTestCase;

class LoginTokenInvalidationTest extends GraphQlTestCase
{
public function testAccessTokenIsNotValidAfterPasswordChange(): void
{
$this->loginCustomerUser();

$this->checkAccessTokenIsValid();

$this->changeCustomerUserPassword();

$this->checkAccessTokenIsRevoked();
}

private function loginCustomerUser(): void
{
$response = $this->getResponseContentForGql(
__DIR__ . '/graphql/LoginMutation.graphql',
$this->getDefaultCredentials(),
);
$responseData = $this->getResponseDataForGraphQlType($response, 'Login');

$this->assertArrayHasKey('tokens', $responseData);
$this->assertArrayHasKey('accessToken', $responseData['tokens']);
$accessToken = $responseData['tokens']['accessToken'];

$clientOptions = ['HTTP_X-Auth-Token' => sprintf('Bearer %s', $accessToken)];
$this->configureCurrentClient(null, null, $clientOptions);
}

private function checkAccessTokenIsValid(): void
{
$response = $this->getResponseContentForGql(
__DIR__ . '/../_graphql/query/CurrentCustomerUserQuery.graphql',
);
$responseData = $this->getResponseDataForGraphQlType($response, 'currentCustomerUser');

$this->assertArrayHasKey('email', $responseData);
$this->assertSame($this->getDefaultCredentials()['email'], $responseData['email']);
}

private function changeCustomerUserPassword(): void
{
$response = $this->getResponseContentForGql(
__DIR__ . '/graphql/ChangePasswordMutation.graphql',
[
'email' => $this->getDefaultCredentials()['email'],
'oldPassword' => $this->getDefaultCredentials()['password'],
'newPassword' => 'user124',
],
);
$responseData = $this->getResponseDataForGraphQlType($response, 'ChangePassword');

$this->assertArrayHasKey('email', $responseData);
$this->assertSame($this->getDefaultCredentials()['email'], $responseData['email']);
}

private function checkAccessTokenIsRevoked(): void
{
$response = $this->getResponseContentForGql(
__DIR__ . '/../_graphql/query/CurrentCustomerUserQuery.graphql',
);
$this->assertResponseContainsArrayOfErrors($response);

$this->assertSame('Token is not valid.', $response['errors'][0]['message']);
$this->assertSame('invalid-token', $response['errors'][0]['extensions']['userCode']);
}

/**
* @return array<string, string>
*/
private function getDefaultCredentials(): array
{
return [
'email' => 'no-reply@shopsys.com',
'password' => 'user123',
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
mutation ChangePasswordMutation (
$email: String!
$oldPassword: Password!
$newPassword: Password!
) {
ChangePassword(input: {
email: $email
oldPassword: $oldPassword
newPassword: $newPassword
}) {
firstName
lastName
email
telephone
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
mutation LoginMutation ($email: String!, $password: Password!) {
Login(input: {
email: $email
password: $password
}) {
tokens {
accessToken
refreshToken
}
}
}

0 comments on commit 5699f2d

Please sign in to comment.