89 lines
4.1 KiB
PHP
89 lines
4.1 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature\Auth;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Contracts\Http\Kernel;
|
|
use Illuminate\Encryption\Encrypter;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Session\Middleware\StartSession;
|
|
use Laravel\Fortify\Actions\EnableTwoFactorAuthentication;
|
|
use Laravel\Fortify\Fortify;
|
|
use PragmaRX\Google2FA\Google2FA;
|
|
use Tests\TestCase;
|
|
|
|
class QaAccountSelectionTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
require_once base_path('tools/portal-qa-account-selection.php');
|
|
config(['portal.qa_synthetic_accounts_only' => true]);
|
|
$kernel = app(Kernel::class);
|
|
$kernel->appendMiddlewareToGroup('web', \PortalQaAccountSelection::class);
|
|
$kernel->addToMiddlewarePriorityAfter(StartSession::class, \PortalQaAccountSelection::class);
|
|
}
|
|
|
|
private function restored(): User
|
|
{
|
|
$user = User::factory()->create(['email' => 'restored-source@source.invalid']);
|
|
$user->forceFill(['two_factor_secret' => (new Encrypter(random_bytes(32), 'AES-256-CBC'))->encrypt('SYNTHETICONLY'),
|
|
'two_factor_confirmed_at' => now()])->save();
|
|
|
|
return $user;
|
|
}
|
|
|
|
public function test_restored_account_is_stopped_before_mfa_without_changing_its_row(): void
|
|
{
|
|
$user = $this->restored();
|
|
$original = hash('sha256', json_encode($user->fresh()->getRawOriginal(), JSON_THROW_ON_ERROR));
|
|
$this->post('/login', ['email' => $user->email, 'password' => 'password'])
|
|
->assertRedirect('/login')->assertSessionHasErrors('email')->assertSessionMissing('login.id');
|
|
$this->assertGuest();
|
|
$this->assertSame($original, hash('sha256', json_encode($user->fresh()->getRawOriginal(), JSON_THROW_ON_ERROR)));
|
|
}
|
|
|
|
public function test_json_restored_login_and_stale_challenge_remain_unauthenticated(): void
|
|
{
|
|
$user = $this->restored();
|
|
$this->postJson('/login', ['email' => $user->email, 'password' => 'password'])
|
|
->assertUnprocessable()->assertJsonValidationErrors('email')->assertSessionMissing('login.id');
|
|
$this->withSession(['login.id' => $user->id])->get('/two-factor-challenge')
|
|
->assertRedirect('/login')->assertSessionHasErrors('email')->assertSessionMissing('login.id');
|
|
$this->assertGuest();
|
|
}
|
|
|
|
public function test_synthetic_login_requires_valid_mfa_then_recovers_after_logout(): void
|
|
{
|
|
$user = User::factory()->create(['email' => 'synthetic-investor@example.invalid']);
|
|
app(EnableTwoFactorAuthentication::class)($user);
|
|
$user->forceFill(['two_factor_confirmed_at' => now()])->save();
|
|
$secret = Fortify::currentEncrypter()->decrypt($user->two_factor_secret);
|
|
$credentials = ['email' => $user->email, 'password' => 'password'];
|
|
$this->post('/login', $credentials)->assertRedirect('/two-factor-challenge');
|
|
$this->assertGuest();
|
|
$this->post('/two-factor-challenge', ['code' => 'invalid'])->assertRedirect('/two-factor-challenge');
|
|
$this->assertGuest();
|
|
$engine = new Google2FA;
|
|
$this->post('/two-factor-challenge', ['code' => $engine->getCurrentOtp($secret)])->assertRedirect('/dashboard');
|
|
$this->assertAuthenticatedAs($user);
|
|
$this->get('/dashboard')->assertOk();
|
|
$this->post('/logout')->assertRedirect('/');
|
|
$this->assertGuest();
|
|
$this->post('/login', $credentials)->assertRedirect('/two-factor-challenge');
|
|
// A new login uses a fresh code in Fortify's normal accepted window, not the consumed code.
|
|
$this->post('/two-factor-challenge', ['code' => $engine->oathTotp($secret, $engine->getTimestamp() + 1)])->assertRedirect('/dashboard');
|
|
$this->assertAuthenticatedAs($user);
|
|
}
|
|
|
|
public function test_qa_account_policy_does_not_apply_when_flag_is_absent(): void
|
|
{
|
|
config(['portal.qa_synthetic_accounts_only' => false]);
|
|
$user = User::factory()->create(['email' => 'ordinary-user@source.invalid']);
|
|
$this->post('/login', ['email' => $user->email, 'password' => 'password'])->assertRedirect('/dashboard');
|
|
$this->assertAuthenticatedAs($user);
|
|
}
|
|
}
|