diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 76e36fe873..f886bef8c8 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -305,7 +305,7 @@ class ServersController extends Controller if (is_null($teamId)) { return invalidTokenResponse(); } - $server = ModelsServer::whereTeamId($teamId)->whereUuid($request->uuid)->first(); + $server = ModelsServer::whereTeamId($teamId)->whereUuid($request->route('uuid'))->first(); if (is_null($server)) { return response()->json(['message' => 'Server not found.'], 404); } diff --git a/app/Livewire/Profile/Index.php b/app/Livewire/Profile/Index.php index 9b1ef42ce2..a69424cffd 100644 --- a/app/Livewire/Profile/Index.php +++ b/app/Livewire/Profile/Index.php @@ -2,15 +2,19 @@ namespace App\Livewire\Profile; +use App\Services\AvatarStorageService; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Validation\Rules\Password; use Livewire\Attributes\Validate; use Livewire\Component; +use Livewire\WithFileUploads; class Index extends Component { + use WithFileUploads; + public int $userId; public string $email; diff --git a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php index e47d3818fe..694f18c9c9 100644 --- a/app/Livewire/Project/Shared/EnvironmentVariable/Show.php +++ b/app/Livewire/Project/Shared/EnvironmentVariable/Show.php @@ -17,6 +17,7 @@ use App\Traits\HasSecretManagerAutocomplete; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\Model; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; +use Illuminate\Validation\ValidationException; use Livewire\Attributes\Computed; use Livewire\Component; @@ -188,7 +189,6 @@ class Show extends Component } private function syncData(bool $toModel = false): void - { if ($toModel) { $this->key = ValidationPatterns::normalizeEnvironmentVariableKey($this->key); @@ -330,6 +330,8 @@ class Show extends Component if ($this->is_required && $this->resource instanceof Service) { event(new ApplicationConfigurationChanged($this->resource->team()->id)); } + } catch (ValidationException $e) { + throw $e; } catch (\Exception $e) { return handleError($e); } diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php index 7a530fe297..ca98698450 100644 --- a/app/Livewire/Security/ApiTokens.php +++ b/app/Livewire/Security/ApiTokens.php @@ -15,7 +15,13 @@ class ApiTokens extends Component public ?string $description = null; - public ?int $expiresInDays = 30; + /** + * Untyped on purpose: Livewire skips hydrating null into typed properties, which + * would silently revert a "Never" (null) choice back to the default on the next request. + * + * @var int|string|null + */ + public $expiresInDays = 30; public $tokens = []; @@ -143,7 +149,7 @@ class ApiTokens extends Component throw new \Exception('You do not have permission to create tokens with read:sensitive permissions.'); } - $expiresAt = $this->expiresInDays ? now()->addDays($this->expiresInDays) : null; + $expiresAt = filled($validated['expiresInDays'] ?? null) ? now()->addDays((int) $validated['expiresInDays']) : null; $token = auth()->user()->createToken($this->description, $permissions, $expiresAt); auditLog('ui.api_token.created', [ 'team_id' => currentTeam()->id, diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index 975ce9a241..320285970d 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -163,6 +163,7 @@ class SettingsEmail extends Component public function toggleSmtp() { try { + $this->authorize('update', $this->settings); $this->resetErrorBag(); if ($this->smtpEnabled) { @@ -185,6 +186,7 @@ class SettingsEmail extends Component public function toggleResend() { try { + $this->authorize('update', $this->settings); $this->resetErrorBag(); if ($this->resendEnabled) { diff --git a/resources/views/livewire/server/navbar.blade.php b/resources/views/livewire/server/navbar.blade.php index 41aedc45ae..01cc64891c 100644 --- a/resources/views/livewire/server/navbar.blade.php +++ b/resources/views/livewire/server/navbar.blade.php @@ -85,7 +85,9 @@ $activeServerMenuItem['route'], $serverRouteParameters, ); - $showSentinelStatus = $server->isFunctional() && $server->isSentinelEnabled(); + $showSentinelStatus = $server->isFunctional() + && $server->isSentinelEnabled() + && auth()->user()?->can('viewSentinel', $server); $proxyCanBeStopped = in_array($proxyStatus, ['running', 'starting', 'restarting'], true); @endphp diff --git a/resources/views/livewire/server/show.blade.php b/resources/views/livewire/server/show.blade.php index 4edbf479a3..6ae3c02aa8 100644 --- a/resources/views/livewire/server/show.blade.php +++ b/resources/views/livewire/server/show.blade.php @@ -88,6 +88,24 @@ @endif + @if ($server->cloudProviderToken) + @if ($provider === 'Hetzner' && !$server->isFunctional() && $hetznerServerStatus === 'off') + + Power On + + @elseif ($provider === 'DigitalOcean' && $digitalOceanDropletStatus === 'off') + + Power On + + @elseif ($provider === 'Vultr' && $vultrInstanceStatus === 'stopped') + + Power On + + @endif + @endif @endif @if ($server->server_metadata) @endif - + Sync name @@ -197,7 +198,8 @@ shortConfirmationLabel="GitHub App Name" :confirmWithPassword="false" step2ButtonText="Permanently Delete" /> @else - + Delete @endcan diff --git a/tests/Feature/SettingsEmailProviderExclusivityTest.php b/tests/Feature/SettingsEmailProviderExclusivityTest.php index 7e6b6ff232..c63df9fe40 100644 --- a/tests/Feature/SettingsEmailProviderExclusivityTest.php +++ b/tests/Feature/SettingsEmailProviderExclusivityTest.php @@ -62,3 +62,28 @@ test('enabling Resend disables SMTP in storage', function () { expect($this->settings->resend_enabled)->toBeTrue(); expect($this->settings->smtp_enabled)->toBeFalse(); }); + +test('a user who is no longer an instance admin cannot toggle email providers', function (string $action, string $column) { + $this->settings->update([ + 'smtp_enabled' => $column === 'smtp_enabled', + 'resend_enabled' => $column === 'resend_enabled', + 'resend_api_key' => 're_test_key', + 'smtp_host' => 'smtp.example.com', + 'smtp_port' => '587', + 'smtp_from_address' => 'from@example.com', + 'smtp_from_name' => 'Coolify', + ]); + + $component = Livewire::test(SettingsEmail::class); + + $member = User::factory()->create(); + $member->teams()->attach($this->rootTeam, ['role' => 'member']); + $this->actingAs($member); + + $component->call($action)->assertDispatched('error'); + + expect($this->settings->refresh()->{$column})->toBeTrue(); +})->with([ + 'SMTP' => ['toggleSmtp', 'smtp_enabled'], + 'Resend' => ['toggleResend', 'resend_enabled'], +]);