Skip to content

Commit 36212bb

Browse files
committed
Fix: logout
1 parent 1e18e5f commit 36212bb

File tree

7 files changed

+90
-70
lines changed

7 files changed

+90
-70
lines changed

assets/vue/components/subscribers/SubscriberDirectory.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,15 @@ const fetchSubscribers = async (afterId = null) => {
161161
162162
try {
163163
const response = await fetch(url, {
164-
headers: { Accept: 'application/json' }
164+
headers: { Accept: 'application/json', 'X-Requested-With': 'XMLHttpRequest' }
165165
})
166166
167+
if (response.status === 401) {
168+
const data = await response.json()
169+
window.location.href = data.redirect
170+
return
171+
}
172+
167173
const data = await response.json()
168174
169175
subscribers.value = data.items

assets/vue/layouts/AdminLayout.vue

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,13 @@
2626
Create Campaign
2727
</button>
2828

29-
<div class="h-8 w-px bg-slate-200 hidden sm:block mx-1"></div>
30-
31-
<button class="relative p-2 text-slate-400 hover:text-slate-600 transition-colors">
32-
<BaseIcon name="notification" />
33-
<span class="absolute top-1.5 right-1.5 w-2 h-2 bg-rose-500 rounded-full border-2 border-white"></span>
34-
</button>
35-
3629
<!-- User dropdown -->
3730
<div class="flex items-center gap-3 pl-2 group cursor-pointer">
3831
<div class="flex flex-col items-end hidden sm:flex">
3932
<span class="text-sm font-bold text-slate-800 leading-none">Admin User</span>
4033
<span class="text-[10px] text-slate-500 mt-0.5">Administrator</span>
4134
</div>
4235

43-
<div class="w-9 h-9 bg-slate-100 rounded-full flex items-center justify-center text-slate-400 border border-slate-200 overflow-hidden">
44-
<img
45-
alt="User Avatar"
46-
class="w-full h-full object-cover"
47-
src="https://images.unsplash.com/photo-1535713875002-d1d0cf377fde?q=80&amp;w=100&amp;h=100&amp;auto=format&amp;fit=crop"
48-
>
49-
</div>
50-
5136
<BaseIcon name="chevronDown" />
5237
</div>
5338
</div>

assets/vue/views/DashboardView.vue

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ import AdminLayout from '../layouts/AdminLayout.vue'
2929
import KpiGrid from '../components/dashboard/KpiGrid.vue'
3030
import PerformanceChartCard from '../components/dashboard/PerformanceChartCard.vue'
3131
import RecentCampaignsCard from '../components/dashboard/RecentCampaignsCard.vue'
32-
import BaseIcon from '../components/base/BaseIcon.vue'
33-
import avatar from '@images/avatar.jpg'
3432
3533
const chart = {
3634
labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],

src/Controller/AuthController.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public function login(Request $request): Response
4949
$authData = $this->apiClient->login($username, $password);
5050
$request->getSession()->set('auth_token', $authData['key']);
5151
$request->getSession()->set('auth_expiry_date', $authData['key']);
52+
$request->getSession()->set('auth_id', $authData['id']);
5253

5354
return $this->redirectToRoute('home');
5455
} catch (Exception $e) {
@@ -67,6 +68,8 @@ public function login(Request $request): Response
6768
public function logout(Request $request): Response
6869
{
6970
$request->getSession()->remove('auth_token');
71+
$request->getSession()->remove('auth_id');
72+
$this->apiClient->logout();
7073

7174
return $this->redirectToRoute('login');
7275
}

src/EventListener/ApiSessionListener.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,12 @@ public function onKernelRequest(RequestEvent $event): void
3737
$authToken = $session->get('auth_token');
3838

3939
if ($authToken) {
40-
$this->apiClient->setSessionId($authToken);
40+
$this->apiClient->setSessionId((string) $authToken);
41+
}
42+
43+
$authId = $session->get('auth_id');
44+
if ($authId) {
45+
$this->apiClient->setId((int) $authId);
4146
}
4247
}
4348
}

src/EventSubscriber/UnauthorizedSubscriber.php

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,20 @@
55
namespace PhpList\WebFrontend\EventSubscriber;
66

77
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
8+
use Symfony\Component\HttpFoundation\JsonResponse;
89
use Symfony\Component\HttpFoundation\RedirectResponse;
10+
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
911
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
1012
use Symfony\Component\HttpKernel\KernelEvents;
1113
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
12-
use GuzzleHttp\Exception\ClientException;
1314
use PhpList\RestApiClient\Exception\AuthenticationException;
1415

1516
class UnauthorizedSubscriber implements EventSubscriberInterface
1617
{
17-
private UrlGeneratorInterface $urlGenerator;
18-
19-
public function __construct(UrlGeneratorInterface $urlGenerator)
20-
{
21-
$this->urlGenerator = $urlGenerator;
18+
public function __construct(
19+
private readonly UrlGeneratorInterface $urlGenerator,
20+
private readonly FlashBagInterface $flashBag,
21+
) {
2222
}
2323

2424
public static function getSubscribedEvents(): array
@@ -32,22 +32,31 @@ public function onKernelException(ExceptionEvent $event): void
3232
{
3333
$exception = $event->getThrowable();
3434

35-
if (($exception instanceof ClientException && $exception->getCode() === 401) ||
36-
$exception instanceof AuthenticationException
37-
) {
35+
if ($exception instanceof AuthenticationException) {
3836
$request = $event->getRequest();
39-
$session = $request->getSession();
4037

41-
if ($session->has('auth_token')) {
42-
$session->remove('auth_token');
38+
if ($request->hasSession()) {
39+
$session = $request->getSession();
40+
$session->invalidate();
4341
}
4442

45-
$session->set('login_error', 'Your session has expired. Please log in again.');
46-
4743
$loginUrl = $this->urlGenerator->generate('login');
48-
$response = new RedirectResponse($loginUrl);
4944

50-
$event->setResponse($response);
45+
if ($request->isXmlHttpRequest()) {
46+
$event->setResponse(new JsonResponse([
47+
'error' => 'session_expired',
48+
'message' => 'Your session has expired. Please log in again.',
49+
'redirect' => $loginUrl,
50+
], 401));
51+
52+
return;
53+
}
54+
55+
if ($request->hasSession()) {
56+
$this->flashBag->add('error', 'Your session has expired. Please log in again.');
57+
}
58+
59+
$event->setResponse(new RedirectResponse($loginUrl));
5160
}
5261
}
5362
}

tests/Unit/EventSubscriber/UnauthorizedSubscriberTest.php

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
namespace PhpList\WebFrontend\Tests\Unit\EventSubscriber;
66

77
use Exception;
8-
use GuzzleHttp\Exception\ClientException;
9-
use GuzzleHttp\Psr7\Request as GuzzleRequest;
10-
use GuzzleHttp\Psr7\Response as GuzzleResponse;
8+
use PhpList\RestApiClient\Exception\AuthenticationException;
119
use PhpList\WebFrontend\EventSubscriber\UnauthorizedSubscriber;
1210
use PHPUnit\Framework\MockObject\MockObject;
1311
use PHPUnit\Framework\TestCase;
12+
use Symfony\Component\HttpFoundation\JsonResponse;
1413
use Symfony\Component\HttpFoundation\RedirectResponse;
1514
use Symfony\Component\HttpFoundation\Request;
15+
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
1616
use Symfony\Component\HttpFoundation\Session\SessionInterface;
1717
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
1818
use Symfony\Component\HttpKernel\HttpKernelInterface;
@@ -23,11 +23,13 @@ class UnauthorizedSubscriberTest extends TestCase
2323
{
2424
private UnauthorizedSubscriber $subscriber;
2525
private UrlGeneratorInterface&MockObject $urlGenerator;
26+
private FlashBagInterface&MockObject $flashBag;
2627

2728
protected function setUp(): void
2829
{
2930
$this->urlGenerator = $this->createMock(UrlGeneratorInterface::class);
30-
$this->subscriber = new UnauthorizedSubscriber($this->urlGenerator);
31+
$this->flashBag = $this->createMock(FlashBagInterface::class);
32+
$this->subscriber = new UnauthorizedSubscriber($this->urlGenerator, $this->flashBag);
3133
}
3234

3335
public function testGetSubscribedEvents(): void
@@ -40,33 +42,25 @@ public function testGetSubscribedEvents(): void
4042

4143
public function testOnKernelExceptionWithUnauthorizedException(): void
4244
{
43-
$guzzleRequest = new GuzzleRequest('GET', 'http://example.com');
44-
$guzzleResponse = new GuzzleResponse(401);
45-
$clientException = new ClientException('Unauthorized', $guzzleRequest, $guzzleResponse);
45+
$authException = new AuthenticationException('Unauthorized');
4646

4747
$session = $this->createMock(SessionInterface::class);
48-
$session->expects($this->once())
49-
->method('has')
50-
->with('auth_token')
51-
->willReturn(true);
48+
$session->expects($this->once())->method('invalidate');
5249

53-
$session->expects($this->once())
54-
->method('remove')
55-
->with('auth_token');
56-
57-
$session->expects($this->once())
58-
->method('set')
59-
->with('login_error', 'Your session has expired. Please log in again.');
50+
$this->flashBag->expects($this->once())
51+
->method('add')
52+
->with('error', 'Your session has expired. Please log in again.');
6053

6154
$request = $this->createMock(Request::class);
55+
$request->method('hasSession')->willReturn(true);
6256
$request->method('getSession')->willReturn($session);
6357

6458
$kernel = $this->createMock(HttpKernelInterface::class);
6559
$event = new ExceptionEvent(
6660
$kernel,
6761
$request,
6862
HttpKernelInterface::MAIN_REQUEST,
69-
$clientException
63+
$authException
7064
);
7165

7266
$loginUrl = '/login';
@@ -100,35 +94,55 @@ public function testOnKernelExceptionWithOtherException(): void
10094
$this->assertNull($event->getResponse());
10195
}
10296

103-
public function testOnKernelExceptionWithNonAuthTokenSession(): void
97+
public function testOnKernelExceptionWithXmlHttpRequest(): void
10498
{
105-
$guzzleRequest = new GuzzleRequest('GET', 'http://example.com');
106-
$guzzleResponse = new GuzzleResponse(401);
107-
$clientException = new ClientException('Unauthorized', $guzzleRequest, $guzzleResponse);
99+
$authException = new AuthenticationException('Unauthorized');
108100

109101
$session = $this->createMock(SessionInterface::class);
110-
$session->expects($this->once())
111-
->method('has')
112-
->with('auth_token')
113-
->willReturn(false);
102+
$session->expects($this->once())->method('invalidate');
114103

115-
$session->expects($this->never())
116-
->method('remove')
117-
->with('auth_token');
104+
$request = $this->createMock(Request::class);
105+
$request->method('hasSession')->willReturn(true);
106+
$request->method('getSession')->willReturn($session);
107+
$request->method('isXmlHttpRequest')->willReturn(true);
118108

119-
$session->expects($this->once())
120-
->method('set')
121-
->with('login_error', 'Your session has expired. Please log in again.');
109+
$kernel = $this->createMock(HttpKernelInterface::class);
110+
$event = new ExceptionEvent(
111+
$kernel,
112+
$request,
113+
HttpKernelInterface::MAIN_REQUEST,
114+
$authException
115+
);
116+
117+
$loginUrl = '/login';
118+
$this->urlGenerator->method('generate')
119+
->with('login')
120+
->willReturn($loginUrl);
121+
122+
$this->subscriber->onKernelException($event);
123+
124+
$response = $event->getResponse();
125+
$this->assertInstanceOf(JsonResponse::class, $response);
126+
$this->assertEquals(401, $response->getStatusCode());
127+
128+
$data = json_decode($response->getContent(), true);
129+
$this->assertEquals('session_expired', $data['error']);
130+
$this->assertEquals($loginUrl, $data['redirect']);
131+
}
132+
133+
public function testOnKernelExceptionWithoutSession(): void
134+
{
135+
$authException = new AuthenticationException('Unauthorized');
122136

123137
$request = $this->createMock(Request::class);
124-
$request->method('getSession')->willReturn($session);
138+
$request->method('hasSession')->willReturn(false);
125139

126140
$kernel = $this->createMock(HttpKernelInterface::class);
127141
$event = new ExceptionEvent(
128142
$kernel,
129143
$request,
130144
HttpKernelInterface::MAIN_REQUEST,
131-
$clientException
145+
$authException
132146
);
133147

134148
$loginUrl = '/login';

0 commit comments

Comments
 (0)