From dc27b1686c8894ba689807c9e6a2c266ab329298 Mon Sep 17 00:00:00 2001 From: HomeOS Bootstrap Date: Fri, 17 Jul 2026 21:55:21 +0200 Subject: [PATCH] Fix R15: no weak admin fallback in prod; validate device room update - DatabaseSeeder throws outside local/testing when HOMEOS_ADMIN_PASSWORD is unset, instead of silently seeding the documented `homeos-dev` password (bootstrap always runs migrate --seed, so a prod misconfig must fail loudly). - Devices\Show::saveRoom validates roomId as nullable|exists:rooms,id, so a crafted request can no longer trigger a foreign-key 500. Added a feature test. Co-Authored-By: Claude Opus 4.8 --- app/Livewire/Devices/Show.php | 1 + database/seeders/DatabaseSeeder.php | 14 +++++++++++--- tests/Feature/HomeTest.php | 16 ++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/Livewire/Devices/Show.php b/app/Livewire/Devices/Show.php index 3adab45..1c33714 100644 --- a/app/Livewire/Devices/Show.php +++ b/app/Livewire/Devices/Show.php @@ -37,6 +37,7 @@ class Show extends Component public function saveRoom(): void { + $this->validate(['roomId' => ['nullable', 'integer', 'exists:rooms,id']]); $this->device->update(['room_id' => $this->roomId ?: null]); $this->flashMessage(__('devices.saved')); } diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index d01e96e..180d3ff 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -15,9 +15,17 @@ class DatabaseSeeder extends Seeder */ public function run(): void { - // `?:` (not env's 2nd arg) so a present-but-empty HOMEOS_ADMIN_PASSWORD can never - // seed a blank-password admin — it falls back to a non-empty dev default. - $adminPassword = env('HOMEOS_ADMIN_PASSWORD') ?: 'homeos-dev'; + // The admin password must be explicit outside local/testing — never silently seed a + // known credential in production. The dev fallback only applies to local/testing. + $adminPassword = env('HOMEOS_ADMIN_PASSWORD'); + + if (blank($adminPassword)) { + if (! app()->environment('local', 'testing')) { + throw new \RuntimeException('HOMEOS_ADMIN_PASSWORD must be set to seed the admin account outside local/testing.'); + } + + $adminPassword = 'homeos-dev'; + } User::updateOrCreate( ['email' => env('HOMEOS_ADMIN_EMAIL', 'admin@homeos.local')], diff --git a/tests/Feature/HomeTest.php b/tests/Feature/HomeTest.php index 22c92ea..acb7f14 100644 --- a/tests/Feature/HomeTest.php +++ b/tests/Feature/HomeTest.php @@ -2,12 +2,14 @@ namespace Tests\Feature; +use App\Livewire\Devices\Show; use App\Models\Device; use App\Models\DeviceState; use App\Models\Entity; use App\Models\Room; use App\Models\User; use Illuminate\Foundation\Testing\RefreshDatabase; +use Livewire\Livewire; use Tests\TestCase; class HomeTest extends TestCase @@ -55,4 +57,18 @@ class HomeTest extends TestCase ->assertSee(__('dashboard.warnings_title')) ->assertSee('Türsensor'); } + + public function test_device_room_update_rejects_invalid_room(): void + { + $device = Device::create(['name' => 'Lampe', 'status' => 'active', 'last_seen_at' => now()]); + + $this->actingAs(User::factory()->create()); + + Livewire::test(Show::class, ['device' => $device]) + ->set('roomId', 999999) + ->call('saveRoom') + ->assertHasErrors('roomId'); + + $this->assertNull($device->fresh()->room_id); + } }