clusev/tests/Feature/Settings/SecurityConfirmTest.php

79 lines
2.8 KiB
PHP

<?php
namespace Tests\Feature\Settings;
use App\Livewire\Settings\Security;
use App\Models\User;
use App\Support\Confirm\ConfirmToken;
use App\Support\Confirm\InvalidConfirmToken;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;
use Tests\TestCase;
/**
* The apply handler is the security gate: it only acts on a CONFIRMED, single-use
* token. A direct dispatch (bypassing the modal), an unconfirmed token, or a replay
* must all be a no-op.
*/
class SecurityConfirmTest extends TestCase
{
use RefreshDatabase;
private function userWithTotp(): User
{
return User::factory()->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);
}
}