feat: add token validation functionality for Hetzner and DigitalOcean providers

This commit is contained in:
Andras Bacsai
2025-10-29 23:21:38 +01:00
parent c95e297f39
commit 2a8fbb3f6e
3 changed files with 82 additions and 18 deletions

View File

@@ -30,6 +30,60 @@ class CloudProviderTokens extends Component
$this->tokens = CloudProviderToken::ownedByCurrentTeam()->get();
}
public function validateToken(int $tokenId)
{
try {
$token = CloudProviderToken::ownedByCurrentTeam()->findOrFail($tokenId);
$this->authorize('view', $token);
if ($token->provider === 'hetzner') {
$isValid = $this->validateHetznerToken($token->token);
if ($isValid) {
$this->dispatch('success', 'Hetzner token is valid.');
} else {
$this->dispatch('error', 'Hetzner token validation failed. Please check the token.');
}
} elseif ($token->provider === 'digitalocean') {
$isValid = $this->validateDigitalOceanToken($token->token);
if ($isValid) {
$this->dispatch('success', 'DigitalOcean token is valid.');
} else {
$this->dispatch('error', 'DigitalOcean token validation failed. Please check the token.');
}
} else {
$this->dispatch('error', 'Unknown provider.');
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
private function validateHetznerToken(string $token): bool
{
try {
$response = \Illuminate\Support\Facades\Http::withToken($token)
->timeout(10)
->get('https://api.hetzner.cloud/v1/servers?per_page=1');
return $response->successful();
} catch (\Throwable $e) {
return false;
}
}
private function validateDigitalOceanToken(string $token): bool
{
try {
$response = \Illuminate\Support\Facades\Http::withToken($token)
->timeout(10)
->get('https://api.digitalocean.com/v2/account');
return $response->successful();
} catch (\Throwable $e) {
return false;
}
}
public function deleteToken(int $tokenId)
{
try {

View File

@@ -14,13 +14,14 @@
<x-forms.input required id="name" label="Token Name"
placeholder="e.g., Production Hetzner. tip: add Hetzner project name to identify easier" />
<x-forms.input required type="password" id="token" label="API Token" placeholder="Enter your API token" />
<x-forms.input required type="password" id="token" label="API Token"
placeholder="Enter your API token" />
@if (auth()->user()->currentTeam()->cloudProviderTokens->where('provider', $provider)->isEmpty())
<div class="text-sm text-neutral-500 dark:text-neutral-400">
Create an API token in the <a
href='{{ $provider === 'hetzner' ? 'https://console.hetzner.com/projects' : '#' }}' target='_blank'
class='underline dark:text-white'>{{ ucfirst($provider) }} Console</a> choose
href='{{ $provider === 'hetzner' ? 'https://console.hetzner.com/projects' : '#' }}'
target='_blank' class='underline dark:text-white'>{{ ucfirst($provider) }} Console</a> choose
Project Security API Tokens.
@if ($provider === 'hetzner')
<br><br>
@@ -33,7 +34,7 @@
</div>
@endif
<x-forms.button type="submit" wire:target="addToken">Validate & Add Token</x-forms.button>
<x-forms.button type="submit">Validate & Add Token</x-forms.button>
@else
{{-- Full page layout: horizontal, spacious --}}
<div class="flex gap-2 items-end flex-wrap">
@@ -49,7 +50,8 @@
</div>
</div>
<div class="flex-1 min-w-64">
<x-forms.input required type="password" id="token" label="API Token" placeholder="Enter your API token" />
<x-forms.input required type="password" id="token" label="API Token"
placeholder="Enter your API token" />
@if (auth()->user()->currentTeam()->cloudProviderTokens->where('provider', $provider)->isEmpty())
<div class="text-sm text-neutral-500 dark:text-neutral-400 mt-2">
Create an API token in the <a href='https://console.hetzner.com/projects' target='_blank'
@@ -64,7 +66,7 @@
</div>
@endif
</div>
<x-forms.button type="submit" wire:target="addToken">Validate & Add Token</x-forms.button>
<x-forms.button type="submit">Validate & Add Token</x-forms.button>
@endif
</form>
</div>

View File

@@ -20,6 +20,13 @@
</div>
<div class="text-sm">Created: {{ $savedToken->created_at->diffForHumans() }}</div>
<div class="flex gap-2 pt-2">
@can('view', $savedToken)
<x-forms.button wire:click="validateToken({{ $savedToken->id }})" type="button">
Validate Token
</x-forms.button>
@endcan
@can('delete', $savedToken)
<x-modal-confirmation title="Confirm Token Deletion?" isErrorButton buttonTitle="Delete Token"
submitAction="deleteToken({{ $savedToken->id }})" :actions="[
@@ -31,6 +38,7 @@
shortConfirmationLabel="Token Name" :confirmWithPassword="false" step2ButtonText="Delete Token" />
@endcan
</div>
</div>
@empty
<div>
<div>No cloud provider tokens found.</div>