diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 06870f160b..10a213e5f3 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -20,11 +20,15 @@ use App\Models\S3Storage; use App\Models\ScheduledDatabaseBackup; use App\Models\Server; use App\Models\StandaloneDocker; +use App\Models\StandaloneDragonfly; +use App\Models\StandaloneKeydb; use App\Models\StandalonePostgresql; +use App\Models\StandaloneRedis; use App\Models\StandaloneSqlite; use App\Models\SwarmDocker; use App\Support\ResourceStartActivity; use App\Support\ValidationPatterns; +use Illuminate\Database\Eloquent\Collection as EloquentCollection; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -275,6 +279,14 @@ class DatabasesController extends Controller $databases = $databases->merge($project->databases($databaseRelations)); } + if ($request->attributes->get('can_read_sensitive', false) === true) { + // Their connection URLs read REDIS_PASSWORD; load the variables once per type instead of per database. + $databases + ->filter(fn ($database) => $database instanceof StandaloneRedis || $database instanceof StandaloneKeydb || $database instanceof StandaloneDragonfly) + ->groupBy(fn ($database) => $database::class) + ->each(fn ($group) => (new EloquentCollection($group->all()))->load('runtime_environment_variables')); + } + $databaseIds = $databases->pluck('id')->toArray(); $backupConfigs = ScheduledDatabaseBackup::ownedByCurrentTeamAPI($teamId)->with('latest_log') @@ -284,6 +296,7 @@ class DatabasesController extends Controller $databases = $databases->map(function ($database) use ($backupConfigs) { $database->backup_configs = $backupConfigs->get($database->getMorphClass().':'.$database->id, collect())->values(); + $database->makeHidden('runtime_environment_variables'); return $this->removeSensitiveData($database); }); diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 16eff1ba18..ad515f3fc7 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -167,10 +167,12 @@ class ProjectController extends Controller if (! $environment) { return response()->json(['message' => 'Environment not found.'], 404); } - $environment = $environment->load(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']); + // Redis connection URLs read REDIS_PASSWORD and REDIS_USERNAME; load the variables once, not per database. + $environment = $environment->load(['applications', 'postgresqls', 'redis.runtime_environment_variables', 'mongodbs', 'mysqls', 'mariadbs', 'services']); collect(['applications', 'postgresqls', 'redis', 'mongodbs', 'mysqls', 'mariadbs', 'services']) ->flatMap(fn (string $relation) => $environment->{$relation}) ->each(fn ($resource) => exposeSensitiveFields($resource)); + $environment->redis->each(fn ($redis) => $redis->makeHidden('runtime_environment_variables')); return response()->json(serializeApiResponse($environment)); } 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/app/Models/StandaloneDragonfly.php b/app/Models/StandaloneDragonfly.php index 15dea750f8..98f9ff5afc 100644 --- a/app/Models/StandaloneDragonfly.php +++ b/app/Models/StandaloneDragonfly.php @@ -369,6 +369,19 @@ class StandaloneDragonfly extends BaseModel return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } + /** + * Uses the loaded runtime_environment_variables relation when present, so lists that eager + * load it do not run a query per database. + */ + private function runtimeEnvironmentVariable(string $key): ?EnvironmentVariable + { + if ($this->relationLoaded('runtime_environment_variables')) { + return $this->runtime_environment_variables->firstWhere('key', $key); + } + + return $this->runtime_environment_variables()->where('key', $key)->first(); + } + /** * The REDIS_PASSWORD variable that sets the server password instead of the stored password. * Databases created before this release (legacy_password_quoting) keep their v4.3.23 server @@ -376,7 +389,7 @@ class StandaloneDragonfly extends BaseModel */ public function serverPasswordEnvironmentVariable(): ?EnvironmentVariable { - $environmentVariable = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first(); + $environmentVariable = $this->runtimeEnvironmentVariable('REDIS_PASSWORD'); if (! $environmentVariable) { return null; diff --git a/app/Models/StandaloneKeydb.php b/app/Models/StandaloneKeydb.php index 35818a8742..bc1e045335 100644 --- a/app/Models/StandaloneKeydb.php +++ b/app/Models/StandaloneKeydb.php @@ -370,6 +370,19 @@ class StandaloneKeydb extends BaseModel return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } + /** + * Uses the loaded runtime_environment_variables relation when present, so lists that eager + * load it do not run a query per database. + */ + private function runtimeEnvironmentVariable(string $key): ?EnvironmentVariable + { + if ($this->relationLoaded('runtime_environment_variables')) { + return $this->runtime_environment_variables->firstWhere('key', $key); + } + + return $this->runtime_environment_variables()->where('key', $key)->first(); + } + /** * The REDIS_PASSWORD variable that sets the server password instead of the stored password. * Databases created before this release (legacy_password_quoting) keep their v4.3.23 server @@ -377,7 +390,7 @@ class StandaloneKeydb extends BaseModel */ public function serverPasswordEnvironmentVariable(): ?EnvironmentVariable { - $environmentVariable = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first(); + $environmentVariable = $this->runtimeEnvironmentVariable('REDIS_PASSWORD'); if (! $environmentVariable) { return null; diff --git a/app/Models/StandaloneRedis.php b/app/Models/StandaloneRedis.php index 2e6f158cf1..b8bdbef49b 100644 --- a/app/Models/StandaloneRedis.php +++ b/app/Models/StandaloneRedis.php @@ -384,6 +384,19 @@ class StandaloneRedis extends BaseModel return $this->morphMany(EnvironmentVariable::class, 'resourceable'); } + /** + * Uses the loaded runtime_environment_variables relation when present, so lists that eager + * load it do not run a query per database. + */ + private function runtimeEnvironmentVariable(string $key): ?EnvironmentVariable + { + if ($this->relationLoaded('runtime_environment_variables')) { + return $this->runtime_environment_variables->firstWhere('key', $key); + } + + return $this->runtime_environment_variables()->where('key', $key)->first(); + } + /** * The REDIS_PASSWORD variable whose resolved value sets the server password. Databases created * before this release (legacy_password_quoting) keep their v4.3.23 server password, the stored @@ -391,7 +404,7 @@ class StandaloneRedis extends BaseModel */ public function serverPasswordEnvironmentVariable(): ?EnvironmentVariable { - $environmentVariable = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first(); + $environmentVariable = $this->runtimeEnvironmentVariable('REDIS_PASSWORD'); if (! $environmentVariable) { return null; @@ -444,7 +457,7 @@ class StandaloneRedis extends BaseModel { return new Attribute( get: function () { - $password = $this->runtime_environment_variables()->where('key', 'REDIS_PASSWORD')->first(); + $password = $this->runtimeEnvironmentVariable('REDIS_PASSWORD'); if (! $password) { return null; } @@ -459,7 +472,9 @@ class StandaloneRedis extends BaseModel { return new Attribute( get: function () { - $username = $this->runtime_environment_variables()->where('key', 'REDIS_USERNAME')->first(); + // A loaded relation does not hold a variable created by an earlier read, so check the table before creating one. + $username = $this->runtimeEnvironmentVariable('REDIS_USERNAME') + ?? $this->runtime_environment_variables()->where('key', 'REDIS_USERNAME')->first(); if (! $username) { $this->runtime_environment_variables()->create([ 'key' => 'REDIS_USERNAME', 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 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 @@