From ca203bc9facad2b54a2cb03c128e4b8ab1da0122 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:59:22 +0200 Subject: [PATCH 1/4] fix(ui): keep the light custom theme rule for white surfaces A comment in app.css contained "*/", which ended the comment early. The minifier then dropped the next rule, so white panels in the light custom theme were not tinted. --- resources/css/app.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/css/app.css b/resources/css/app.css index ccdac0ed10..4f42de4775 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1425,7 +1425,7 @@ html[data-theme="custom"] [class~="dark:hover:text-coollabs"]:hover { } /* Light custom theme reaches raw neutral utilities too. Dark mode tints - everything through the pervasive dark:bg-*/dark:border-* utilities (which + everything through the pervasive dark:bg-* and dark:border-* utilities (which resolve to --color-* vars the block overrides); light mode has no equivalent, so components fall back to hardcoded neutrals. Remap the dominant light surface/border utilities to the tinted custom vars so the color reaches the From e1f9e6fb3922d489a8569874f73ad559d87a23a5 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:59:22 +0200 Subject: [PATCH 2/4] fix(ui): deny members on cloud tokens and OAuth settings without a 500 - /security/cloud-tokens returns 403 for members instead of crashing on a null token list - /settings/oauth redirects non-admins to the dashboard; it used the missing route "home" and a wrong redirect return type --- app/Livewire/Security/CloudProviderTokens.php | 14 ++-- app/Livewire/SettingsOauth.php | 6 +- .../MemberRestrictedSettingsPagesTest.php | 66 +++++++++++++++++++ 3 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 tests/Feature/MemberRestrictedSettingsPagesTest.php diff --git a/app/Livewire/Security/CloudProviderTokens.php b/app/Livewire/Security/CloudProviderTokens.php index 12cc95c74b..d16c729fca 100644 --- a/app/Livewire/Security/CloudProviderTokens.php +++ b/app/Livewire/Security/CloudProviderTokens.php @@ -13,14 +13,10 @@ class CloudProviderTokens extends Component public $tokens; - public function mount() + public function mount(): void { - try { - $this->authorize('viewAny', CloudProviderToken::class); - $this->loadTokens(); - } catch (\Throwable $e) { - return handleError($e, $this); - } + $this->authorize('viewAny', CloudProviderToken::class); + $this->loadTokens(); } public function getListeners() @@ -153,6 +149,8 @@ class CloudProviderTokens extends Component public function render() { - return view('livewire.security.cloud-provider-tokens'); + return view('livewire.security.cloud-provider-tokens', [ + 'tokens' => $this->tokens ?? collect(), + ]); } } diff --git a/app/Livewire/SettingsOauth.php b/app/Livewire/SettingsOauth.php index 6287bd00ca..d70dd6421e 100644 --- a/app/Livewire/SettingsOauth.php +++ b/app/Livewire/SettingsOauth.php @@ -5,9 +5,9 @@ namespace App\Livewire; use App\Models\InstanceSettings; use App\Models\OauthSetting; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; -use Illuminate\Http\RedirectResponse; use Illuminate\Validation\ValidationException; use Livewire\Component; +use Livewire\Features\SupportRedirects\Redirector; class SettingsOauth extends Component { @@ -57,10 +57,10 @@ class SettingsOauth extends Component return $rules; } - public function mount(?string $provider = null): ?RedirectResponse + public function mount(?string $provider = null): ?Redirector { if (! isInstanceAdmin()) { - return redirect()->route('home'); + return redirect()->route('dashboard'); } $this->settings = instanceSettings(); diff --git a/tests/Feature/MemberRestrictedSettingsPagesTest.php b/tests/Feature/MemberRestrictedSettingsPagesTest.php new file mode 100644 index 0000000000..3b6f8f2c4d --- /dev/null +++ b/tests/Feature/MemberRestrictedSettingsPagesTest.php @@ -0,0 +1,66 @@ +withoutVite(); + config()->set('app.maintenance.store', 'array'); + + InstanceSettings::forceCreate(['id' => 0]); + Once::flush(); + OauthSetting::create(['provider' => 'authentik']); + + $this->rootTeam = Team::forceCreate(['id' => 0, 'name' => 'Root Team', 'personal_team' => false, 'show_boarding' => false]); +}); + +function actingAsRootTeamMemberWithRole(string $role): User +{ + $team = Team::find(0); + $user = User::factory()->create(['email_verified_at' => now()]); + $user->teams()->detach(); + $user->teams()->attach($team, ['role' => $role]); + Team::query()->update(['show_boarding' => false]); + Cache::flush(); + + session(['currentTeam' => $team]); + test()->actingAs($user); + + return $user; +} + +it('forbids team members from the cloud tokens page', function () { + actingAsRootTeamMemberWithRole('member'); + + $this->get(route('security.cloud-tokens'))->assertForbidden(); +}); + +it('shows the cloud tokens page to team admins and owners', function (string $role) { + actingAsRootTeamMemberWithRole($role); + + $this->get(route('security.cloud-tokens')) + ->assertSuccessful() + ->assertSee('No cloud tokens'); +})->with(['admin', 'owner']); + +it('redirects team members from the oauth settings page to the dashboard', function (string $routeName, array $parameters) { + actingAsRootTeamMemberWithRole('member'); + + $this->get(route($routeName, $parameters))->assertRedirect(route('dashboard')); +})->with([ + 'oauth settings' => ['settings.oauth', []], + 'oauth provider settings' => ['settings.oauth.provider', ['provider' => 'authentik']], +]); + +it('shows the oauth settings page to instance admins', function (string $role) { + actingAsRootTeamMemberWithRole($role); + + $this->get(route('settings.oauth'))->assertSuccessful()->assertSee('Authentik'); +})->with(['admin', 'owner']); From 88ba00db2bcf0fdef48a05d46f76d078f625c981 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 6 Oct 2026 06:59:22 +0200 Subject: [PATCH 3/4] fix(ui): pass values to Alpine correctly in component attributes Blade does not compile @js() inside component tag attributes, so Alpine got the literal text and threw an expression error. - The environment variable editor closes again after an update - The change email button in the profile gets a valid disabled state --- resources/views/livewire/profile/index.blade.php | 2 +- .../livewire/project/shared/environment-variable/show.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/livewire/profile/index.blade.php b/resources/views/livewire/profile/index.blade.php index c92c3d970e..1c77a2d70e 100644 --- a/resources/views/livewire/profile/index.blade.php +++ b/resources/views/livewire/profile/index.blade.php @@ -134,7 +134,7 @@
+ :disabled="$uses_sso" x-bind:disabled="emailModalOpen || {{ $uses_sso ? 'true' : 'false' }}"> Change
diff --git a/resources/views/livewire/project/shared/environment-variable/show.blade.php b/resources/views/livewire/project/shared/environment-variable/show.blade.php index a79c5e5d69..d53719aca0 100644 --- a/resources/views/livewire/project/shared/environment-variable/show.blade.php +++ b/resources/views/livewire/project/shared/environment-variable/show.blade.php @@ -90,7 +90,7 @@ {{-- Open modal immediately (Alpine); decrypt value in a follow-up Livewire request. --}} + @environment-variable-updated.window="if ($event.detail.envId === {{ (int) $env->id }}) modalOpen = false">