skipUnlessFortifyFeature(Features::twoFactorAuthentication()); } public function test_two_factor_challenge_redirects_to_login_when_not_authenticated(): void { $response = $this->get(route('two-factor.login')); $response->assertRedirect(route('login')); } public function test_two_factor_challenge_can_be_rendered(): void { Features::twoFactorAuthentication([ 'confirm' => true, 'confirmPassword' => true, ]); $user = User::factory()->create(); $user->forceFill([ 'two_factor_secret' => encrypt('test-secret'), 'two_factor_recovery_codes' => encrypt(json_encode(['code1', 'code2'])), 'two_factor_confirmed_at' => now(), ])->save(); $this->post(route('login'), [ 'email' => $user->email, 'password' => 'password', ]); $this->get(route('two-factor.login')) ->assertOk() ->assertInertia(fn (Assert $page) => $page ->component('auth/TwoFactorChallenge'), ); } public function test_valid_authenticator_code_completes_the_pending_login(): void { $user = User::factory()->withTwoFactor()->create(); $this->mock(TwoFactorAuthenticationProvider::class) ->shouldReceive('verify')->once()->with('secret', '012345')->andReturnTrue(); $this->withSession(['url.intended' => route('dashboard')]) ->post(route('login.store'), [ 'email' => $user->email, 'password' => 'password', 'remember' => true, ])->assertRedirect(route('two-factor.login')); $this->assertGuest(); $this->post(route('two-factor.login.store'), ['code' => '012345']) ->assertRedirect(route('dashboard')) ->assertSessionMissing('login.id') ->assertSessionMissing('login.remember') ->assertCookie(Auth::guard('web')->getRecallerName()); $this->assertAuthenticatedAs($user); } public function test_invalid_authenticator_code_keeps_the_pending_login_for_retry(): void { $user = User::factory()->withTwoFactor()->create(); $this->mock(TwoFactorAuthenticationProvider::class) ->shouldReceive('verify')->once()->with('secret', '000000')->andReturnFalse(); $this->withSession(['login.id' => $user->id]) ->from(route('two-factor.login')) ->post(route('two-factor.login.store'), ['code' => '000000']) ->assertRedirect(route('two-factor.login')) ->assertSessionHasErrors('code') ->assertSessionHas('login.id', $user->id); $this->assertGuest(); } public function test_recovery_code_signs_in_once_and_is_replaced(): void { $user = User::factory()->withTwoFactor()->create(); $this->withSession(['login.id' => $user->id, 'login.remember' => true]) ->post(route('two-factor.login.store'), ['recovery_code' => 'recovery-code-1']) ->assertRedirect(route('dashboard', absolute: false)) ->assertSessionMissing('login.id') ->assertCookie(Auth::guard('web')->getRecallerName()); $this->assertAuthenticatedAs($user); $this->assertNotContains('recovery-code-1', $user->fresh()->recoveryCodes()); $this->assertCount(1, $user->fresh()->recoveryCodes()); $this->post(route('logout')); $this->withSession(['login.id' => $user->id]) ->post(route('two-factor.login.store'), ['recovery_code' => 'recovery-code-1']) ->assertSessionHasErrors('recovery_code') ->assertSessionHas('login.id', $user->id); $this->assertGuest(); } public function test_challenge_submission_cannot_sign_in_without_a_pending_login(): void { $this->from(route('two-factor.login')) ->post(route('two-factor.login.store'), ['code' => '012345']) ->assertRedirect(route('two-factor.login')) ->assertSessionHasErrors('code'); $this->assertGuest(); $this->get(route('two-factor.login'))->assertRedirect(route('login')); } }