From a8000ac2ad2fa0882223b8360028e21e9aaa6ad4 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 7 Jul 2026 14:41:17 +0200 Subject: [PATCH] fix(hetzner): require at least one public IP protocol --- .../Controllers/Api/HetznerController.php | 10 +++++++++ app/Livewire/Server/New/ByHetzner.php | 7 ++++++ tests/Feature/Api/HetznerApiTest.php | 22 +++++++++++++++++++ .../Server/HetznerServerCreationTest.php | 21 +++++++++++++++++- 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/HetznerController.php b/app/Http/Controllers/Api/HetznerController.php index 151f9854b..4cadc0eb6 100644 --- a/app/Http/Controllers/Api/HetznerController.php +++ b/app/Http/Controllers/Api/HetznerController.php @@ -824,6 +824,16 @@ public function createServer(Request $request) $request->offsetSet('instant_validate', false); } + if (! $request->boolean('enable_ipv4') && ! $request->boolean('enable_ipv6')) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'enable_ipv4' => ['Enable at least one public IP protocol.'], + 'enable_ipv6' => ['Enable at least one public IP protocol.'], + ], + ], 422); + } + // Validate cloud provider token $tokenUuid = $this->getCloudProviderTokenUuid($request); $token = CloudProviderToken::whereTeamId($teamId) diff --git a/app/Livewire/Server/New/ByHetzner.php b/app/Livewire/Server/New/ByHetzner.php index f991bc3e8..96740537d 100644 --- a/app/Livewire/Server/New/ByHetzner.php +++ b/app/Livewire/Server/New/ByHetzner.php @@ -620,6 +620,13 @@ public function submit() { $this->validate(); + if (! $this->enable_ipv4 && ! $this->enable_ipv6) { + $this->addError('enable_ipv4', 'Enable at least one public IP protocol.'); + $this->addError('enable_ipv6', 'Enable at least one public IP protocol.'); + + return null; + } + try { $this->authorize('create', Server::class); diff --git a/tests/Feature/Api/HetznerApiTest.php b/tests/Feature/Api/HetznerApiTest.php index 46311e5b5..8c1856f02 100644 --- a/tests/Feature/Api/HetznerApiTest.php +++ b/tests/Feature/Api/HetznerApiTest.php @@ -669,6 +669,28 @@ $response->assertJsonFragment(['ip' => '2001:db8::1']); }); + test('rejects server creation when both public IP protocols are disabled', function () { + Http::fake(); + + $response = $this->withHeaders([ + 'Authorization' => 'Bearer '.$this->bearerToken, + 'Content-Type' => 'application/json', + ])->postJson('/api/v1/servers/hetzner', [ + 'cloud_provider_token_id' => $this->hetznerToken->uuid, + 'location' => 'nbg1', + 'server_type' => 'cx11', + 'image' => 15512617, + 'name' => 'test-server', + 'private_key_uuid' => $this->privateKey->uuid, + 'enable_ipv4' => false, + 'enable_ipv6' => false, + ]); + + $response->assertStatus(422); + $response->assertJsonValidationErrors(['enable_ipv4', 'enable_ipv6']); + Http::assertNothingSent(); + }); + test('passes selected firewalls and networks to Hetzner server creation', function () { Http::fake([ 'https://api.hetzner.cloud/v1/ssh_keys' => Http::response([ diff --git a/tests/Feature/Server/HetznerServerCreationTest.php b/tests/Feature/Server/HetznerServerCreationTest.php index 34e25d58a..957641601 100644 --- a/tests/Feature/Server/HetznerServerCreationTest.php +++ b/tests/Feature/Server/HetznerServerCreationTest.php @@ -240,7 +240,7 @@ 'token' => 'test-hetzner-api-token', ]); - PrivateKey::factory()->create([ + $this->privateKey = PrivateKey::factory()->create([ 'team_id' => $this->team->id, ]); }); @@ -298,4 +298,23 @@ expect($component->get('hetznerFirewalls'))->toHaveCount(1) ->and($component->get('hetznerNetworks'))->toHaveCount(1); }); + + test('rejects submitting without a public IP protocol before calling Hetzner', function () { + Http::fake(); + + Livewire::test(ByHetzner::class) + ->set('current_step', 2) + ->set('selected_token_id', $this->hetznerToken->id) + ->set('server_name', 'test-server') + ->set('selected_location', 'nbg1') + ->set('selected_server_type', 'cx11') + ->set('selected_image', 15512617) + ->set('private_key_id', $this->privateKey->id) + ->set('enable_ipv4', false) + ->set('enable_ipv6', false) + ->call('submit') + ->assertHasErrors(['enable_ipv4', 'enable_ipv6']); + + Http::assertNothingSent(); + }); });