presseportale/tests/Feature/Auth/TwoFactorLoginTest.php
Kevin Adametz ae79d5bee4 Security: JSON-Login durchläuft die is_active-/Verifizierungschecks
RoleAwareLoginResponse gab bei wantsJson() sofort 204 zurück – VOR den
Sicherheitschecks. Ein XHR/JSON-Login eines verifiziert-inaktiven Accounts
erhielt damit eine Session ohne Logout. Checks laufen jetzt zuerst:
verifiziert-inaktiv → Logout + Session-Invalidate + 403 (JSON) bzw. Login mit
Fehler (HTML); unverifiziert → 204 (JSON) bzw. Notice (HTML); danach der
Erfolgsfall.

Tests: JSON-Login eines inaktiven Accounts (403, guest), JSON-Login eines
aktiven Users (204, authentifiziert).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-16 10:19:32 +00:00

101 lines
3.3 KiB
PHP

<?php
use App\Models\User;
use Database\Seeders\RolesAndPermissionsSeeder;
use Livewire\Volt\Volt;
use PragmaRX\Google2FA\Google2FA;
use Tests\TestCase;
function enableTwoFactor(User $user): string
{
$secret = app(Google2FA::class)->generateSecretKey();
$user->forceFill([
'two_factor_secret' => encrypt($secret),
'two_factor_confirmed_at' => now(),
])->save();
return $secret;
}
test('a user with two-factor enabled is handed to the challenge, not logged in', function () {
/** @var TestCase $this */
$user = User::factory()->create(['is_active' => true]);
enableTwoFactor($user);
Volt::test('auth.login')
->set('email', $user->email)
->set('password', 'password')
->call('login')
->assertHasNoErrors()
->assertRedirect(route('two-factor.challenge'));
$this->assertGuest();
expect(session('login.id'))->toBe($user->id);
});
test('the challenge page redirects to login without a pending challenge', function () {
/** @var TestCase $this */
$this->get(route('two-factor.challenge'))->assertRedirect(route('login'));
});
test('a valid two-factor code completes login with a role-aware redirect', function () {
/** @var TestCase $this */
$this->seed(RolesAndPermissionsSeeder::class);
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$secret = enableTwoFactor($customer);
$otp = app(Google2FA::class)->getCurrentOtp($secret);
$this->withSession(['login.id' => $customer->id, 'login.remember' => false])
->post('/two-factor-challenge', ['code' => $otp])
->assertRedirect(route('me.dashboard', absolute: false));
$this->assertAuthenticatedAs($customer);
});
test('the fortify login post blocks inactive but verified users', function () {
/** @var TestCase $this */
$user = User::factory()->create(['is_active' => false]);
$this->post('/login', ['email' => $user->email, 'password' => 'password'])
->assertRedirect(route('login'));
$this->assertGuest();
});
test('a json login does not grant a session to an inactive verified account', function () {
/** @var TestCase $this */
$user = User::factory()->create(['is_active' => false]);
$this->postJson('/login', ['email' => $user->email, 'password' => 'password'])
->assertStatus(403);
$this->assertGuest();
});
test('a json login for an active user succeeds with 204', function () {
/** @var TestCase $this */
$this->seed(RolesAndPermissionsSeeder::class);
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$this->postJson('/login', ['email' => $customer->email, 'password' => 'password'])
->assertNoContent();
$this->assertAuthenticatedAs($customer);
});
test('the fortify login post keeps a customer out of the admin area on stale intended', function () {
/** @var TestCase $this */
$this->seed(RolesAndPermissionsSeeder::class);
$customer = User::factory()->create(['is_active' => true]);
$customer->assignRole('customer');
$this->withSession(['url.intended' => url('/admin/users')])
->post('/login', ['email' => $customer->email, 'password' => 'password'])
->assertRedirect(route('me.dashboard', absolute: false));
$this->assertAuthenticatedAs($customer);
});