98dacef556
PHP Tests / php-tests (push) Has been cancelled
Staging terminates SSL at a reverse proxy in front of the app, but Laravel had no trustProxies() configured, so it never saw the request as HTTPS and generated http:// asset URLs on the https:// page. Browsers block that as mixed content, which silently broke every JS-enhanced Filament field (FileUpload, Textarea, etc.) — e.g. the Ev Company logo field falling back to a bare native file input. - bootstrap/app.php: trust the proxy via X-Forwarded-* headers. - AppServiceProvider: force the https scheme when APP_URL is https, as a fallback in case the forwarded header is ever missing.
112 lines
5.2 KiB
PHP
112 lines
5.2 KiB
PHP
<?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\AuthenticateSanctumOrFastApiJwt;
|
|
use Modules\Identity\Http\Middleware\EnsureFastApiAgent;
|
|
use Symfony\Component\HttpFoundation\Request as SymfonyRequest;
|
|
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(
|
|
web: __DIR__.'/../routes/web.php',
|
|
api: __DIR__.'/../routes/api.php',
|
|
commands: __DIR__.'/../routes/console.php',
|
|
health: '/up',
|
|
)
|
|
->withMiddleware(function (Middleware $middleware): void {
|
|
// Staging/production sit behind a reverse proxy/load balancer that
|
|
// terminates SSL — without this, Laravel never sees the original
|
|
// request as HTTPS, so it generates http:// asset URLs, which
|
|
// browsers then block as mixed content on the https:// page (e.g.
|
|
// Filament's file-upload.js failing to load, breaking that field's
|
|
// JS-enhanced dropzone). Trusting '*' is the standard Laravel
|
|
// pattern when the proxy's IP isn't fixed/known in advance.
|
|
$middleware->trustProxies(
|
|
at: '*',
|
|
headers: SymfonyRequest::HEADER_X_FORWARDED_FOR
|
|
| SymfonyRequest::HEADER_X_FORWARDED_HOST
|
|
| SymfonyRequest::HEADER_X_FORWARDED_PORT
|
|
| SymfonyRequest::HEADER_X_FORWARDED_PROTO
|
|
| SymfonyRequest::HEADER_X_FORWARDED_AWS_ELB,
|
|
);
|
|
|
|
$middleware->alias([
|
|
'fastapi.agent' => EnsureFastApiAgent::class,
|
|
'api.auth' => AuthenticateSanctumOrFastApiJwt::class,
|
|
]);
|
|
})
|
|
->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();
|