create([ 'two_factor_secret' => 'S', 'two_factor_confirmed_at' => now(), ]); } public function test_direct_handler_call_with_a_garbage_token_does_not_disable_2fa(): void { $user = $this->userWithTotp(); Livewire::actingAs($user)->test(Security::class)->call('disableTwoFactor', 'forged-token'); $this->assertTrue($user->fresh()->hasTotp(), 'forged token must not disable 2FA'); } public function test_an_unconfirmed_token_is_rejected_by_the_handler(): void { // Issuing a token (the opener) is not enough — the modal's confirm() must run // first. This closes the "skip the confirmation modal" bypass. $user = $this->userWithTotp(); $this->actingAs($user); $token = ConfirmToken::issue('twoFactorDisabled', [], 'two_factor.disable', $user->email); Livewire::actingAs($user->fresh())->test(Security::class)->call('disableTwoFactor', $token); $this->assertTrue($user->fresh()->hasTotp(), 'a never-confirmed token must not apply'); } public function test_a_confirmed_token_disables_2fa(): void { $user = $this->userWithTotp(); $this->actingAs($user); $token = ConfirmToken::issue('twoFactorDisabled', [], 'two_factor.disable', $user->email); ConfirmToken::confirm($token); // the modal confirmation step Livewire::actingAs($user->fresh())->test(Security::class)->call('disableTwoFactor', $token); $this->assertFalse($user->fresh()->hasTotp(), 'a confirmed token must disable 2FA'); } public function test_the_handler_burns_the_token_so_it_cannot_be_replayed(): void { $user = $this->userWithTotp(); $this->actingAs($user); $token = ConfirmToken::issue('twoFactorDisabled', [], 'two_factor.disable', $user->email); ConfirmToken::confirm($token); Livewire::actingAs($user->fresh())->test(Security::class)->call('disableTwoFactor', $token); $this->assertFalse($user->fresh()->hasTotp()); // Burned: the same token can no longer be confirmed or consumed. $this->expectException(InvalidConfirmToken::class); ConfirmToken::confirm($token); } }