Files
SealShare/tests/Feature/Auth/AuthenticationTest.php
T
surtic86andClaude Opus 5.5 84c8b81f0a Fix the security audit's findings
Fixes the nine findings of security audit run 1 on 2.4.1 (T1-T11).

F1: Uploads that are not complete now count only the bytes the server
received, and a chunk that goes above the storage quota gets 422. An
anonymous uploader can no longer fill the quota with declared sizes.

F2: The share password and the admin login have a second limit without
the address (20 attempts per minute per share and per e-mail), because
a forged X-Forwarded-For changes the address.

F3: The system password gate and the admin check now also run on
Livewire updates, so an old uploader snapshot cannot register files.

F4: The session keeps a fingerprint of the system password's hash.
When the admin changes the system password, earlier verifications end.

F5: The page after the upload opens only in the session that created
the share, and only while recipients can still reach the share.

F6: The share password limit counts an attempt before the password is
checked, so parallel attempts cannot pass it.

F7: The system password has the same two limits (5 per address, 20 in
total per minute).

F8: A password change or reset signs the user's other sessions out.

F9: The only admin cannot delete the account, which would open /setup
to every visitor.

The setup wizard also holds the admin password to the password
defaults, and Livewire applies EnsureAdmin to updates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 14:46:38 +02:00

92 lines
2.4 KiB
PHP

<?php
use App\Models\User;
use Laravel\Fortify\Features;
test('login screen can be rendered', function () {
$response = $this->get(route('login'));
$response->assertOk();
});
test('users can authenticate using the login screen', function () {
$user = User::factory()->create();
$response = $this->post(route('login.store'), [
'email' => $user->email,
'password' => 'password',
]);
$response
->assertSessionHasNoErrors()
->assertRedirect(route('admin.dashboard', absolute: false));
$this->assertAuthenticated();
});
test('users can not authenticate with invalid password', function () {
$user = User::factory()->create();
$response = $this->post(route('login.store'), [
'email' => $user->email,
'password' => 'wrong-password',
]);
$response->assertSessionHasErrorsIn('email');
$this->assertGuest();
});
test('the login limit per e-mail holds when each attempt claims another address', function () {
$user = User::factory()->create();
for ($i = 1; $i <= 20; $i++) {
$response = $this->withHeader('X-Forwarded-For', "203.0.113.$i")->post(route('login.store'), [
'email' => $user->email,
'password' => 'wrong-password',
]);
if ($i === 1) {
expect($response->status())->not->toBe(429);
}
}
$this->withHeader('X-Forwarded-For', '203.0.113.21')->post(route('login.store'), [
'email' => $user->email,
'password' => 'password',
])->assertTooManyRequests();
$this->assertGuest();
});
test('users with two factor enabled are redirected to two factor challenge', function () {
if (! Features::canManageTwoFactorAuthentication()) {
$this->markTestSkipped('Two-factor authentication is not enabled.');
}
Features::twoFactorAuthentication([
'confirm' => true,
'confirmPassword' => true,
]);
$user = User::factory()->withTwoFactor()->create();
$response = $this->post(route('login.store'), [
'email' => $user->email,
'password' => 'password',
]);
$response->assertRedirect(route('two-factor.login'));
$this->assertGuest();
});
test('users can logout', function () {
$user = User::factory()->create();
$response = $this->actingAs($user)->post(route('logout'));
$response->assertRedirect(route('home'));
$this->assertGuest();
});