From 350d8a657194b3a761fa09d56698a1abb7481752 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Mon, 28 Sep 2026 12:07:02 +0200 Subject: [PATCH] fix(databases): refresh database form values consistently Co-Authored-By: Claude Opus 5.5 --- .../Project/Database/Clickhouse/General.php | 6 +- .../Project/Database/Dragonfly/General.php | 6 +- .../Project/Database/Keydb/General.php | 6 +- .../Project/Database/Mariadb/General.php | 9 +- .../Project/Database/Mongodb/General.php | 6 +- .../Project/Database/Mysql/General.php | 9 +- .../Project/Database/Postgresql/General.php | 6 +- .../Project/Database/Redis/General.php | 6 +- app/Traits/HasDatabaseStatusInfo.php | 1 + .../DatabaseCredentialVisibilityTest.php | 99 +++++++++++++++++++ 10 files changed, 118 insertions(+), 36 deletions(-) create mode 100644 tests/Feature/Authorization/DatabaseCredentialVisibilityTest.php diff --git a/app/Livewire/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php index 1d8354a4fb..2f87edcb2b 100644 --- a/app/Livewire/Project/Database/Clickhouse/General.php +++ b/app/Livewire/Project/Database/Clickhouse/General.php @@ -76,9 +76,6 @@ class General extends Component } $this->isPasswordHiddenForMember = auth()->user()?->isMember() ?? false; - if ($this->isPasswordHiddenForMember) { - $this->clickhouseAdminPassword = ''; - } } protected function rules(): array @@ -141,7 +138,8 @@ class General extends Component $this->name = $this->database->name; $this->description = $this->database->description; $this->clickhouseAdminUser = $this->database->clickhouse_admin_user; - $this->clickhouseAdminPassword = $this->database->clickhouse_admin_password; + $canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false; + $this->clickhouseAdminPassword = $canSeeCredentials ? $this->database->clickhouse_admin_password : ''; $this->image = $this->database->image; $this->portsMappings = $this->database->ports_mappings; $this->isPublic = $this->database->is_public; diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php index a8bde2f007..556925727d 100644 --- a/app/Livewire/Project/Database/Dragonfly/General.php +++ b/app/Livewire/Project/Database/Dragonfly/General.php @@ -74,9 +74,6 @@ class General extends Component } $this->isPasswordHiddenForMember = auth()->user()?->isMember() ?? false; - if ($this->isPasswordHiddenForMember) { - $this->dragonflyPassword = ''; - } } protected function rules(): array @@ -133,7 +130,8 @@ class General extends Component } else { $this->name = $this->database->name; $this->description = $this->database->description; - $this->dragonflyPassword = $this->database->dragonfly_password; + $canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false; + $this->dragonflyPassword = $canSeeCredentials ? $this->database->dragonfly_password : ''; $this->image = $this->database->image; $this->portsMappings = $this->database->ports_mappings; $this->isPublic = $this->database->is_public; diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php index 0398362bbb..c32a814a1a 100644 --- a/app/Livewire/Project/Database/Keydb/General.php +++ b/app/Livewire/Project/Database/Keydb/General.php @@ -76,9 +76,6 @@ class General extends Component } $this->isPasswordHiddenForMember = auth()->user()?->isMember() ?? false; - if ($this->isPasswordHiddenForMember) { - $this->keydbPassword = ''; - } } protected function rules(): array @@ -138,7 +135,8 @@ class General extends Component $this->name = $this->database->name; $this->description = $this->database->description; $this->keydbConf = $this->database->keydb_conf; - $this->keydbPassword = $this->database->keydb_password; + $canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false; + $this->keydbPassword = $canSeeCredentials ? $this->database->keydb_password : ''; $this->image = $this->database->image; $this->portsMappings = $this->database->ports_mappings; $this->isPublic = $this->database->is_public; diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php index 4d2dd9d8c2..747389522d 100644 --- a/app/Livewire/Project/Database/Mariadb/General.php +++ b/app/Livewire/Project/Database/Mariadb/General.php @@ -130,10 +130,6 @@ class General extends Component } $this->isPasswordHiddenForMember = auth()->user()?->isMember() ?? false; - if ($this->isPasswordHiddenForMember) { - $this->mariadbRootPassword = ''; - $this->mariadbPassword = ''; - } } private function syncData(bool $toModel = false): void @@ -158,9 +154,10 @@ class General extends Component } else { $this->name = $this->database->name; $this->description = $this->database->description; - $this->mariadbRootPassword = $this->database->mariadb_root_password; + $canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false; + $this->mariadbRootPassword = $canSeeCredentials ? $this->database->mariadb_root_password : ''; $this->mariadbUser = $this->database->mariadb_user; - $this->mariadbPassword = $this->database->mariadb_password; + $this->mariadbPassword = $canSeeCredentials ? $this->database->mariadb_password : ''; $this->mariadbDatabase = $this->database->mariadb_database; $this->mariadbConf = $this->database->mariadb_conf; $this->image = $this->database->image; diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php index d3545564ee..517a3e5a42 100644 --- a/app/Livewire/Project/Database/Mongodb/General.php +++ b/app/Livewire/Project/Database/Mongodb/General.php @@ -123,9 +123,6 @@ class General extends Component } $this->isPasswordHiddenForMember = auth()->user()?->isMember() ?? false; - if ($this->isPasswordHiddenForMember) { - $this->mongoInitdbRootPassword = ''; - } } private function syncData(bool $toModel = false): void @@ -151,7 +148,8 @@ class General extends Component $this->description = $this->database->description; $this->mongoConf = $this->database->mongo_conf; $this->mongoInitdbRootUsername = $this->database->mongo_initdb_root_username; - $this->mongoInitdbRootPassword = $this->database->mongo_initdb_root_password; + $canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false; + $this->mongoInitdbRootPassword = $canSeeCredentials ? $this->database->mongo_initdb_root_password : ''; $this->mongoInitdbDatabase = $this->database->mongo_initdb_database; $this->image = $this->database->image; $this->portsMappings = $this->database->ports_mappings; diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php index ce7fc01ecd..ddac8ab829 100644 --- a/app/Livewire/Project/Database/Mysql/General.php +++ b/app/Livewire/Project/Database/Mysql/General.php @@ -130,10 +130,6 @@ class General extends Component } $this->isPasswordHiddenForMember = auth()->user()?->isMember() ?? false; - if ($this->isPasswordHiddenForMember) { - $this->mysqlRootPassword = ''; - $this->mysqlPassword = ''; - } } private function syncData(bool $toModel = false): void @@ -158,9 +154,10 @@ class General extends Component } else { $this->name = $this->database->name; $this->description = $this->database->description; - $this->mysqlRootPassword = $this->database->mysql_root_password; + $canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false; + $this->mysqlRootPassword = $canSeeCredentials ? $this->database->mysql_root_password : ''; $this->mysqlUser = $this->database->mysql_user; - $this->mysqlPassword = $this->database->mysql_password; + $this->mysqlPassword = $canSeeCredentials ? $this->database->mysql_password : ''; $this->mysqlDatabase = $this->database->mysql_database; $this->mysqlConf = $this->database->mysql_conf; $this->image = $this->database->image; diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php index 3d0406956f..3d11fa6b7a 100644 --- a/app/Livewire/Project/Database/Postgresql/General.php +++ b/app/Livewire/Project/Database/Postgresql/General.php @@ -144,9 +144,6 @@ class General extends Component } $this->isPasswordHiddenForMember = auth()->user()?->isMember() ?? false; - if ($this->isPasswordHiddenForMember) { - $this->postgresPassword = ''; - } } private function syncData(bool $toModel = false): void @@ -174,7 +171,8 @@ class General extends Component $this->name = $this->database->name; $this->description = $this->database->description; $this->postgresUser = $this->database->postgres_user; - $this->postgresPassword = $this->database->postgres_password; + $canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false; + $this->postgresPassword = $canSeeCredentials ? $this->database->postgres_password : ''; $this->postgresDb = $this->database->postgres_db; $this->postgresInitdbArgs = $this->database->postgres_initdb_args; $this->postgresHostAuthMethod = $this->database->postgres_host_auth_method; diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php index 7c6313c8da..d25f875459 100644 --- a/app/Livewire/Project/Database/Redis/General.php +++ b/app/Livewire/Project/Database/Redis/General.php @@ -122,9 +122,6 @@ class General extends Component } $this->isPasswordHiddenForMember = auth()->user()?->isMember() ?? false; - if ($this->isPasswordHiddenForMember) { - $this->redisPassword = ''; - } } private function syncData(bool $toModel = false): void @@ -155,7 +152,8 @@ class General extends Component $this->customDockerRunOptions = $this->database->custom_docker_run_options; $this->redisVersion = $this->database->getRedisVersion(); $this->redisUsername = $this->database->redis_username; - $this->redisPassword = $this->database->redis_password; + $canSeeCredentials = auth()->user()?->can('update', $this->database) ?? false; + $this->redisPassword = $canSeeCredentials ? $this->database->redis_password : ''; } } diff --git a/app/Traits/HasDatabaseStatusInfo.php b/app/Traits/HasDatabaseStatusInfo.php index 5127e05958..4b84748fd4 100644 --- a/app/Traits/HasDatabaseStatusInfo.php +++ b/app/Traits/HasDatabaseStatusInfo.php @@ -82,6 +82,7 @@ trait HasDatabaseStatusInfo public function refresh(): void { $this->database->refresh(); + $this->isPasswordHiddenForMember = ! (auth()->user()?->can('update', $this->database) ?? false); if ($this->isPasswordHiddenForMember) { $this->dbUrl = null; $this->dbUrlPublic = null; diff --git a/tests/Feature/Authorization/DatabaseCredentialVisibilityTest.php b/tests/Feature/Authorization/DatabaseCredentialVisibilityTest.php new file mode 100644 index 0000000000..0ce2d7b624 --- /dev/null +++ b/tests/Feature/Authorization/DatabaseCredentialVisibilityTest.php @@ -0,0 +1,99 @@ + 0]); + + $this->team = Team::factory()->create(); + $this->owner = User::factory()->create(); + $this->member = User::factory()->create(); + $this->team->members()->attach($this->owner->id, ['role' => 'owner']); + $this->team->members()->attach($this->member->id, ['role' => 'member']); + + $server = Server::factory()->create(['team_id' => $this->team->id]); + $this->destination = StandaloneDocker::where('server_id', $server->id)->first(); + $project = Project::factory()->create(['team_id' => $this->team->id]); + $this->environment = Environment::factory()->create(['project_id' => $project->id]); +}); + +dataset('database-credential-components', [ + 'postgresql' => ['create_standalone_postgresql', PostgresqlGeneral::class, PostgresqlStatusInfo::class, ['postgres_password' => 'stored-db-credential'], ['postgresPassword']], + 'mysql' => ['create_standalone_mysql', MysqlGeneral::class, MysqlStatusInfo::class, ['mysql_root_password' => 'stored-db-credential', 'mysql_password' => 'stored-db-credential'], ['mysqlRootPassword', 'mysqlPassword']], + 'mariadb' => ['create_standalone_mariadb', MariadbGeneral::class, MariadbStatusInfo::class, ['mariadb_root_password' => 'stored-db-credential', 'mariadb_password' => 'stored-db-credential'], ['mariadbRootPassword', 'mariadbPassword']], + 'mongodb' => ['create_standalone_mongodb', MongodbGeneral::class, MongodbStatusInfo::class, ['mongo_initdb_root_password' => 'stored-db-credential'], ['mongoInitdbRootPassword']], + 'redis' => ['create_standalone_redis', RedisGeneral::class, RedisStatusInfo::class, ['redis_password' => 'stored-db-credential'], ['redisPassword']], + 'keydb' => ['create_standalone_keydb', KeydbGeneral::class, KeydbStatusInfo::class, ['keydb_password' => 'stored-db-credential'], ['keydbPassword']], + 'dragonfly' => ['create_standalone_dragonfly', DragonflyGeneral::class, DragonflyStatusInfo::class, ['dragonfly_password' => 'stored-db-credential'], ['dragonflyPassword']], + 'clickhouse' => ['create_standalone_clickhouse', ClickhouseGeneral::class, ClickhouseStatusInfo::class, ['clickhouse_admin_password' => 'stored-db-credential'], ['clickhouseAdminPassword']], +]); + +function createDatabaseWithCredential(string $createDatabase, array $credentials): mixed +{ + return $createDatabase(test()->environment->id, test()->destination, $credentials)->fresh(); +} + +it('keeps database credentials out of member state after a refresh', function (string $createDatabase, string $generalClass, string $statusInfoClass, array $credentials, array $passwordProperties) { + $database = createDatabaseWithCredential($createDatabase, $credentials); + + $this->actingAs($this->member); + session(['currentTeam' => $this->team]); + + $general = Livewire::test($generalClass, ['database' => $database]); + if (method_exists($generalClass, 'refresh')) { + $general->call('refresh'); + } + foreach ($passwordProperties as $property) { + $general->assertSet($property, ''); + } + expect(json_encode($general->snapshot))->not->toContain('stored-db-credential'); + + $statusInfo = Livewire::test($statusInfoClass, ['database' => $database]) + ->set('isPasswordHiddenForMember', false) + ->call('refresh') + ->assertSet('dbUrl', null) + ->assertSet('dbUrlPublic', null); + expect(json_encode($statusInfo->snapshot))->not->toContain('stored-db-credential'); +})->with('database-credential-components'); + +it('keeps database credentials available to an owner', function (string $createDatabase, string $generalClass, string $statusInfoClass, array $credentials, array $passwordProperties) { + $database = createDatabaseWithCredential($createDatabase, $credentials); + + $this->actingAs($this->owner); + session(['currentTeam' => $this->team]); + + $general = Livewire::test($generalClass, ['database' => $database]); + foreach ($passwordProperties as $property) { + $general->assertSet($property, 'stored-db-credential'); + } + + Livewire::test($statusInfoClass, ['database' => $database]) + ->call('refresh') + ->assertSet('dbUrl', $database->fresh()->internal_db_url); +})->with('database-credential-components');