79 lines
2.7 KiB
PHP
79 lines
2.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Livewire\Auth\TwoFactorSetup;
|
|
use App\Livewire\Settings\WebauthnKeys;
|
|
use App\Models\User;
|
|
use App\Models\WebauthnCredential;
|
|
use App\Services\WebauthnService;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Livewire\Livewire;
|
|
use Mockery;
|
|
use PragmaRX\Google2FAQRCode\Google2FA;
|
|
use Tests\TestCase;
|
|
|
|
class FirstFactorCodesTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function tearDown(): void
|
|
{
|
|
Mockery::close();
|
|
parent::tearDown();
|
|
}
|
|
|
|
public function test_first_totp_confirm_generates_codes_and_opens_modal(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
$g = new Google2FA;
|
|
$secret = $g->generateSecretKey();
|
|
|
|
Livewire::actingAs($user)->test(TwoFactorSetup::class)
|
|
->set('secret', $secret)
|
|
->set('code', $g->getCurrentOtp($secret))
|
|
->call('confirm')
|
|
->assertRedirect(route('settings'));
|
|
|
|
$this->assertTrue($user->fresh()->hasRecoveryCodes());
|
|
$this->assertTrue(session('open_recovery_modal'));
|
|
// The modal that opens after the redirect reveals the fresh set once. The flag is a
|
|
// timestamp (TTL-bounded) — the modal pulls it within its 10-minute window.
|
|
$this->assertIsInt(session('2fa.codes_fresh'));
|
|
}
|
|
|
|
public function test_first_key_register_generates_codes_and_opens_modal(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
$svc = Mockery::mock(WebauthnService::class);
|
|
$svc->shouldReceive('available')->andReturnTrue();
|
|
$svc->shouldReceive('verifyRegistration')->andReturnUsing(fn ($u, $a, $name) => WebauthnCredential::create([
|
|
'user_id' => $u->id, 'name' => $name, 'credential_id' => 'cid', 'public_key' => '{}', 'sign_count' => 0,
|
|
]));
|
|
app()->instance(WebauthnService::class, $svc);
|
|
|
|
Livewire::actingAs($user)->test(WebauthnKeys::class)
|
|
->set('newName', 'YubiKey 5C')
|
|
->call('register', ['dummy' => true], $svc)
|
|
->assertDispatched('openModal');
|
|
|
|
$this->assertTrue($user->fresh()->hasRecoveryCodes());
|
|
// The opening modal reveals the fresh set once. The flag is a TTL-bounded timestamp.
|
|
$this->assertIsInt(session('2fa.codes_fresh'));
|
|
}
|
|
|
|
public function test_malformed_setup_secret_yields_validation_error_not_500(): void
|
|
{
|
|
$user = User::factory()->create();
|
|
|
|
Livewire::actingAs($user)->test(TwoFactorSetup::class)
|
|
->set('secret', 'x') // client-tampered, too short for Google2FA
|
|
->set('code', '123456')
|
|
->call('confirm')
|
|
->assertHasErrors('code');
|
|
|
|
$this->assertFalse($user->fresh()->hasTotp());
|
|
}
|
|
}
|