Send an already signed-in operator back to the console, not the portal
guest:operator's authenticated-user redirect had no console-aware override, so it fell to Laravel's own default: the named 'dashboard' route, the portal. An operator revisiting admin.login while already signed in bounced to the portal's own login instead — in exclusive mode, to a 404, since dashboard isn't a console route there. redirectUsersTo() now mirrors the existing redirectGuestsTo() override right above it, keyed on the same AdminArea::isConsole($request) condition, so the two read as a pair.feat/operator-identity
parent
92dd6cc2d8
commit
f970dda0bb
|
|
@ -1,5 +1,11 @@
|
|||
<?php
|
||||
|
||||
use App\Http\Middleware\EnsureAdmin;
|
||||
use App\Http\Middleware\EnsureCustomerActive;
|
||||
use App\Http\Middleware\PublicSiteGate;
|
||||
use App\Http\Middleware\RequireOperatorTwoFactor;
|
||||
use App\Http\Middleware\RestrictAdminHost;
|
||||
use App\Http\Middleware\RestrictConsoleNetwork;
|
||||
use App\Support\AdminArea;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
|
|
@ -15,25 +21,25 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
)
|
||||
->withMiddleware(function (Middleware $middleware): void {
|
||||
$middleware->alias([
|
||||
'admin' => \App\Http\Middleware\EnsureAdmin::class,
|
||||
'admin.host' => \App\Http\Middleware\RestrictAdminHost::class,
|
||||
'customer.active' => \App\Http\Middleware\EnsureCustomerActive::class,
|
||||
'operator.2fa' => \App\Http\Middleware\RequireOperatorTwoFactor::class,
|
||||
'admin' => EnsureAdmin::class,
|
||||
'admin.host' => RestrictAdminHost::class,
|
||||
'customer.active' => EnsureCustomerActive::class,
|
||||
'operator.2fa' => RequireOperatorTwoFactor::class,
|
||||
]);
|
||||
|
||||
// Runs before the whole route stack (incl. `auth`), so /admin on a public
|
||||
// hostname 404s instead of redirecting to /login and thereby revealing
|
||||
// that an operator console is hosted here. Self-scoped to /admin*.
|
||||
$middleware->prependToGroup('web', \App\Http\Middleware\RestrictAdminHost::class);
|
||||
$middleware->prependToGroup('web', RestrictAdminHost::class);
|
||||
|
||||
// And WHO may reach it, by network address — the VPN plus whatever the
|
||||
// owner has listed in the console. The host guard above cannot answer
|
||||
// that question: a Host header is chosen by the caller.
|
||||
$middleware->prependToGroup('web', \App\Http\Middleware\RestrictConsoleNetwork::class);
|
||||
$middleware->prependToGroup('web', RestrictConsoleNetwork::class);
|
||||
|
||||
// Runs after the host guard: the console must stay reachable even while
|
||||
// the public site is switched off.
|
||||
$middleware->appendToGroup('web', \App\Http\Middleware\PublicSiteGate::class);
|
||||
$middleware->appendToGroup('web', PublicSiteGate::class);
|
||||
|
||||
// TLS is terminated by the reverse proxy (Zoraxy on the private LAN), so
|
||||
// without this Laravel sees plain http and builds http:// URLs into an
|
||||
|
|
@ -69,6 +75,19 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||
$middleware->redirectGuestsTo(
|
||||
fn (Request $request) => AdminArea::isConsole($request) ? route('admin.login') : route('login'),
|
||||
);
|
||||
|
||||
// The mirror image of redirectGuestsTo() above, same reasoning: left
|
||||
// at its default, `guest:operator` sends an already signed-in
|
||||
// operator revisiting admin.login through Laravel's own fallback —
|
||||
// the named 'dashboard' route, the PORTAL, where the web guard is
|
||||
// usually unauthenticated, bouncing them to ITS login instead. In
|
||||
// exclusive mode it is worse: dashboard is not a console route at
|
||||
// all, so RestrictAdminHost 404s it on the console host. Keyed on
|
||||
// the same AdminArea::isConsole($request) condition as the guest
|
||||
// redirect, so the two read as a pair and neither can drift from it.
|
||||
$middleware->redirectUsersTo(
|
||||
fn (Request $request) => AdminArea::isConsole($request) ? route('admin.overview') : route('dashboard'),
|
||||
);
|
||||
})
|
||||
->withExceptions(function (Exceptions $exceptions): void {
|
||||
$exceptions->shouldRenderJsonWhen(
|
||||
|
|
|
|||
|
|
@ -28,6 +28,18 @@ it('logs in with valid credentials and redirects to the dashboard', function ()
|
|||
$this->assertAuthenticatedAs($user);
|
||||
});
|
||||
|
||||
it('still sends an already signed-in customer to the portal dashboard, not the console', function () {
|
||||
// The mirror of OperatorLoginTest's "sends an already signed-in operator
|
||||
// back to the console" — the redirectUsersTo() override that fixes that
|
||||
// is keyed on AdminArea::isConsole($request), so a plain portal request
|
||||
// must stay on the path it always took.
|
||||
$user = User::factory()->create();
|
||||
|
||||
$this->actingAs($user)
|
||||
->get('/login')
|
||||
->assertRedirect('/dashboard');
|
||||
});
|
||||
|
||||
it('rejects invalid credentials', function () {
|
||||
$user = User::factory()->create(['password' => Hash::make('secret-password')]);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,21 @@ it('offers no way to register — an operator account is not self-served', funct
|
|||
->assertDontSee(__('auth.sign_up'));
|
||||
});
|
||||
|
||||
it('sends an already signed-in operator back to the console instead of the portal', function () {
|
||||
// Codex + whole-branch review (M1), independently: guest:operator's
|
||||
// authenticated-user redirect had no console-aware override, so it fell
|
||||
// to Laravel's own default — the named 'dashboard' route, the PORTAL.
|
||||
// The web guard is usually unauthenticated there, so an operator
|
||||
// revisiting admin.login bounced straight to the portal's OWN login. In
|
||||
// exclusive mode it is worse: dashboard is not a console route at all,
|
||||
// so RestrictAdminHost 404s it on the console host.
|
||||
$operator = Operator::factory()->role('Owner')->create();
|
||||
|
||||
$this->actingAs($operator, 'operator')
|
||||
->get(route('admin.login'))
|
||||
->assertRedirect(route('admin.overview'));
|
||||
});
|
||||
|
||||
it('signs an operator in on the operator guard', function () {
|
||||
$operator = Operator::factory()->role('Owner')->create(['password' => 'richtig-langes-pw']);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue