fix: unique customer↔user link + reject inactive datacenters on host create

- customers.user_id unique so impersonation/billing can't cross customers
- HostCreate validates datacenter is active (exists rule + active=1)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat/portal-design
nexxo 2026-07-25 13:56:07 +02:00
parent b44e224bee
commit 80348d6a3f
3 changed files with 39 additions and 1 deletions

View File

@ -13,7 +13,7 @@ class HostCreate extends Component
#[Validate('required|string|max:255')]
public string $name = '';
#[Validate('required|string|exists:datacenters,code')]
#[Validate('required|string|exists:datacenters,code,active,1')]
public string $datacenter = '';
public function mount(): void

View File

@ -0,0 +1,27 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
/**
* One customer per portal user, so impersonation and billing can't cross
* customer boundaries. (customers.email is already unique, giving a 1:1
* customer↔email mapping; this guarantees the same for the user link.)
*/
return new class extends Migration
{
public function up(): void
{
Schema::table('customers', function (Blueprint $table) {
$table->unique('user_id'); // nullable — SQL permits multiple NULLs
});
}
public function down(): void
{
Schema::table('customers', function (Blueprint $table) {
$table->dropUnique(['user_id']);
});
}
};

View File

@ -59,3 +59,14 @@ it('offers only known datacenter codes when adding a host', function () {
->test(HostCreate::class)
->assertSet('datacenter', 'fsn');
});
it('rejects creating a host in an inactive datacenter', function () {
Datacenter::query()->firstOrCreate(['code' => 'fsn'], ['name' => 'Falkenstein']);
Datacenter::factory()->create(['code' => 'old', 'active' => false]);
Livewire::actingAs(admin())
->test(HostCreate::class)
->set('name', 'pve-old')->set('datacenter', 'old')->set('public_ip', '203.0.113.6')->set('root_password', 'supersecret')
->call('save')
->assertHasErrors(['datacenter']);
});