fix(hetzner): secure token-backed option fetches

Authorize stored Hetzner cloud provider tokens before fetching
firewalls or networks, hide provider error details, and keep server
creation intact if backup activation fails.

Collapse advanced Hetzner server options by default while preserving
visibility when advanced values are selected.
This commit is contained in:
Andras Bacsai committed 2026-07-07 14:24:15 +02:00
1 parent 2ad11fdd9a
commit d723a52d9c
6 files changed
+330 -85

No files matched your search

+12 -6
View File
@@ -543,13 +543,14 @@ class HetznerController extends Controller
if (! $token) {
return response()->json(['message' => 'Hetzner cloud provider token not found.'], 404);
}
$this->authorize('view', $token);
try {
$hetznerService = new HetznerService($token->token);
return response()->json($hetznerService->getFirewalls());
} catch (\Throwable $e) {
return response()->json(['message' => 'Failed to fetch firewalls: '.$e->getMessage()], 500);
return response()->json(['message' => 'Failed to fetch Hetzner firewalls.'], 500);
}
}
@@ -637,13 +638,14 @@ class HetznerController extends Controller
if (! $token) {
return response()->json(['message' => 'Hetzner cloud provider token not found.'], 404);
}
$this->authorize('view', $token);
try {
$hetznerService = new HetznerService($token->token);
return response()->json($hetznerService->getNetworks());
} catch (\Throwable $e) {
return response()->json(['message' => 'Failed to fetch networks: '.$e->getMessage()], 500);
return response()->json(['message' => 'Failed to fetch Hetzner networks.'], 500);
}
}
@@ -914,10 +916,6 @@ class HetznerController extends Controller
// Create server on Hetzner
$hetznerServer = $hetznerService->createServer($params);
if ($request->enable_backups) {
$hetznerService->enableServerBackup((int) $hetznerServer['id']);
}
// Determine IP address to use (prefer IPv4, fallback to IPv6)
$ipAddress = null;
if ($request->enable_ipv4 && isset($hetznerServer['public_net']['ipv4']['ip'])) {
@@ -946,6 +944,14 @@ class HetznerController extends Controller
$server->proxy->set('type', ProxyTypes::TRAEFIK->value);
$server->save();
if ($request->enable_backups) {
try {
$hetznerService->enableServerBackup((int) $hetznerServer['id']);
} catch (\Throwable $e) {
report($e);
}
}
// Validate server if requested
if ($request->instant_validate) {
ValidateServer::dispatch($server);
+87 -4
View File
@@ -74,6 +74,10 @@ class ByHetzner extends Component
public bool $enable_backups = false;
public bool $show_advanced_hetzner_options = false;
public bool $show_cloud_init_script = false;
public ?string $cloud_init_script = null;
public bool $save_cloud_init_script = false;
@@ -127,6 +131,8 @@ class ByHetzner extends Component
$this->save_cloud_init_script = false;
$this->cloud_init_script_name = null;
$this->selected_cloud_init_script_id = null;
$this->show_advanced_hetzner_options = false;
$this->show_cloud_init_script = false;
$this->selectedHetznerSshKeyIds = [];
$this->selectedHetznerFirewallIds = [];
$this->selectedHetznerNetworkIds = [];
@@ -185,6 +191,8 @@ class ByHetzner extends Component
'enable_ipv4' => 'required|boolean',
'enable_ipv6' => 'required|boolean',
'enable_backups' => 'required|boolean',
'show_advanced_hetzner_options' => 'boolean',
'show_cloud_init_script' => 'boolean',
'cloud_init_script' => ['nullable', 'string', new ValidCloudInitYaml],
'save_cloud_init_script' => 'boolean',
'cloud_init_script_name' => 'nullable|string|max:255',
@@ -444,6 +452,67 @@ class ByHetzner extends Component
return '€'.number_format($price * 0.2, 2);
}
public function getShouldShowAdvancedHetznerOptionsProperty(): bool
{
return $this->show_advanced_hetzner_options
|| $this->selectedHetznerSshKeyIds !== []
|| $this->selectedHetznerFirewallIds !== []
|| $this->selectedHetznerNetworkIds !== []
|| $this->enable_backups
|| ! $this->enable_ipv4
|| ! $this->enable_ipv6
|| $this->show_cloud_init_script
|| filled($this->cloud_init_script)
|| $this->save_cloud_init_script
|| filled($this->cloud_init_script_name)
|| filled($this->selected_cloud_init_script_id);
}
public function getAdvancedHetznerOptionsSummaryProperty(): array
{
$summary = [];
if (count($this->selectedHetznerSshKeyIds) > 0) {
$summary[] = count($this->selectedHetznerSshKeyIds).' extra SSH '.str('key')->plural(count($this->selectedHetznerSshKeyIds));
}
if (count($this->selectedHetznerFirewallIds) > 0) {
$summary[] = count($this->selectedHetznerFirewallIds).' '.str('firewall')->plural(count($this->selectedHetznerFirewallIds));
}
if (count($this->selectedHetznerNetworkIds) > 0) {
$summary[] = count($this->selectedHetznerNetworkIds).' private '.str('network')->plural(count($this->selectedHetznerNetworkIds));
}
if ($this->enable_backups) {
$summary[] = 'Backups on';
}
if (! $this->enable_ipv4 || ! $this->enable_ipv6) {
$summary[] = collect([
$this->enable_ipv4 ? 'IPv4' : null,
$this->enable_ipv6 ? 'IPv6' : null,
])->filter()->join(' + ') ?: 'No public IP';
}
if ($this->show_cloud_init_script || filled($this->cloud_init_script) || filled($this->selected_cloud_init_script_id)) {
$summary[] = 'Cloud-init';
}
return $summary;
}
public function toggleAdvancedHetznerOptions(): void
{
$this->show_advanced_hetzner_options = ! $this->show_advanced_hetzner_options;
}
public function showCloudInitScript(): void
{
$this->show_cloud_init_script = true;
$this->show_advanced_hetzner_options = true;
}
public function updatedSelectedLocation($value)
{
// Reset server type and image when location changes
@@ -475,6 +544,15 @@ class ByHetzner extends Component
$script = CloudInitScript::ownedByCurrentTeam()->findOrFail($value);
$this->cloud_init_script = $script->script;
$this->cloud_init_script_name = $script->name;
$this->show_cloud_init_script = true;
$this->show_advanced_hetzner_options = true;
}
}
public function updatedSaveCloudInitScript(bool $value): void
{
if (! $value) {
$this->cloud_init_script_name = null;
}
}
@@ -484,6 +562,7 @@ class ByHetzner extends Component
$this->cloud_init_script = '';
$this->cloud_init_script_name = '';
$this->save_cloud_init_script = false;
$this->show_cloud_init_script = false;
}
private function createHetznerServer(HetznerService $hetznerService): array
@@ -592,10 +671,6 @@ class ByHetzner extends Component
// Create server on Hetzner
$hetznerServer = $this->createHetznerServer($hetznerService);
if ($this->enable_backups) {
$hetznerService->enableServerBackup((int) $hetznerServer['id']);
}
// Determine IP address to use (prefer IPv4, fallback to IPv6)
$ipAddress = null;
if ($this->enable_ipv4 && isset($hetznerServer['public_net']['ipv4']['ip'])) {
@@ -624,6 +699,14 @@ class ByHetzner extends Component
$server->proxy->set('type', ProxyTypes::TRAEFIK->value);
$server->save();
if ($this->enable_backups) {
try {
$hetznerService->enableServerBackup((int) $hetznerServer['id']);
} catch (\Throwable $e) {
report($e);
}
}
if ($this->from_onboarding) {
// Complete the boarding when server is successfully created via Hetzner
currentTeam()->update([