✨ feat(ApiSubscriber): Améliore la gestion des erreurs et des headers pour l'API privée.
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user