56 lines
2.6 KiB
PHP
56 lines
2.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Session\ArraySessionHandler;
|
|
use Illuminate\Session\Store;
|
|
use Laravel\Fortify\Contracts\TwoFactorAuthenticationProvider;
|
|
use PragmaRX\Google2FA\Google2FA;
|
|
use Symfony\Component\HttpFoundation\Response;
|
|
use Tests\TestCase;
|
|
|
|
class PortalQaMfaTracePrivacyTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_diagnostic_distinguishes_replay_without_consuming_code_or_authenticating(): void
|
|
{
|
|
require_once base_path('tools/portal-qa-auth-trace.php');
|
|
config(['cache.default' => 'array']);
|
|
$engine = new Google2FA;
|
|
$secret = $engine->generateSecretKey();
|
|
User::factory()->create(['id' => 18, 'email' => 'investor-mfa-qa@example.invalid', 'two_factor_secret' => encrypt($secret), 'two_factor_confirmed_at' => now()]);
|
|
$code = $engine->getCurrentOtp($secret);
|
|
$request = Request::create('/two-factor-challenge', 'POST', ['code' => $code]);
|
|
$session = new Store('qa_trace_unit', new ArraySessionHandler(120));
|
|
$session->start();
|
|
$session->put('login.id', 18);
|
|
$request->setLaravelSession($session);
|
|
$request->attributes->set('qa_auth_trace', 'unit');
|
|
$trace = new \PortalQaSessionTrace;
|
|
$trace->handle($request, fn () => new Response('controlled'));
|
|
$first = $request->attributes->get('qa_mfa_input_check');
|
|
$this->assertTrue($first['secret_decrypts']);
|
|
$this->assertTrue($first['matches_configured_time_window']);
|
|
$this->assertFalse($first['previously_used_in_cache']);
|
|
$this->assertTrue($first['accepted_by_replay_check']);
|
|
$this->assertFalse(auth()->check());
|
|
// Fortify must still accept the unused code: the diagnostic cannot consume it.
|
|
$this->assertTrue(app(TwoFactorAuthenticationProvider::class)->verify($secret, $code));
|
|
$trace->handle($request, fn () => new Response('controlled'));
|
|
$second = $request->attributes->get('qa_mfa_input_check');
|
|
$this->assertTrue($second['matches_configured_time_window']);
|
|
$this->assertTrue($second['previously_used_in_cache']);
|
|
$this->assertFalse($second['accepted_by_replay_check']);
|
|
$this->assertFalse(auth()->check());
|
|
$encoded = json_encode($second, JSON_THROW_ON_ERROR);
|
|
foreach ([$secret, $code, md5($code)] as $value) {
|
|
$this->assertStringNotContainsString($value, $encoded);
|
|
}
|
|
$this->assertSame(18, $session->get('login.id'));
|
|
}
|
|
}
|