diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f73bc8b..4f34806 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -17,6 +17,8 @@ use App\Services\Traefik\SshTraefikWriter; use App\Services\Traefik\TraefikWriter; use App\Services\Wireguard\LocalWireguardHub; use App\Services\Wireguard\WireguardHub; +use App\Http\Middleware\EnsureAdmin; +use App\Http\Middleware\EnsureCustomerActive; use App\Http\Middleware\RestrictAdminHost; use App\Mail\MaintenanceCancelledMail; use App\Models\MaintenanceNotification; @@ -57,7 +59,17 @@ class AppServiceProvider extends ServiceProvider // path-based guard would skip. Marking the host restriction persistent // makes Livewire re-apply it from the component's original route, so an // admin action cannot be driven through a public hostname. - Livewire::addPersistentMiddleware(RestrictAdminHost::class); + // Livewire re-applies only the middleware on this list when an action + // posts to /livewire/update. Its own defaults cover `auth` — but not + // ours. Without these two, a signed-in NON-operator could drive console + // components, and a suspended customer could keep driving the portal: + // the page would never load for them, but the page is not where the + // actions run. + Livewire::addPersistentMiddleware([ + RestrictAdminHost::class, + EnsureAdmin::class, + EnsureCustomerActive::class, + ]); // Send-time guard for maintenance mail (X-CP-Notification carries the // ledger id). Deliberately READ-ONLY — it never marks the row sent or diff --git a/tests/Feature/Admin/RbacTest.php b/tests/Feature/Admin/RbacTest.php index 23d25f8..a2884e5 100644 --- a/tests/Feature/Admin/RbacTest.php +++ b/tests/Feature/Admin/RbacTest.php @@ -44,3 +44,16 @@ it('lets Support retry provisioning but not manage datacenters', function () { // ...but cannot manage datacenters. Livewire::actingAs(operator('Support'))->test(Datacenters::class)->call('toggle', 'x')->assertForbidden(); }); + +it('replays the operator and account checks on every Livewire action', function () { + // Livewire re-applies only the middleware on this list when an action posts + // to /livewire/update, and its defaults cover auth but not ours. Without + // these, the console page would refuse a non-operator while the console's + // actions still answered them — the page is not where the actions run. + $persistent = app(\Livewire\Mechanisms\PersistentMiddleware\PersistentMiddleware::class) + ->getPersistentMiddleware(); + + expect($persistent)->toContain(\App\Http\Middleware\EnsureAdmin::class) + ->and($persistent)->toContain(\App\Http\Middleware\EnsureCustomerActive::class) + ->and($persistent)->toContain(\App\Http\Middleware\RestrictAdminHost::class); +});