85 lines
4.2 KiB
PHP
85 lines
4.2 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Auth;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Encryption\Encrypter;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Crypt;
|
|
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
|
|
use Laravel\Fortify\Fortify;
|
|
use PragmaRX\Google2FA\Google2FA;
|
|
use Tests\TestCase;
|
|
|
|
class TwoFactorDecryptionFailureTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function actor(bool $brokenRecovery = false): User
|
|
{
|
|
$user = User::factory()->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();
|
|
}
|
|
}
|