set('admin_access.hosts', ['admin.example.test']); config()->set('admin_access.exclusive', true); }); it('refuses an operator at the shared portal login — the account is not in that table any more', function () { // Before the identity move this landed in the console (that is what this // file used to prove). Now there is no operator row Fortify's web-guard // attempt could ever match, on any host — refusing it here is the correct // behaviour, not a gap Task 5's dedicated console login has yet to close. $operator = operator('Owner'); $this->post('http://admin.example.test/login', [ 'email' => $operator->email, 'password' => 'password', ])->assertSessionHasErrors('email'); expect(auth()->check())->toBeFalse(); }); it('sends a customer signing in on the portal host to the portal', function () { $user = User::factory()->create(); $this->post('http://app.example.test/login', [ 'email' => $user->email, 'password' => 'password', ])->assertRedirect('/dashboard'); }); it('refuses to leave a customer holding a session on the console host', function () { // Fortify authenticates before anything downstream can object, so the // session already exists. Refusing each page afterwards would leave it in // the browser; it is taken away at the moment it was created. $customer = Customer::factory()->create(); $user = $customer->ensureUser(); $user->forceFill(['password' => bcrypt('password')])->save(); $this->post('http://admin.example.test/login', [ 'email' => $user->email, 'password' => 'password', ])->assertSessionHasErrors('email'); expect(auth()->check())->toBeFalse(); }); it('makes the same decision after a two-factor challenge', function () { // Fortify never reaches LoginResponse on this path. Binding only that one // left the accounts most likely to be operators landing in the portal. $operator = operator('Owner'); $response = app(Laravel\Fortify\Contracts\TwoFactorLoginResponse::class) ->toResponse(tap(request(), function ($request) use ($operator) { $request->headers->set('HOST', 'admin.example.test'); $request->setUserResolver(fn () => $operator); })); // The console root on the console host — emphatically not the portal. expect($response->getTargetUrl())->toContain('admin.example.test') ->and($response->getTargetUrl())->not->toContain('/dashboard'); }); it('gives a JSON client the same refusal as a browser gets', function () { // bootstrap/app.php restricts JSON exception rendering to api/* paths, so // a failed /login attempt redirects with session errors regardless of the // caller's Accept header — there is no separate JSON error shape here for // an operator's address to diverge from. What still matters is that a // JSON-flavoured request does not somehow slip past that and end up // authenticated. $operator = operator('Owner'); $this->postJson('http://admin.example.test/login', [ 'email' => $operator->email, 'password' => 'password', ])->assertRedirect(); expect(auth()->check())->toBeFalse(); }); it('does not let a JSON client keep a session a browser would lose', function () { // Answering JSON early skipped the permission check entirely. $customer = Customer::factory()->create(); $user = $customer->ensureUser(); $user->forceFill(['password' => bcrypt('password')])->save(); $this->postJson('http://admin.example.test/login', [ 'email' => $user->email, 'password' => 'password', ])->assertForbidden(); expect(auth()->check())->toBeFalse(); }); it('removes the session of a non-operator who was heading for /admin on a shared host', function () { // In shared mode everyone posts to /login, so the request never looks like // the console. Without reading the intended destination, a non-operator was // authenticated, shown a 403 — and left holding the session. config()->set('admin_access.exclusive', false); config()->set('admin_access.hosts', []); $customer = Customer::factory()->create(); $user = $customer->ensureUser(); $user->forceFill(['password' => bcrypt('password')])->save(); $this->get('/admin')->assertRedirect('/login'); $this->post('/login', ['email' => $user->email, 'password' => 'password']) ->assertSessionHasErrors('email'); expect(auth()->check())->toBeFalse(); });