set('admin_access.hosts', ['admin.example.test']); config()->set('admin_access.exclusive', true); }); it('sends an operator signing in on the console host to the console', function () { $operator = operator('Owner'); $this->post('http://admin.example.test/login', [ 'email' => $operator->email, 'password' => 'password', ])->assertRedirect('/'); expect(auth()->check())->toBeTrue(); }); 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('still answers JSON to a JSON client', function () { // Replacing Fortify's JSON reply with a redirect breaks every caller that // is not a browser. $operator = operator('Owner'); $this->postJson('http://admin.example.test/login', [ 'email' => $operator->email, 'password' => 'password', ])->assertOk()->assertJson(['two_factor' => false]); }); 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(); });