seed(RolePermissionSeeder::class); config()->set('pireon.password_grant_enabled', true); } #[Test] public function correct_credentials_sign_somebody_in(): void { $this->fakePireon(); Livewire::test(PireonLogin::class) ->fillForm([ 'email' => 'ada@example.com', 'password' => 'correcta', 'remember' => true, ]) ->call('authenticate') ->assertHasNoFormErrors(); $this->assertTrue(Auth::check()); // The local account is provisioned on the way in, so the very first // page already has roles to authorise against. $user = User::query()->where('email', 'ada@example.com')->sole(); $this->assertSame(Auth::id(), $user->id); $this->assertSame(4242, $user->oauth_id); } #[Test] public function the_password_goes_to_the_identity_server_and_nowhere_else(): void { $this->fakePireon(); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 's3cr3t']) ->call('authenticate'); Http::assertSent(fn ($request) => str_contains($request->url(), '/oauth/token') && $request['grant_type'] === 'password' && $request['password'] === 's3cr3t'); // It is never written down: the local row has no usable password. $user = User::query()->where('email', 'ada@example.com')->sole(); $this->assertNotSame('s3cr3t', $user->password); } #[Test] public function wrong_credentials_say_the_same_thing_as_an_unknown_address(): void { Http::fake([ "{$this->pireonServer}/oauth/token" => Http::response(['error' => 'invalid_grant'], 401), ]); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'quien@example.com', 'password' => 'mala']) ->call('authenticate') ->assertHasFormErrors(['email']); // The identity server distinguishes the two; repeating that here would // turn the form into a way of finding out who has an account. $this->assertFalse(Auth::check()); } #[Test] public function an_identity_server_that_breaks_is_not_blamed_on_the_password(): void { // What a Passport server answers when it cannot sign a token at all — // missing keys, say. It has judged nothing about these credentials. Http::fake([ "{$this->pireonServer}/oauth/token" => Http::response(['message' => 'Server Error'], 500), ]); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'correcta']) ->call('authenticate') ->assertHasFormErrors(['email' => __('pireon.server_unavailable')]); // Saying "those credentials are wrong" here sends somebody to check a // password that was never the problem. $this->assertFalse(Auth::check()); } #[Test] public function an_identity_server_that_cannot_be_reached_says_so_too(): void { Http::fake(fn () => throw new ConnectionException('Connection refused')); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'correcta']) ->call('authenticate') ->assertHasFormErrors(['email' => __('pireon.server_unavailable')]); } #[Test] public function the_api_token_endpoint_makes_the_same_distinction(): void { Http::fake([ "{$this->pireonServer}/oauth/token" => Http::response(['message' => 'Server Error'], 500), ]); // 503, not 401: nothing was decided about these credentials. $this->postJson('/api/v1/auth/token', [ 'grant_type' => 'password', 'username' => 'ada@example.com', 'password' => 'correcta', ])->assertStatus(503); } #[Test] public function a_deactivated_account_is_told_why(): void { $this->fakePireon(['is_active' => false]); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'correcta']) ->call('authenticate') ->assertHasFormErrors(['email' => __('pireon.inactive')]); // Said only once the password was right, so it reveals nothing to // somebody guessing. $this->assertFalse(Auth::check()); } #[Test] public function an_account_still_awaiting_approval_is_told_so(): void { $this->fakePireon(['approved_at' => null]); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'correcta']) ->call('authenticate') ->assertHasFormErrors(['email' => __('pireon.pending_approval')]); $this->assertFalse(Auth::check()); } #[Test] public function a_valid_account_with_no_role_here_is_still_refused(): void { // Nothing grants the default role in this test, so the user arrives // with no way into the panel. config()->set('pireon.default_role', null); $this->fakePireon(); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'correcta']) ->call('authenticate') ->assertHasFormErrors(['email' => __('pireon.no_permission')]); // Being a valid account upstream is not the same as being allowed in // here: roles are this application's business. $this->assertFalse(Auth::check()); } #[Test] public function it_says_so_plainly_when_password_sign_in_is_switched_off(): void { config()->set('pireon.password_grant_enabled', false); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'correcta']) ->call('authenticate') ->assertHasFormErrors(['email' => __('pireon.password_grant_disabled')]); // A misconfiguration is an operator's problem, not a visitor's, so it // does not masquerade as "those credentials are incorrect". Http::assertNothingSent(); } #[Test] public function it_stops_after_five_attempts(): void { Http::fake([ "{$this->pireonServer}/oauth/token" => Http::response(['error' => 'invalid_grant'], 401), ]); $component = Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'mala']); for ($attempt = 0; $attempt < 5; $attempt++) { $component->call('authenticate')->assertHasFormErrors(['email']); } // The sixth is refused here rather than spending the identity // server's own allowance. Http::fake(); $component->call('authenticate'); Http::assertNothingSent(); } #[Test] public function the_session_id_is_rotated_on_the_way_in(): void { $this->fakePireon(); $before = session()->getId(); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'correcta']) ->call('authenticate'); // A session fixated before signing in must not survive now that it // carries an identity. $this->assertNotSame($before, session()->getId()); } #[Test] public function the_users_language_follows_them_into_the_panel(): void { $this->fakePireon(); // A pre-existing account: the default role is only granted to brand // new identities, so this one needs one of its own to get in. User::factory() ->create(['email' => 'ada@example.com', 'oauth_id' => 4242, 'locale' => 'ca']) ->assignRole('viewer'); Livewire::test(PireonLogin::class) ->fillForm(['email' => 'ada@example.com', 'password' => 'correcta']) ->call('authenticate'); $this->assertSame('ca', session(config('locales.session_key'))); } }