Phase 6: security & ops hardening (T6.1-T6.5)
- T6.1: named rate limiters (api-read/api-write/api-auth/api-webhooks), applied per module route group with tighter limits on booking/payment writes and the KBZ webhook than read-only catalog/routing endpoints. - T6.2: install spatie/laravel-activitylog; LogsActivity on Booking/ Payment/Refund status transitions and catalog/pricing admin CRUD (EvCompany, Destination, DepartureTimeSlot, EvRoute, RoutePricing). New IdentityPlugin with a read-only AuditLogResource gated by view_audit_log. - T6.3: JSON error envelope for api/* in bootstrap/app.php (401/403/404/ 405/429/500 fallback), plus PaymentGatewayException (422 declined / 502 unavailable). - T6.4: feature tests proving the FastAPI agent token gets 403 on refund/cancel-not-owned and 405 (no write handler) on catalog/routing writes. - T6.5: install gboquizosanchez/filament-log-viewer with a custom Filament admin theme (required for its views' Tailwind classes to compile), LOG_CHANNEL/FILAMENT_LOG_VIEWER_DRIVER=daily, registered under Operations in the sidebar. 252 tests passing.
This commit is contained in:
+71
-1
@@ -1,9 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Auth\AuthenticationException;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
use Illuminate\Http\Exceptions\HttpResponseException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
use Modules\Identity\Http\Middleware\EnsureFastApiAgent;
|
||||
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException;
|
||||
|
||||
return Application::configure(basePath: dirname(__DIR__))
|
||||
->withRouting(
|
||||
@@ -18,5 +28,65 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
]);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
//
|
||||
// api/* always gets a JSON error envelope regardless of the
|
||||
// client's Accept header (T6.3) — module exceptions that define
|
||||
// their own render() (app-modules/*/src/Exceptions) still win,
|
||||
// since Laravel checks those before these fallback callbacks.
|
||||
$exceptions->shouldRenderJsonWhen(fn (Request $request, Throwable $e) => $request->is('api/*') || $request->expectsJson());
|
||||
|
||||
$exceptions->render(function (AuthenticationException $e, Request $request) {
|
||||
if ($request->is('api/*')) {
|
||||
return response()->json(['message' => 'Unauthenticated.'], 401);
|
||||
}
|
||||
});
|
||||
|
||||
$exceptions->render(function (AuthorizationException $e, Request $request) {
|
||||
if ($request->is('api/*')) {
|
||||
return response()->json(['message' => $e->getMessage() ?: 'This action is unauthorized.'], 403);
|
||||
}
|
||||
});
|
||||
|
||||
$exceptions->render(function (ModelNotFoundException $e, Request $request) {
|
||||
if ($request->is('api/*')) {
|
||||
return response()->json(['message' => 'The requested resource was not found.'], 404);
|
||||
}
|
||||
});
|
||||
|
||||
$exceptions->render(function (NotFoundHttpException $e, Request $request) {
|
||||
if ($request->is('api/*')) {
|
||||
return response()->json(['message' => 'The requested resource was not found.'], 404);
|
||||
}
|
||||
});
|
||||
|
||||
$exceptions->render(function (MethodNotAllowedHttpException $e, Request $request) {
|
||||
if ($request->is('api/*')) {
|
||||
return response()->json(['message' => 'This method is not allowed for the requested route.'], 405);
|
||||
}
|
||||
});
|
||||
|
||||
$exceptions->render(function (TooManyRequestsHttpException $e, Request $request) {
|
||||
if ($request->is('api/*')) {
|
||||
return response()->json(['message' => 'Too many requests.'], 429);
|
||||
}
|
||||
});
|
||||
|
||||
// Last-resort fallback: anything reaching here on api/* is an
|
||||
// exception with no render() of its own and no more specific
|
||||
// handler above — never let it leak a raw trace or fall through to
|
||||
// a bare, unenveloped 500 (T6.3). ValidationException/
|
||||
// HttpResponseException are excluded — Laravel's default handling
|
||||
// of those (after renderable callbacks run) already produces the
|
||||
// right JSON envelope, this fallback would only get in the way.
|
||||
$exceptions->render(function (Throwable $e, Request $request) {
|
||||
if (! $request->is('api/*')
|
||||
|| $e instanceof HttpExceptionInterface
|
||||
|| $e instanceof ValidationException
|
||||
|| $e instanceof HttpResponseException) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'message' => app()->hasDebugModeEnabled() ? $e->getMessage() : 'Server Error',
|
||||
], 500);
|
||||
});
|
||||
})->create();
|
||||
|
||||
Reference in New Issue
Block a user