diff --git a/app/Http/Controllers/Api/HostingerController.php b/app/Http/Controllers/Api/HostingerController.php index 84017e7ded..af4173e39d 100644 --- a/app/Http/Controllers/Api/HostingerController.php +++ b/app/Http/Controllers/Api/HostingerController.php @@ -122,6 +122,7 @@ class HostingerController extends Controller responses: [ new OA\Response(response: 201, description: 'Hostinger VPS purchased and linked to a Coolify server.'), new OA\Response(response: 401, ref: '#/components/responses/401'), + new OA\Response(response: 403, description: 'The token needs the deploy ability to set post_install_script_id or public_key_ids.'), new OA\Response(response: 422, description: 'Validation failed.'), new OA\Response(response: 429, description: 'Hostinger rate limit exceeded.'), ] @@ -182,6 +183,10 @@ class HostingerController extends Controller ], 422); } + if ($request->filled('post_install_script_id') || $request->array('public_key_ids')) { + abort_unless($request->user()->tokenCan('deploy') || $request->user()->tokenCan('root'), 403, 'Missing required permissions: deploy'); + } + $team = Team::find($teamId); if (Team::serverLimitReached($team)) { return response()->json(['message' => 'Server limit reached for your subscription.'], 400); diff --git a/openapi.json b/openapi.json index 805871904f..70c4ec8c47 100644 --- a/openapi.json +++ b/openapi.json @@ -13108,6 +13108,9 @@ "401": { "$ref": "#\/components\/responses\/401" }, + "403": { + "description": "The token needs the deploy ability to set post_install_script_id or public_key_ids." + }, "422": { "description": "Validation failed." }, diff --git a/openapi.yaml b/openapi.yaml index c4975a0564..7d82b2ef23 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -8402,6 +8402,8 @@ paths: description: 'Hostinger VPS purchased and linked to a Coolify server.' '401': $ref: '#/components/responses/401' + '403': + description: 'The token needs the deploy ability to set post_install_script_id or public_key_ids.' '422': description: 'Validation failed.' '429': diff --git a/tests/Feature/HostingerApiTest.php b/tests/Feature/HostingerApiTest.php index bdb5c309e4..08454fb5a9 100644 --- a/tests/Feature/HostingerApiTest.php +++ b/tests/Feature/HostingerApiTest.php @@ -192,6 +192,57 @@ it('turns paid Hostinger backups off when the API request does not enable them', && $request['setup']['enable_backups'] === false); }); +it('requires the deploy ability to run a post-install script or add SSH keys on a Hostinger VPS', function (array $extra) { + Http::fake(); + $writeToken = $this->user->createToken('write-token', ['read', 'write'])->plainTextToken; + + $this->withToken($writeToken) + ->postJson('/api/v1/servers/hostinger', [ + 'cloud_provider_token_uuid' => $this->hostingerToken->uuid, + 'item_id' => 'hostingercom-vps-kvm1-usd-1m', + 'data_center_id' => 19, + 'template_id' => 1130, + 'private_key_uuid' => $this->privateKey->uuid, + ...$extra, + ]) + ->assertForbidden() + ->assertJsonFragment(['message' => 'Missing required permissions: deploy']); + + Http::assertNothingSent(); + $this->assertDatabaseMissing('servers', ['team_id' => $this->team->id]); +})->with([ + 'post-install script' => [['post_install_script_id' => 73]], + 'extra SSH keys' => [['public_key_ids' => [42]]], +]); + +it('lets a deploy token run a post-install script on a Hostinger VPS', function () { + Http::fake([ + 'https://developers.hostinger.com/api/vps/v1/virtual-machines' => Http::response([ + 'virtual_machine' => [ + 'id' => 17923, + 'state' => 'creating', + 'ipv4' => [['address' => '203.0.113.10']], + ], + ]), + ]); + $deployToken = $this->user->createToken('deploy-token', ['read', 'write', 'deploy'])->plainTextToken; + + $this->withToken($deployToken) + ->postJson('/api/v1/servers/hostinger', [ + 'cloud_provider_token_uuid' => $this->hostingerToken->uuid, + 'item_id' => 'hostingercom-vps-kvm1-usd-1m', + 'data_center_id' => 19, + 'template_id' => 1130, + 'private_key_uuid' => $this->privateKey->uuid, + 'post_install_script_id' => 73, + ]) + ->assertCreated(); + + Http::assertSent(fn ($request) => $request->method() === 'POST' + && $request->url() === 'https://developers.hostinger.com/api/vps/v1/virtual-machines' + && $request['setup']['post_install_script_id'] === 73); +}); + it('keeps a charged Hostinger VPS when the purchase returns an error through the API', function () { $listRequests = 0; Http::fake([