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 <noreply@anthropic.com>
feat/phase-1-bootstrap
HomeOS Bootstrap 2026-07-17 21:55:21 +02:00
parent f00a1ac069
commit dc27b1686c
3 changed files with 28 additions and 3 deletions

View File

@ -37,6 +37,7 @@ class Show extends Component
public function saveRoom(): void public function saveRoom(): void
{ {
$this->validate(['roomId' => ['nullable', 'integer', 'exists:rooms,id']]);
$this->device->update(['room_id' => $this->roomId ?: null]); $this->device->update(['room_id' => $this->roomId ?: null]);
$this->flashMessage(__('devices.saved')); $this->flashMessage(__('devices.saved'));
} }

View File

@ -15,9 +15,17 @@ class DatabaseSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
// `?:` (not env's 2nd arg) so a present-but-empty HOMEOS_ADMIN_PASSWORD can never // The admin password must be explicit outside local/testing — never silently seed a
// seed a blank-password admin — it falls back to a non-empty dev default. // known credential in production. The dev fallback only applies to local/testing.
$adminPassword = env('HOMEOS_ADMIN_PASSWORD') ?: 'homeos-dev'; $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( User::updateOrCreate(
['email' => env('HOMEOS_ADMIN_EMAIL', 'admin@homeos.local')], ['email' => env('HOMEOS_ADMIN_EMAIL', 'admin@homeos.local')],

View File

@ -2,12 +2,14 @@
namespace Tests\Feature; namespace Tests\Feature;
use App\Livewire\Devices\Show;
use App\Models\Device; use App\Models\Device;
use App\Models\DeviceState; use App\Models\DeviceState;
use App\Models\Entity; use App\Models\Entity;
use App\Models\Room; use App\Models\Room;
use App\Models\User; use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase; use Tests\TestCase;
class HomeTest extends TestCase class HomeTest extends TestCase
@ -55,4 +57,18 @@ class HomeTest extends TestCase
->assertSee(__('dashboard.warnings_title')) ->assertSee(__('dashboard.warnings_title'))
->assertSee('Türsensor'); ->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);
}
} }