124 lines
4.8 KiB
PHP
124 lines
4.8 KiB
PHP
<?php
|
|
|
|
use App\Models\Customer;
|
|
use App\Models\User;
|
|
|
|
/**
|
|
* Signing in lands where it was performed.
|
|
*
|
|
* Fortify sends everyone to /dashboard. That is why an operator who signed in
|
|
* ended up in the customer portal — and once the console has a hostname to
|
|
* itself, /dashboard does not exist there at all, so it would have been a 404.
|
|
*/
|
|
|
|
beforeEach(function () {
|
|
config()->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();
|
|
});
|