setRouteResolver(function () use ($routeName, $path) { $route = new RoutingRoute(['GET'], $path, fn () => null); return $routeName !== null ? $route->name($routeName) : $route; }); try { (new RestrictAdminHost)->handle($request, fn () => response('through')); return 'through'; } catch (NotFoundHttpException) { return '404'; } } describe('shared host', function () { beforeEach(function () { config()->set('admin_access.hosts', ['10.10.90.185', 'localhost']); config()->set('admin_access.exclusive', false); }); it('keeps the console under /admin', function () { expect(AdminArea::prefix())->toBe('admin') ->and(AdminArea::isConsole(Request::create('http://localhost/admin/customers')))->toBeTrue() ->and(AdminArea::isConsole(Request::create('http://localhost/cloud')))->toBeFalse(); }); it('still serves the portal and the public site from that host', function () { // The regression: a machine reached by IP has that IP in ADMIN_HOSTS, // and 404ing everything else there removes the product from it. expect(guard('localhost', '/cloud', 'cloud'))->toBe('through') ->and(guard('localhost', '/', 'home'))->toBe('through') ->and(guard('localhost', '/status', 'status'))->toBe('through'); }); it('keeps the console off a host that is not listed', function () { expect(guard('www.example.test', '/admin', 'admin.overview'))->toBe('404'); }); }); describe('exclusive host', function () { beforeEach(function () { config()->set('admin_access.hosts', ['admin.example.test']); config()->set('admin_access.exclusive', true); }); it('puts the console at the root of its own host', function () { expect(AdminArea::prefix())->toBe('') ->and(AdminArea::home())->toBe('/') ->and(AdminArea::isConsole(Request::create('http://admin.example.test/customers')))->toBeTrue() ->and(AdminArea::isConsole(Request::create('http://app.example.test/customers')))->toBeFalse(); }); it('answers nothing but the console on that host', function () { expect(guard('admin.example.test', '/', 'admin.overview'))->toBe('through') ->and(guard('admin.example.test', '/customers', 'admin.customers'))->toBe('through') // The portal and the public site are gone from this hostname. ->and(guard('admin.example.test', '/cloud', 'cloud'))->toBe('404') ->and(guard('admin.example.test', '/status', 'status'))->toBe('404') ->and(guard('admin.example.test', '/dashboard', 'dashboard'))->toBe('404'); }); it('lets the shared authentication endpoints through on both hosts', function () { // Sign-in posts and Livewire's own endpoints are used by both sides. If // this list is wrong the symptom is "the console is broken", so it is // written out rather than inferred. foreach (['/login', '/logout', '/livewire/update', '/livewire/livewire.min.js', '/up'] as $path) { expect(guard('admin.example.test', $path, null))->toBe('through', $path); } }); it('keeps the console off the public hosts', function () { expect(guard('app.example.test', '/', 'admin.overview'))->toBe('404') ->and(guard('www.example.test', '/customers', 'admin.customers'))->toBe('404'); }); it('still serves the public site on its own host', function () { expect(guard('www.example.test', '/', 'home'))->toBe('through') ->and(guard('app.example.test', '/dashboard', 'dashboard'))->toBe('through'); }); }); it('never treats a request as the console just because a hostname is configured', function () { // isConsole drives the public-site switch and the network allowlist. Getting // it wrong in this direction hands the console's exemptions to the portal. config()->set('admin_access.hosts', ['admin.example.test']); config()->set('admin_access.exclusive', false); expect(AdminArea::isHostBound())->toBeTrue() ->and(AdminArea::isExclusive())->toBeFalse() ->and(AdminArea::isConsole(Request::create('http://admin.example.test/cloud')))->toBeFalse() ->and(AdminArea::isConsole(Request::create('http://admin.example.test/admin')))->toBeTrue(); });