create(); app(EnableTwoFactorAuthentication::class)($user); $user->forceFill(['two_factor_confirmed_at' => now()])->save(); $foreign = new Encrypter(random_bytes(32), 'AES-256-CBC'); $user->forceFill($brokenRecovery ? ['two_factor_recovery_codes' => $foreign->encrypt('["synthetic-only"]')] : ['two_factor_secret' => $foreign->encrypt('SYNTHETICQAONLY')])->save(); return $user; } public function test_wrong_key_secret_returns_controlled_json_and_cancels_challenge_without_authenticating(): void { $user = $this->actor(); $original = $user->getRawOriginal('two_factor_secret'); $this->postJson('/login', ['email' => $user->email, 'password' => 'password'])->assertOk()->assertJson(['two_factor' => true]); $oldSession = session()->getId(); $this->postJson('/two-factor-challenge', ['code' => '123456'])->assertUnprocessable()->assertJsonValidationErrors('code') ->assertJsonPath('message', 'Não foi possível verificar a autenticação desta conta. Inicie sessão novamente ou contacte o suporte.') ->assertSessionMissing('login.id')->assertSessionMissing('_old_input.code'); $this->assertGuest(); $this->assertNotSame($oldSession, session()->getId()); $this->assertSame($original, $user->fresh()->two_factor_secret); $this->getJson('/dashboard')->assertUnauthorized(); } public function test_wrong_key_recovery_codes_are_controlled_and_never_replaced_or_authenticated(): void { $user = $this->actor(true); $original = $user->two_factor_recovery_codes; $this->postJson('/login', ['email' => $user->email, 'password' => 'password'])->assertOk(); $this->postJson('/two-factor-challenge', ['recovery_code' => 'synthetic-only'])->assertUnprocessable()->assertJsonValidationErrors('recovery_code') ->assertSessionMissing('login.id')->assertSessionMissing('_old_input.recovery_code'); $this->assertGuest(); $this->assertSame($original, $user->fresh()->two_factor_recovery_codes); } public function test_browser_failure_redirects_to_login_with_safe_visible_message(): void { $user = $this->actor(); $this->post('/login', ['email' => $user->email, 'password' => 'password'])->assertRedirect('/two-factor-challenge'); $this->post('/two-factor-challenge', ['code' => '123456'])->assertRedirect('/login')->assertSessionHasErrors('email') ->assertSessionMissing('login.id')->assertSessionMissing('_old_input.code'); $this->assertGuest(); $this->get('/two-factor-challenge')->assertRedirect('/login'); } public function test_normal_enrollment_and_valid_mfa_still_authenticate_after_invalid_code_is_rejected(): void { $user = User::factory()->create(); app(EnableTwoFactorAuthentication::class)($user); $user->forceFill(['two_factor_confirmed_at' => now()])->save(); $secret = Fortify::currentEncrypter()->decrypt($user->two_factor_secret); $this->assertCount(8, $user->recoveryCodes()); $this->assertSame($secret, Crypt::decrypt($user->two_factor_secret)); $this->postJson('/login', ['email' => $user->email, 'password' => 'password'])->assertOk()->assertJson(['two_factor' => true]); $this->postJson('/two-factor-challenge', ['code' => 'invalid'])->assertUnprocessable(); $this->assertGuest(); $code = (new Google2FA)->getCurrentOtp($secret); $this->postJson('/two-factor-challenge', ['code' => $code])->assertNoContent(); $this->assertAuthenticatedAs($user); $this->postJson('/logout')->assertNoContent(); $this->assertGuest(); } }