From acff543e09ae5c7f8da78e5a092ebb1e57f24dc0 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sat, 27 Dec 2025 16:17:56 +0100 Subject: [PATCH] fix(settings): fix 404 on /settings for root user on cloud instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Make Server property nullable in Settings components (Index, Advanced, Updates) - Add conditional server loading: only load when not on cloud - Add null checks before using server for DNS validation and proxy configuration - Fix isInstanceAdmin() to check root team's pivot role directly instead of current team - Make root team (id=0) bypass subscription check on cloud - Remove isInstanceAdmin() from main middleware bypass: only settings/admin routes are exempted - Update isSubscribed() to only check isSubscriptionActive() for navbar consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 --- app/Http/Middleware/DecideWhatToDoWithUser.php | 6 +++++- app/Livewire/Settings/Advanced.php | 8 ++++---- app/Livewire/Settings/Index.php | 18 ++++++++++++++---- app/Livewire/Settings/Updates.php | 10 +++++++--- app/Models/User.php | 3 ++- bootstrap/helpers/shared.php | 2 +- bootstrap/helpers/subscriptions.php | 4 ++++ 7 files changed, 37 insertions(+), 14 deletions(-) diff --git a/app/Http/Middleware/DecideWhatToDoWithUser.php b/app/Http/Middleware/DecideWhatToDoWithUser.php index 8b1c550df..64952533f 100644 --- a/app/Http/Middleware/DecideWhatToDoWithUser.php +++ b/app/Http/Middleware/DecideWhatToDoWithUser.php @@ -19,13 +19,17 @@ public function handle(Request $request, Closure $next): Response if (auth()?->user()?->currentTeam()) { refreshSession(auth()->user()->currentTeam()); } - if (! auth()->user() || ! isCloud() || isInstanceAdmin()) { + if (! auth()->user() || ! isCloud()) { if (! isCloud() && showBoarding() && ! in_array($request->path(), allowedPathsForBoardingAccounts())) { return redirect()->route('onboarding'); } return $next($request); } + // Instance admins can access settings and admin routes regardless of subscription + if (isInstanceAdmin() && (Str::startsWith($request->path(), 'settings') || $request->path() === 'admin')) { + return $next($request); + } if (! auth()->user()->hasVerifiedEmail()) { if ($request->path() === 'verify' || in_array($request->path(), allowedPathsForInvalidAccounts()) || $request->routeIs('verify.verify')) { return $next($request); diff --git a/app/Livewire/Settings/Advanced.php b/app/Livewire/Settings/Advanced.php index fb9c91263..0a7ba319d 100644 --- a/app/Livewire/Settings/Advanced.php +++ b/app/Livewire/Settings/Advanced.php @@ -10,8 +10,7 @@ class Advanced extends Component { - #[Validate('required')] - public Server $server; + public ?Server $server = null; public InstanceSettings $settings; @@ -44,7 +43,6 @@ class Advanced extends Component public function rules() { return [ - 'server' => 'required', 'is_registration_enabled' => 'boolean', 'do_not_track' => 'boolean', 'is_dns_validation_enabled' => 'boolean', @@ -62,7 +60,9 @@ public function mount() if (! isInstanceAdmin()) { return redirect()->route('dashboard'); } - $this->server = Server::findOrFail(0); + if (! isCloud()) { + $this->server = Server::findOrFail(0); + } $this->settings = instanceSettings(); $this->custom_dns_servers = $this->settings->custom_dns_servers; $this->allowed_ips = $this->settings->allowed_ips; diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index 7a96eabb2..ae7448807 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -12,7 +12,7 @@ class Index extends Component { public InstanceSettings $settings; - public Server $server; + public ?Server $server = null; #[Validate('nullable|string|max:255')] public ?string $fqdn = null; @@ -57,7 +57,9 @@ public function mount() return redirect()->route('dashboard'); } $this->settings = instanceSettings(); - $this->server = Server::findOrFail(0); + if (! isCloud()) { + $this->server = Server::findOrFail(0); + } $this->fqdn = $this->settings->fqdn; $this->public_port_min = $this->settings->public_port_min; $this->public_port_max = $this->settings->public_port_max; @@ -121,7 +123,7 @@ public function submit() } $this->validate(); - if ($this->settings->is_dns_validation_enabled && $this->fqdn) { + if ($this->settings->is_dns_validation_enabled && $this->fqdn && $this->server) { if (! validateDNSEntry($this->fqdn, $this->server)) { $this->dispatch('error', "Validating DNS failed.

Make sure you have added the DNS records correctly.

{$this->fqdn}->{$this->server->ip}

Check this documentation for further help."); $error_show = true; @@ -145,7 +147,9 @@ public function submit() $this->instantSave(isSave: false); $this->settings->save(); - $this->server->setupDynamicProxyConfiguration(); + if ($this->server) { + $this->server->setupDynamicProxyConfiguration(); + } if (! $error_show) { $this->dispatch('success', 'Instance settings updated successfully!'); } @@ -163,6 +167,12 @@ public function buildHelperImage() return; } + if (! $this->server) { + $this->dispatch('error', 'Server not available.'); + + return; + } + $version = $this->dev_helper_version ?: config('constants.coolify.helper_version'); if (empty($version)) { $this->dispatch('error', 'Please specify a version to build.'); diff --git a/app/Livewire/Settings/Updates.php b/app/Livewire/Settings/Updates.php index fe20763b6..01a67c38c 100644 --- a/app/Livewire/Settings/Updates.php +++ b/app/Livewire/Settings/Updates.php @@ -12,7 +12,7 @@ class Updates extends Component { public InstanceSettings $settings; - public Server $server; + public ?Server $server = null; #[Validate('string')] public string $auto_update_frequency; @@ -25,7 +25,9 @@ class Updates extends Component public function mount() { - $this->server = Server::findOrFail(0); + if (! isCloud()) { + $this->server = Server::findOrFail(0); + } $this->settings = instanceSettings(); $this->auto_update_frequency = $this->settings->auto_update_frequency; @@ -76,7 +78,9 @@ public function submit() } $this->instantSave(); - $this->server->setupDynamicProxyConfiguration(); + if ($this->server) { + $this->server->setupDynamicProxyConfiguration(); + } } catch (\Exception $e) { return handleError($e, $this); } diff --git a/app/Models/User.php b/app/Models/User.php index b790efcf1..bbc4e603c 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -297,7 +297,8 @@ public function isInstanceAdmin() { $found_root_team = Auth::user()->teams->filter(function ($team) { if ($team->id == 0) { - if (! Auth::user()->isAdmin()) { + $role = $team->pivot->role; + if ($role !== 'admin' && $role !== 'owner') { return false; } diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 9fc1e6f1c..f9645fd9f 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -384,7 +384,7 @@ function base_url(bool $withPort = true): string function isSubscribed() { - return isSubscriptionActive() || auth()->user()->isInstanceAdmin(); + return isSubscriptionActive(); } function isProduction(): bool diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index 1a0ae0fbd..4b84fb7f6 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -13,6 +13,10 @@ function isSubscriptionActive() if (! $team) { return false; } + // Root team (id=0) doesn't require subscription + if ($team->id === 0) { + return true; + } $subscription = $team?->subscription; if (is_null($subscription)) {