fix(auth): enforce authorization checks across API and Livewire components

- Add authorization checks to API controller endpoints (view, create, update, delete)
- Wrap Livewire component methods with try-catch for consistent error handling
- Add AuthorizesRequests trait to components requiring authorization checks
- Ensure all sensitive operations verify user permissions before execution
- Implement unified error handling with handleError() helper function
This commit is contained in:
Andras Bacsai committed 2026-02-25 14:20:29 +01:00
1 parent 12f8f80eb1
commit 86b05b902a
84 files changed
+4046 -1055

No files matched your search

+11 -7
View File
@@ -78,14 +78,18 @@ class ByHetzner extends Component
public function mount()
{
$this->authorize('viewAny', CloudProviderToken::class);
$this->loadTokens();
$this->loadSavedCloudInitScripts();
$this->server_name = generate_random_name();
$this->private_keys = PrivateKey::ownedAndOnlySShKeys()->where('id', '!=', 0)->get();
try {
$this->authorize('viewAny', CloudProviderToken::class);
$this->loadTokens();
$this->loadSavedCloudInitScripts();
$this->server_name = generate_random_name();
$this->private_keys = PrivateKey::ownedAndOnlySShKeys()->where('id', '!=', 0)->get();
if ($this->private_keys->count() > 0) {
$this->private_key_id = $this->private_keys->first()->id;
if ($this->private_keys->count() > 0) {
$this->private_key_id = $this->private_keys->first()->id;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
+8 -4
View File
@@ -96,11 +96,15 @@ class Proxy extends Component
public function changeProxy()
{
$this->authorize('update', $this->server);
$this->server->proxy = null;
$this->server->save();
try {
$this->authorize('update', $this->server);
$this->server->proxy = null;
$this->server->save();
$this->dispatch('reloadWindow');
$this->dispatch('reloadWindow');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function selectProxy($proxy_type)
@@ -22,34 +22,38 @@ class DynamicConfigurationNavbar extends Component
public function delete(string $fileName)
{
$this->authorize('update', $this->server);
$proxy_path = $this->server->proxyPath();
$proxy_type = $this->server->proxyType();
try {
$this->authorize('update', $this->server);
$proxy_path = $this->server->proxyPath();
$proxy_type = $this->server->proxyType();
// Decode filename: pipes are used to encode dots for Livewire property binding
// (e.g., 'my|service.yaml' -> 'my.service.yaml')
// This must happen BEFORE validation because validateShellSafePath() correctly
// rejects pipe characters as dangerous shell metacharacters
$file = str_replace('|', '.', $fileName);
// Decode filename: pipes are used to encode dots for Livewire property binding
// (e.g., 'my|service.yaml' -> 'my.service.yaml')
// This must happen BEFORE validation because validateShellSafePath() correctly
// rejects pipe characters as dangerous shell metacharacters
$file = str_replace('|', '.', $fileName);
// Validate filename to prevent command injection
validateShellSafePath($file, 'proxy configuration filename');
// Validate filename to prevent command injection
validateShellSafePath($file, 'proxy configuration filename');
if ($proxy_type === 'CADDY' && $file === 'Caddyfile') {
$this->dispatch('error', 'Cannot delete Caddyfile.');
if ($proxy_type === 'CADDY' && $file === 'Caddyfile') {
$this->dispatch('error', 'Cannot delete Caddyfile.');
return;
return;
}
$fullPath = "{$proxy_path}/dynamic/{$file}";
$escapedPath = escapeshellarg($fullPath);
instant_remote_process(["rm -f {$escapedPath}"], $this->server);
if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy();
}
$this->dispatch('success', 'File deleted.');
$this->dispatch('loadDynamicConfigurations');
$this->dispatch('refresh');
} catch (\Throwable $e) {
return handleError($e, $this);
}
$fullPath = "{$proxy_path}/dynamic/{$file}";
$escapedPath = escapeshellarg($fullPath);
instant_remote_process(["rm -f {$escapedPath}"], $this->server);
if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy();
}
$this->dispatch('success', 'File deleted.');
$this->dispatch('loadDynamicConfigurations');
$this->dispatch('refresh');
}
public function render()
+24 -9
View File
@@ -29,23 +29,38 @@ class Resources extends Component
public function startUnmanaged($id)
{
$this->server->startUnmanaged($id);
$this->dispatch('success', 'Container started.');
$this->loadUnmanagedContainers();
try {
$this->authorize('update', $this->server);
$this->server->startUnmanaged($id);
$this->dispatch('success', 'Container started.');
$this->loadUnmanagedContainers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function restartUnmanaged($id)
{
$this->server->restartUnmanaged($id);
$this->dispatch('success', 'Container restarted.');
$this->loadUnmanagedContainers();
try {
$this->authorize('update', $this->server);
$this->server->restartUnmanaged($id);
$this->dispatch('success', 'Container restarted.');
$this->loadUnmanagedContainers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function stopUnmanaged($id)
{
$this->server->stopUnmanaged($id);
$this->dispatch('success', 'Container stopped.');
$this->loadUnmanagedContainers();
try {
$this->authorize('update', $this->server);
$this->server->stopUnmanaged($id);
$this->dispatch('success', 'Container stopped.');
$this->loadUnmanagedContainers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function refreshStatus()
+14 -10
View File
@@ -41,7 +41,11 @@ class Patches extends Component
{
$this->parameters = get_route_parameters();
$this->server = Server::ownedByCurrentTeam()->whereUuid($this->parameters['server_uuid'])->firstOrFail();
$this->authorize('viewSecurity', $this->server);
try {
$this->authorize('viewSecurity', $this->server);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function checkForUpdatesDispatch()
@@ -69,14 +73,14 @@ class Patches extends Component
public function updateAllPackages()
{
$this->authorize('update', $this->server);
if (! $this->packageManager || ! $this->osId) {
$this->dispatch('error', message: 'Run "Check for updates" first.');
return;
}
try {
$this->authorize('update', $this->server);
if (! $this->packageManager || ! $this->osId) {
$this->dispatch('error', message: 'Run "Check for updates" first.');
return;
}
$activity = UpdatePackage::run(
server: $this->server,
packageManager: $this->packageManager,
@@ -84,8 +88,8 @@ class Patches extends Component
all: true
);
$this->dispatch('activityMonitor', $activity->id, ServerPackageUpdated::class);
} catch (\Exception $e) {
$this->dispatch('error', message: $e->getMessage());
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
+15 -11
View File
@@ -286,18 +286,22 @@ class Show extends Component
public function checkLocalhostConnection()
{
$this->syncData(true);
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = $this->isReachable = true;
$this->server->settings->is_usable = $this->isUsable = true;
$this->server->settings->save();
ServerReachabilityChanged::dispatch($this->server);
} else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error);
try {
$this->syncData(true);
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = $this->isReachable = true;
$this->server->settings->is_usable = $this->isUsable = true;
$this->server->settings->save();
ServerReachabilityChanged::dispatch($this->server);
} else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error);
return;
return;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
+27 -19
View File
@@ -72,31 +72,39 @@ class ValidateAndInstall extends Component
public function retry()
{
$this->authorize('update', $this->server);
$this->uptime = null;
$this->supported_os_type = null;
$this->prerequisites_installed = null;
$this->docker_installed = null;
$this->docker_compose_installed = null;
$this->docker_version = null;
$this->error = null;
$this->number_of_tries = 0;
$this->init();
try {
$this->authorize('update', $this->server);
$this->uptime = null;
$this->supported_os_type = null;
$this->prerequisites_installed = null;
$this->docker_installed = null;
$this->docker_compose_installed = null;
$this->docker_version = null;
$this->error = null;
$this->number_of_tries = 0;
$this->init();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function validateConnection()
{
$this->authorize('update', $this->server);
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (! $this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
$this->server->update([
'validation_logs' => $this->error,
]);
try {
$this->authorize('update', $this->server);
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (! $this->uptime) {
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
$this->server->update([
'validation_logs' => $this->error,
]);
return;
return;
}
$this->dispatch('validateOS');
} catch (\Throwable $e) {
return handleError($e, $this);
}
$this->dispatch('validateOS');
}
public function validateOS()