feat(ApiSubscriber): Améliore la gestion des erreurs et des headers pour l'API privée.

This commit is contained in:
Serreau Jovann
2025-11-11 14:44:56 +01:00
parent b8b4dc62b8
commit d0f49c96c8

View File

@@ -1,6 +1,7 @@
<?php
namespace App\EventListener;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
@@ -8,67 +9,93 @@ use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
#[AsEventListener(event: KernelEvents::REQUEST)]
#[AsEventListener(event: KernelEvents::EXCEPTION)]
// Use '#[AsEventListener]' on the class for both events for cleaner annotation structure
#[AsEventListener(event: KernelEvents::REQUEST, method: 'onKernelRequest', priority: 10)]
#[AsEventListener(event: KernelEvents::EXCEPTION, method: 'onKernelException', priority: 10)]
class ApiSubscriber
{
public function onKernelException(ExceptionEvent $event)
// Define the API prefix once for easier maintenance
private const API_PRIVATE_PREFIX = '/api/private';
/**
* Handles exceptions specifically for the private API routes.
*/
public function onKernelException(ExceptionEvent $event): void
{
$request = $event->getRequest();
$pathInfo = $request->getPathInfo();
if (str_contains($pathInfo, '/api/private')) {
if($event->getThrowable() instanceof NotFoundHttpException) {
$event->setResponse(new JsonResponse([
'state' => 'error',
'message' => 'Route `'.$request->getPathInfo().'` not exist'.'`'
]));
$event->stopPropagation();
return;
}
// 1. Quick exit if the route is not part of the private API
if (!str_starts_with($request->getPathInfo(), self::API_PRIVATE_PREFIX)) {
return;
}
$throwable = $event->getThrowable();
// 2. Handle only NotFoundHttpException
if ($throwable instanceof NotFoundHttpException) {
// Set error response
$response = new JsonResponse([
'state' => 'error',
'message' => sprintf('Route `%s` not exist', $request->getPathInfo()),
]);
$event->setResponse($response);
$event->stopPropagation();
}
}
/**
* Checks for required headers on private API requests.
*/
public function onKernelRequest(RequestEvent $event): void
{
// 1. Quick exit if the request is not a master request or not part of the private API
if (!$event->isMainRequest()) {
return;
}
$request = $event->getRequest();
$pathInfo = $request->getPathInfo();
if (str_contains($pathInfo, '/api/private')) {
if(!$request->headers->has('EsyWebKey')) {
$event->setResponse(new JsonResponse([
'state' => 'error',
'message' => 'Missing Header `EsyWebKey`'
]));
$event->stopPropagation();
if (!str_starts_with($pathInfo, self::API_PRIVATE_PREFIX)) {
return;
}
// 2. Define required headers and their respective error messages
$requiredHeaders = [
'EsyWebKey' => 'Missing Header `EsyWebKey`',
'EsyWebDns' => 'Missing Header `EsyWebDns`',
'EsyWebApiKey' => 'Missing Header `EsyWebApiKey`',
];
// 3. Loop through required headers and check for existence (removes deep nesting)
foreach ($requiredHeaders as $headerName => $errorMessage) {
if (!$request->headers->has($headerName)) {
$this->setAccessDeniedResponse($event, $errorMessage);
return;
} else {
if($request->headers->get('EsyWebKey') != $_ENV['APP_SECRET']) {
$event->setResponse(new JsonResponse([
'state' => 'error',
'message' => 'Header `EsyWebKey` Is Invalid'
]));
$event->stopPropagation();
return;
} else {
if(!$request->headers->has('EsyWebDns')) {
$event->setResponse(new JsonResponse([
'state' => 'error',
'message' => 'Missing Header `EsyWebDns`'
]));
$event->stopPropagation();
return;
} else {
if(!$request->headers->has('EsyWebApiKey')) {
$event->setResponse(new JsonResponse([
'state' => 'error',
'message' => 'Missing Header `EsyWebApiKey`'
]));
$event->stopPropagation();
return;
}
}
}
}
}
// 4. Validate the 'EsyWebKey' against the environment variable
// Use environment variables via Symfony's container/services, but for a quick fix,
// using $_ENV is kept from the original code (better to inject as a service parameter).
if ($request->headers->get('EsyWebKey') !== ($_ENV['APP_SECRET'] ?? '')) {
$this->setAccessDeniedResponse($event, 'Header `EsyWebKey` Is Invalid');
return;
}
}
/**
* Helper method to set a standardized JSON error response.
*/
private function setAccessDeniedResponse(RequestEvent $event, string $message): void
{
$response = new JsonResponse([
'state' => 'error',
'message' => $message,
], JsonResponse::HTTP_UNAUTHORIZED); // Use proper HTTP status code for unauthorized access
$event->setResponse($response);
$event->stopPropagation();
}
}