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'],
+]);