clusev/app/Livewire/ServerSwitcher.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,
]);
}
}