Skip to content

Commit

Permalink
Practice 4: Authorization with Cookie
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoineGonzalez committed May 7, 2024
1 parent 7184f98 commit b565614
Show file tree
Hide file tree
Showing 16 changed files with 256 additions and 18 deletions.
9 changes: 9 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,14 @@ services:
tags:
- { name: kernel.event_listener, event: ResponseEvent, method: onKernelResponse }

App\Listener\MercureAuthorizationListener:
tags:
- { name: kernel.event_listener, event: ResponseEvent, method: onKernelResponse }

App\Listener\AuthenticationListener:
tags:
- { name: kernel.event_listener, event: loginSuccessEvent, method: onLoginSuccess }
- { name: kernel.event_listener, event: loginFailureEvent, method: onLoginFailure }

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
63 changes: 63 additions & 0 deletions public/js/activity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const activityContainer = document.querySelector('#activity-container')


const dicoverMercureHub = async () => fetch(window.location)
.then(response => {
const hubUrl = response
.headers
.get('Link')
.match(/<([^>]+)>;\s+rel=(?:mercure|"[^"]*mercure[^"]*")/)[1];

return new URL(hubUrl);
}
)

const addLogItem = (message) => {
const item = document.createElement('li')

item.setAttribute('class', 'alert alert-primary')
item.setAttribute('role', 'alert')

item.innerHTML = message

activityContainer.append(item)
}

document.addEventListener('DOMContentLoaded', async () => {
const hubUrl = await dicoverMercureHub();

hubUrl.searchParams.append('topic', 'http://localhost/activity')
hubUrl.searchParams.append('topic', 'http://localhost/dinosaurs')

const es = new EventSource(hubUrl, { withCredentials: true });

es.addEventListener('login', e => {
const message = JSON.parse(e.data)

addLogItem(message.message)
})

es.addEventListener('logout', e => {
const message = JSON.parse(e.data)

addLogItem(message.message)
})

es.addEventListener('created', e => {
const message = JSON.parse(e.data)

addLogItem(message.message)
})

es.addEventListener('updated', e => {
const message = JSON.parse(e.data)

addLogItem(message.message)
})

es.addEventListener('deleted', e => {
const message = JSON.parse(e.data)

addLogItem(message.message)
})
});
2 changes: 1 addition & 1 deletion public/js/dinosaurs.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ document.addEventListener('DOMContentLoaded', async () => {

hubUrl.searchParams.append('topic', 'http://localhost/dinosaurs')

const es = new EventSource(hubUrl);
const es = new EventSource(hubUrl, { withCredentials: true });

es.addEventListener('created', e => {
const message = JSON.parse(e.data)
Expand Down
18 changes: 18 additions & 0 deletions src/Controller/ActivityController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

final class ActivityController extends AbstractController
{
#[Route('/activity', name: 'app_activity')]
public function activity(): Response
{
return $this->render('activity.html.twig');
}
}
1 change: 0 additions & 1 deletion src/Controller/DinosaursController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mercure\Discovery;
use Symfony\Component\Routing\Annotation\Route;

final class DinosaursController extends AbstractController
Expand Down
36 changes: 36 additions & 0 deletions src/Listener/AuthenticationListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace App\Listener;

use App\Realtime\Trigger\UserLoggetIn;
use App\Realtime\Trigger\UserLoggetOut;
use App\Service\Realtime\Publisher;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
use Symfony\Component\Security\Http\Event\LogoutEvent;

final readonly class AuthenticationListener
{
public function __construct(
private Publisher $publisher
) {
}

#[AsEventListener(event: LoginSuccessEvent::class)]
public function onLoginSuccess(LoginSuccessEvent $event): void
{
$identifier = $event->getAuthenticatedToken()->getUserIdentifier();

$this->publisher->publish(new UserLoggetIn($identifier));
}

#[AsEventListener(event: LogoutEvent::class)]
public function onLogout(LogoutEvent $event): void
{
$identifier = $event->getToken()->getUserIdentifier();

$this->publisher->publish(new UserLoggetOut($identifier));
}
}
41 changes: 41 additions & 0 deletions src/Listener/MercureAuthorizationListener.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

declare(strict_types=1);

namespace App\Listener;

use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpKernel\Event\ResponseEvent;
use Symfony\Component\Mercure\Authorization;

#[AsEventListener(event: ResponseEvent::class)]
final readonly class MercureAuthorizationListener
{
public function __construct(
private Security $security,
private Authorization $authorization
) {
}

public function onKernelResponse(ResponseEvent $event): void
{
$topics = [];

if ($this->security->isGranted('ROLE_USER')) {
$topics[] = 'http://localhost/dinosaurs';
}

if ($this->security->isGranted('ROLE_ADMIN')) {
$topics[] = 'http://localhost/activity';
}

if (empty($topics)) {
return;
}

$request = $event->getRequest();

$this->authorization->setCookie($request, $topics);
}
}
5 changes: 3 additions & 2 deletions src/Realtime/Trigger.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

use JsonSerializable;

abstract class Trigger implements JsonSerializable
abstract readonly class Trigger implements JsonSerializable
{
/**
* @param array<string> $topics
Expand All @@ -15,7 +15,8 @@ abstract class Trigger implements JsonSerializable
public function __construct(
public string $type,
public array $topics,
private array $data
private array $data,
public bool $isPrivate = true
) {
}

Expand Down
2 changes: 1 addition & 1 deletion src/Realtime/Trigger/DinosaurCreated.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use App\Entity\Dinosaur;
use App\Realtime\Trigger;

class DinosaurCreated extends Trigger
final readonly class DinosaurCreated extends Trigger
{
public function __construct(
Dinosaur $dinosaur,
Expand Down
2 changes: 1 addition & 1 deletion src/Realtime/Trigger/DinosaurDeleted.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use App\Entity\Dinosaur;
use App\Realtime\Trigger;

class DinosaurDeleted extends Trigger
final readonly class DinosaurDeleted extends Trigger
{
public function __construct($id, Dinosaur $dinosaur)
{
Expand Down
2 changes: 1 addition & 1 deletion src/Realtime/Trigger/DinosaurUpdated.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use App\Entity\Dinosaur;
use App\Realtime\Trigger;

class DinosaurUpdated extends Trigger
final readonly class DinosaurUpdated extends Trigger
{
public function __construct(
Dinosaur $dinosaur,
Expand Down
22 changes: 22 additions & 0 deletions src/Realtime/Trigger/UserLoggedIn.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace App\Realtime\Trigger;

use App\Realtime\Trigger;

final readonly class UserLoggetIn extends Trigger
{
public function __construct(string $userIdentifier)
{
parent::__construct(
'login',
['http://localhost/activity'],
[
'userIdentifier' => $userIdentifier,
'message' => "The user {$userIdentifier} has been logged in !"
]
);
}
}
22 changes: 22 additions & 0 deletions src/Realtime/Trigger/UserLoggetOut.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace App\Realtime\Trigger;

use App\Realtime\Trigger;

final readonly class UserLoggetOut extends Trigger
{
public function __construct(string $userIdentifier)
{
parent::__construct(
'logout',
['http://localhost/activity'],
[
'userIdentifier' => $userIdentifier,
'message' => "The user {$userIdentifier} has been logged out !"
]
);
}
}
3 changes: 2 additions & 1 deletion src/Service/Realtime/Publisher.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ public function publish(Trigger $trigger): void
$this->hub->publish(new Update(
$trigger->topics,
json_encode($trigger),
type: $trigger->type
type: $trigger->type,
private: $trigger->isPrivate
));
}
}
18 changes: 18 additions & 0 deletions templates/activity.html.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{% extends 'base.html.twig' %}

{% block title %}Activity panel{% endblock %}

{% block javascripts %}
<script src="{{ asset('js/bootstrap.min.js') }}"></script>
<script src="{{ asset('js/activity.js') }}" defer></script>
{% endblock %}

{% block body %}
<main class="w-100 p-3">
<h1>Activity Panel</h1>

<ul id="activity-container">
{# Here will go the realtime activity messages #}
</ul>
</main>
{% endblock %}
28 changes: 18 additions & 10 deletions templates/base.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,16 @@
</li>
{% if 'ROLE_ADMIN' in app.user.roles %}
<li class="nav-item">
<a
class="nav-link{% if app.request.get('_route') == 'app_create_dinosaur' %} active{% endif %}"
<a
class="nav-link{% if app.request.get('_route') == 'app_create_dinosaur' %} active{% endif %}"
href="{{ path('app_create_dinosaur') }}"
>
Create a dinosaur
</a>
</li>
{% endif %}
<li class="nav-item">
<a
<a
class="nav-link{% if app.request.get('_route') == 'app_list_species' %} active{% endif %}"
aria-current="page"
href="{{ path('app_list_species') }}"
Expand All @@ -55,25 +55,33 @@
</li>
{% if 'ROLE_ADMIN' in app.user.roles %}
<li class="nav-item">
<a
class="nav-link{% if app.request.get('_route') == 'app_create_species' %} active{% endif %}"
<a
class="nav-link{% if app.request.get('_route') == 'app_create_species' %} active{% endif %}"
href="{{ path('app_create_species') }}"
>
Create a species
</a>
</li>
{% endif %}
<li class="nav-item">
<a
class="nav-link{% if app.request.get('_route') == 'app_create_species' %} active{% endif %}"
<a
class="nav-link{% if app.request.get('_route') == 'app_create_species' %} active{% endif %}"
href="{{ path('app_activity') }}"
>
Activity
</a>
</li>
<li class="nav-item">
<a
class="nav-link{% if app.request.get('_route') == 'app_create_species' %} active{% endif %}"
href="{{ path('logout') }}"
>
Logout
</a>
</li>
{% else %}
<li class="nav-item">
<a
<a
class="nav-link{% if app.request.get('_route') == 'app_list_species' %} active{% endif %}"
aria-current="page"
href="{{ path('register') }}"
Expand All @@ -82,8 +90,8 @@
</a>
</li>
<li class="nav-item">
<a
class="nav-link{% if app.request.get('_route') == 'app_create_species' %} active{% endif %}"
<a
class="nav-link{% if app.request.get('_route') == 'app_create_species' %} active{% endif %}"
href="{{ path('login') }}"
>
Login
Expand Down

0 comments on commit b565614

Please sign in to comment.