74 lines
3.8 KiB
PHP
74 lines
3.8 KiB
PHP
<?php
|
|
|
|
use App\Http\Middleware\BlockBannedIp;
|
|
use App\Http\Middleware\PanelScheme;
|
|
use App\Http\Middleware\SecurityHeaders;
|
|
use App\Http\Middleware\SetLocale;
|
|
use Illuminate\Foundation\Application;
|
|
use Illuminate\Foundation\Configuration\Exceptions;
|
|
use Illuminate\Foundation\Configuration\Middleware;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Session\Middleware\AuthenticateSession;
|
|
|
|
return Application::configure(basePath: dirname(__DIR__))
|
|
->withRouting(
|
|
web: __DIR__.'/../routes/web.php',
|
|
commands: __DIR__.'/../routes/console.php',
|
|
health: '/up',
|
|
)
|
|
// Registers the `/broadcasting/auth` endpoint (under the `web` group, so PanelScheme
|
|
// guards it) and the private-channel authorization callbacks in routes/channels.php.
|
|
// Without this, private/presence channels can never be subscribed to. See R-note in
|
|
// routes/channels.php: every broadcast channel is PRIVATE (authenticated) — metrics are
|
|
// fleet infrastructure data and must not be reachable on a stale Caddy host via Reverb.
|
|
->withBroadcasting(__DIR__.'/../routes/channels.php')
|
|
->withMiddleware(function (Middleware $middleware): void {
|
|
// Trust Caddy's X-Forwarded-* ONLY in production, where the app has no host port
|
|
// (docker-compose.prod.yml exposes app:80 on the internal net only) so Caddy is
|
|
// the sole peer. In dev the app port is published on the host, so trusting forged
|
|
// proxy headers would let a LAN peer fake HTTPS (forcing a Secure cookie over
|
|
// plain HTTP and breaking the bare-IP recovery login).
|
|
if (($_SERVER['APP_ENV'] ?? getenv('APP_ENV') ?: 'local') === 'production') {
|
|
$middleware->trustProxies(at: '*');
|
|
}
|
|
|
|
// PanelScheme first (scheme/host enforcement before auth), so an HTTP request to
|
|
// the active domain is sent straight to HTTPS and a wrong host is refused before
|
|
// any auth redirect. AuthenticateSession ties each request to the password hash that
|
|
// was current when the session started, so a password change (or a remember-token
|
|
// rotation in the session-management actions) invalidates every OTHER session — the
|
|
// database session driver makes those sessions enumerable/revocable in Settings.
|
|
$middleware->web(
|
|
prepend: [PanelScheme::class],
|
|
append: [SetLocale::class, SecurityHeaders::class, AuthenticateSession::class, BlockBannedIp::class],
|
|
);
|
|
|
|
// The terminal sidecar POSTs to the internal resolve endpoint from the private network with
|
|
// no browser session/CSRF token — it is guarded by the shared-secret header instead.
|
|
$middleware->validateCsrfTokens(except: ['_internal/terminal/*']);
|
|
})
|
|
->withExceptions(function (Exceptions $exceptions): void {
|
|
$exceptions->shouldRenderJsonWhen(
|
|
fn (Request $request) => $request->is('api/*'),
|
|
);
|
|
|
|
// Errors thrown BEFORE the web-group SetLocale middleware (CSRF 419, an
|
|
// unmatched-route 404, maintenance 503) would render the custom error page in
|
|
// the default locale. Resolve the user's language first so those pages honour
|
|
// it too (R16); returning null falls through to the normal renderer (the
|
|
// resources/views/errors/* views).
|
|
$exceptions->render(function (Throwable $e, Request $request) {
|
|
// Best-effort: locale resolution touches the session/user, which can be the
|
|
// very thing that failed (e.g. a DB outage). Swallow any error so this hook
|
|
// can never throw a second exception and break the custom error page — the
|
|
// page just renders in the default locale instead.
|
|
try {
|
|
SetLocale::apply($request);
|
|
} catch (Throwable) {
|
|
// keep the default locale
|
|
}
|
|
|
|
return null;
|
|
});
|
|
})->create();
|