feat: add routes, lang, tests, stubs, docs, and docker configurations
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Session fixation prevention tests.
|
||||
*
|
||||
* Verifies that all authentication flows regenerate the session ID
|
||||
* after a successful login, preventing session fixation attacks.
|
||||
*/
|
||||
|
||||
use App\Models\Role;
|
||||
use App\Models\User;
|
||||
use App\Services\SystemConfig\SystemConfigService;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Illuminate\Support\Facades\Password;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
beforeEach(function () {
|
||||
$ref = new ReflectionClass(SystemConfigService::class);
|
||||
$prop = $ref->getProperty('resolvedSettings');
|
||||
$prop->setAccessible(true);
|
||||
$prop->setValue(null, null);
|
||||
Cache::flush();
|
||||
});
|
||||
|
||||
// ── Web login ─────────────────────────────────────────────────────────────────
|
||||
|
||||
test('web login regenerates the session id', function () {
|
||||
$user = User::factory()->create();
|
||||
$before = session()->getId();
|
||||
|
||||
$this->post('/login', [
|
||||
'email' => $user->email,
|
||||
'password' => 'password',
|
||||
]);
|
||||
|
||||
// After login the session must have a different ID
|
||||
expect(session()->getId())->not->toBe($before);
|
||||
});
|
||||
|
||||
// ── 2FA verify ────────────────────────────────────────────────────────────────
|
||||
|
||||
test('2fa verify regenerates the session id', function () {
|
||||
$user = User::factory()->create();
|
||||
Session::put('auth.2fa_user_id', $user->id);
|
||||
Session::put('auth.2fa_code', '123456');
|
||||
Session::put('auth.2fa_expires_at', now()->addMinutes(10)->timestamp);
|
||||
|
||||
$before = session()->getId();
|
||||
|
||||
$this->post('/2fa', ['code' => '123456']);
|
||||
|
||||
expect(session()->getId())->not->toBe($before);
|
||||
});
|
||||
|
||||
// ── OAuth / Socialite callback ────────────────────────────────────────────────
|
||||
|
||||
test('oauth callback regenerates the session id after successful login', function () {
|
||||
Role::firstOrCreate(['name' => 'User', 'guard_name' => 'web']);
|
||||
app(SystemConfigService::class)->update(['feature_google_oauth' => true]);
|
||||
session(['social_auth_provider' => 'google']);
|
||||
|
||||
$socialUser = new Laravel\Socialite\Two\User;
|
||||
$socialUser->id = 'google-fixation-id';
|
||||
$socialUser->name = 'Fixation User';
|
||||
$socialUser->email = 'fixation@example.com';
|
||||
$socialUser->avatar = '';
|
||||
$socialUser->user = [];
|
||||
|
||||
Socialite::shouldReceive('driver->user')->andReturn($socialUser);
|
||||
|
||||
$before = session()->getId();
|
||||
|
||||
$this->get('/auth/callback')->assertRedirect('/dashboard');
|
||||
|
||||
expect(session()->getId())->not->toBe($before);
|
||||
});
|
||||
|
||||
// ── Password reset ────────────────────────────────────────────────────────────
|
||||
|
||||
test('password reset regenerates the session id', function () {
|
||||
Notification::fake();
|
||||
$user = User::factory()->create();
|
||||
|
||||
// Request a reset token
|
||||
$this->post('/forgot-password', ['email' => $user->email]);
|
||||
$token = Password::broker()->createToken($user);
|
||||
|
||||
$before = session()->getId();
|
||||
|
||||
$this->post('/reset-password', [
|
||||
'token' => $token,
|
||||
'email' => $user->email,
|
||||
'password' => 'NewSecurePass1!',
|
||||
'password_confirmation' => 'NewSecurePass1!',
|
||||
])->assertRedirect(route('login'));
|
||||
|
||||
expect(session()->getId())->not->toBe($before);
|
||||
});
|
||||
|
||||
// ── Impersonation ─────────────────────────────────────────────────────────────
|
||||
|
||||
test('starting impersonation regenerates the session id', function () {
|
||||
$perm = \App\Models\Permission::firstOrCreate(['name' => 'impersonate users', 'guard_name' => 'web']);
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->givePermissionTo($perm);
|
||||
$target = User::factory()->create(['is_active' => true]);
|
||||
|
||||
$before = session()->getId();
|
||||
|
||||
$this->actingAs($admin)->post("/impersonate/{$target->id}");
|
||||
|
||||
expect(session()->getId())->not->toBe($before);
|
||||
});
|
||||
|
||||
test('stopping impersonation regenerates the session id', function () {
|
||||
$perm = \App\Models\Permission::firstOrCreate(['name' => 'impersonate users', 'guard_name' => 'web']);
|
||||
$admin = User::factory()->create(['is_active' => true]);
|
||||
$admin->givePermissionTo($perm);
|
||||
$target = User::factory()->create(['is_active' => true]);
|
||||
|
||||
// Start impersonation first
|
||||
$this->actingAs($admin)->post("/impersonate/{$target->id}");
|
||||
|
||||
$before = session()->getId();
|
||||
|
||||
$this->post('/impersonate/stop');
|
||||
|
||||
expect(session()->getId())->not->toBe($before);
|
||||
});
|
||||
Reference in New Issue
Block a user