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. --}}
id)) modalOpen = false">
+ @environment-variable-updated.window="if ($event.detail.envId === {{ (int) $env->id }}) modalOpen = false">