From 8304f2e7dcc2ae9a2fb55b8415367e6e7a7f00d9 Mon Sep 17 00:00:00 2001 From: nexxo Date: Sat, 25 Jul 2026 18:19:24 +0200 Subject: [PATCH] fix(admin): self-heal legacy is_admin into Owner (no RBAC bypass); validate datacenter country server-side Co-Authored-By: Claude Opus 4.8 --- app/Http/Middleware/EnsureAdmin.php | 15 ++++++++++++--- app/Livewire/Admin/Datacenters.php | 8 +++++++- app/Models/User.php | 8 ++------ routes/channels.php | 2 +- tests/Feature/Admin/RbacTest.php | 9 +++++++++ 5 files changed, 31 insertions(+), 11 deletions(-) diff --git a/app/Http/Middleware/EnsureAdmin.php b/app/Http/Middleware/EnsureAdmin.php index 1b06cb0..68d29cc 100644 --- a/app/Http/Middleware/EnsureAdmin.php +++ b/app/Http/Middleware/EnsureAdmin.php @@ -2,6 +2,7 @@ namespace App\Http\Middleware; +use App\Models\User; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; @@ -9,12 +10,20 @@ use Symfony\Component\HttpFoundation\Response; class EnsureAdmin { /** - * Only operators (is_admin) may reach the admin console. + * Only operators (a user holding an RBAC role) may reach the admin console. + * A legacy is_admin account with no role is self-healed into Owner on access + * — migrating it into RBAC rather than permanently trusting the old flag, so + * such accounts are never locked out yet the role boundary still governs. */ public function handle(Request $request, Closure $next): Response { - // console.view via RBAC, or the legacy is_admin flag as a safety net. - abort_unless((bool) $request->user()?->isOperator(), 403); + $user = $request->user(); + + if ($user instanceof User && $user->is_admin && ! $user->isOperator()) { + $user->assignRole('Owner'); + } + + abort_unless((bool) $user?->can('console.view'), 403); return $next($request); } diff --git a/app/Livewire/Admin/Datacenters.php b/app/Livewire/Admin/Datacenters.php index dd55569..0df7d61 100644 --- a/app/Livewire/Admin/Datacenters.php +++ b/app/Livewire/Admin/Datacenters.php @@ -27,7 +27,13 @@ class Datacenters extends Component // stored — otherwise `FSN` passes the rule but the lowercased insert collides. $this->code = strtolower(trim($this->code)); - $data = $this->validate(); + // Country must be one of the curated list — a forged request can bypass + // the