76 lines
2.6 KiB
PHP
76 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire;
|
|
|
|
use App\Models\Server;
|
|
use Livewire\Component;
|
|
|
|
/**
|
|
* Sidebar fleet switcher. Lives in the persistent layout, so it survives
|
|
* wire:navigate. Selecting a server stores it in the session and reloads the
|
|
* current page so every panel reflects the new active server.
|
|
*/
|
|
class ServerSwitcher extends Component
|
|
{
|
|
public function select(string $uuid): void
|
|
{
|
|
$server = Server::where('uuid', $uuid)->firstOrFail();
|
|
|
|
session(['active_server_id' => $server->id]);
|
|
|
|
$this->redirect($this->safeReturnUrl(), navigate: true);
|
|
}
|
|
|
|
/**
|
|
* Return a SAME-ORIGIN relative target derived from the Referer, or the
|
|
* dashboard. An attacker-supplied Referer must never turn a server switch
|
|
* into an open redirect, so we:
|
|
* 1. reject any Referer whose host differs or whose scheme is not http(s)
|
|
* (blocks javascript:/data: and off-site hosts), then
|
|
* 2. keep ONLY the path + query and redirect to that rooted relative URL —
|
|
* so the result can never carry a foreign scheme, host, or port (e.g.
|
|
* `https://host:444/x` collapses to `/x` on the current origin), and
|
|
* 3. reject protocol-relative (`//host`, `/\host`) and non-rooted values.
|
|
*/
|
|
private function safeReturnUrl(): string
|
|
{
|
|
$referer = request()->header('Referer');
|
|
$fallback = route('dashboard');
|
|
|
|
if (! is_string($referer) || $referer === '') {
|
|
return $fallback;
|
|
}
|
|
|
|
$parts = parse_url($referer);
|
|
if ($parts === false
|
|
|| ! isset($parts['host'])
|
|
|| strtolower($parts['host']) !== strtolower(request()->getHost())
|
|
|| (isset($parts['scheme']) && ! in_array(strtolower($parts['scheme']), ['http', 'https'], true))) {
|
|
return $fallback;
|
|
}
|
|
|
|
$target = ($parts['path'] ?? '/').(isset($parts['query']) ? '?'.$parts['query'] : '');
|
|
|
|
// Must be a rooted, host-relative path; reject protocol-relative tricks.
|
|
if ($target === '' || $target[0] !== '/'
|
|
|| (isset($target[1]) && ($target[1] === '/' || $target[1] === '\\'))) {
|
|
return $fallback;
|
|
}
|
|
|
|
return $target;
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
$servers = Server::orderBy('name')->get();
|
|
$active = $servers->firstWhere('id', session('active_server_id'))
|
|
?? $servers->firstWhere('status', 'online')
|
|
?? $servers->first();
|
|
|
|
return view('livewire.server-switcher', [
|
|
'servers' => $servers,
|
|
'active' => $active,
|
|
]);
|
|
}
|
|
}
|