fix(security): enforce team access on mutable actions

Authorize cloud provider token access, audit sensitive operations, and
standardize public IDs across deployment and resource flows.
This commit is contained in:
Andras Bacsai committed 2026-06-04 11:03:06 +02:00
1 parent 9dca7ca351
commit 062ad57740
83 files changed
+824 -278

No files matched your search

+15 -2
View File
@@ -3,6 +3,7 @@
namespace App\Livewire\Security;
use App\Models\CloudInitScript;
use App\Rules\ValidCloudInitYaml;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Component;
@@ -40,7 +41,7 @@ class CloudInitScriptForm extends Component
{
return [
'name' => 'required|string|max:255',
'script' => ['required', 'string', new \App\Rules\ValidCloudInitYaml],
'script' => ['required', 'string', new ValidCloudInitYaml],
];
}
@@ -68,17 +69,29 @@ class CloudInitScriptForm extends Component
'script' => $this->script,
]);
auditLog('ui.cloud_init_script.updated', [
'team_id' => currentTeam()->id,
'cloud_init_script_id' => $cloudInitScript->id,
'cloud_init_script_name' => $cloudInitScript->name,
]);
$message = 'Cloud-init script updated successfully.';
} else {
// Create new script
$this->authorize('create', CloudInitScript::class);
CloudInitScript::create([
$cloudInitScript = CloudInitScript::create([
'team_id' => currentTeam()->id,
'name' => $this->name,
'script' => $this->script,
]);
auditLog('ui.cloud_init_script.created', [
'team_id' => currentTeam()->id,
'cloud_init_script_id' => $cloudInitScript->id,
'cloud_init_script_name' => $cloudInitScript->name,
]);
$message = 'Cloud-init script created successfully.';
}
@@ -36,9 +36,16 @@ class CloudInitScripts extends Component
$script = CloudInitScript::ownedByCurrentTeam()->findOrFail($scriptId);
$this->authorize('delete', $script);
$scriptName = $script->name;
$script->delete();
$this->loadScripts();
auditLog('ui.cloud_init_script.deleted', [
'team_id' => currentTeam()->id,
'cloud_init_script_id' => $scriptId,
'cloud_init_script_name' => $scriptName,
]);
$this->dispatch('success', 'Cloud-init script deleted successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);
@@ -54,7 +54,6 @@ class CloudProviderTokenForm extends Component
$response = Http::withHeaders([
'Authorization' => 'Bearer '.$token,
])->timeout(10)->get('https://api.hetzner.cloud/v1/servers');
ray($response);
return $response->successful();
}
@@ -85,6 +84,13 @@ class CloudProviderTokenForm extends Component
'name' => $this->name,
]);
auditLog('ui.cloud_token.created', [
'team_id' => currentTeam()->id,
'cloud_token_uuid' => $savedToken->uuid,
'cloud_token_name' => $savedToken->name,
'provider' => $savedToken->provider,
]);
$this->reset(['token', 'name']);
// Dispatch event with token ID so parent components can react
+21 -2
View File
@@ -4,6 +4,7 @@ namespace App\Livewire\Security;
use App\Models\CloudProviderToken;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Http;
use Livewire\Component;
class CloudProviderTokens extends Component
@@ -57,6 +58,14 @@ class CloudProviderTokens extends Component
} else {
$this->dispatch('error', 'Unknown provider.');
}
auditLog('ui.cloud_token.validated', [
'team_id' => currentTeam()->id,
'cloud_token_uuid' => $token->uuid,
'cloud_token_name' => $token->name,
'provider' => $token->provider,
'valid' => $isValid ?? false,
]);
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -65,7 +74,7 @@ class CloudProviderTokens extends Component
private function validateHetznerToken(string $token): bool
{
try {
$response = \Illuminate\Support\Facades\Http::withToken($token)
$response = Http::withToken($token)
->timeout(10)
->get('https://api.hetzner.cloud/v1/servers?per_page=1');
@@ -78,7 +87,7 @@ class CloudProviderTokens extends Component
private function validateDigitalOceanToken(string $token): bool
{
try {
$response = \Illuminate\Support\Facades\Http::withToken($token)
$response = Http::withToken($token)
->timeout(10)
->get('https://api.digitalocean.com/v2/account');
@@ -102,9 +111,19 @@ class CloudProviderTokens extends Component
return;
}
$tokenUuid = $token->uuid;
$tokenName = $token->name;
$tokenProvider = $token->provider;
$token->delete();
$this->loadTokens();
auditLog('ui.cloud_token.deleted', [
'team_id' => currentTeam()->id,
'cloud_token_uuid' => $tokenUuid,
'cloud_token_name' => $tokenName,
'provider' => $tokenProvider,
]);
$this->dispatch('success', 'Cloud provider token deleted successfully.');
} catch (\Throwable $e) {
return handleError($e, $this);