diff --git a/app/Models/User.php b/app/Models/User.php index 07ebb5f..f059f1e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -152,7 +152,10 @@ class User extends Authenticatable } /** - * Cek apakah token sesi aktif masih "hidup" (session file/DB masih ada). + * Cek apakah token sesi aktif masih "hidup". + * Sesi dianggap hidup hanya jika file/row masih ada DAN belum lewat masa + * hidup (session.lifetime). Sesi yang sudah kadaluarsa dianggap mati, + * sehingga user bebas login lagi di perangkat mana pun. * Driver array/lain → dianggap hidup (aman, tolak login ganda). */ public function sessionTokenIsAlive(): bool @@ -162,12 +165,31 @@ class User extends Authenticatable return false; } + $lifetimeSeconds = (int) config('session.lifetime') * 60; + return match (config('session.driver')) { - 'file' => file_exists(storage_path('framework/sessions/'.$token)), + 'file' => $this->fileSessionIsAlive($token, $lifetimeSeconds), 'database' => \Illuminate\Support\Facades\DB::table(config('session.table', 'sessions')) ->where('id', $token) + ->where('last_activity', '>=', time() - $lifetimeSeconds) ->exists(), default => true, }; } + + /** + * Cek sesi berbasis file: file ada DAN aktivitas terakhir (mtime) masih + * dalam masa hidup. Laravel menulis ulang file sesi di setiap request, + * jadi mtime = aktivitas terakhir user. + */ + protected function fileSessionIsAlive(string $token, int $lifetimeSeconds): bool + { + $path = storage_path('framework/sessions/'.$token); + + if (! is_file($path)) { + return false; + } + + return (time() - (int) filemtime($path)) <= $lifetimeSeconds; + } } diff --git a/tests/Feature/SingleSessionTest.php b/tests/Feature/SingleSessionTest.php index aba83fe..1b24973 100644 --- a/tests/Feature/SingleSessionTest.php +++ b/tests/Feature/SingleSessionTest.php @@ -94,4 +94,48 @@ class SingleSessionTest extends TestCase $this->assertNull($user->refresh()->active_session_token); } + + public function test_sesi_yang_sudah_kadaluarsa_dianggap_mati(): void + { + // Produksi pakai driver file — uji perilaku sesungguhnya + config()->set('session.driver', 'file'); + + $user = $this->makeUser(); + $sessionId = str_repeat('a', 40); // id sesi valid (format 40 char) + $user->forceFill(['active_session_token' => $sessionId])->save(); + + // Simulasikan file sesi lama: sudah lewat masa hidup (10 menit lalu) + $file = storage_path('framework/sessions/'.$sessionId); + file_put_contents($file, 'expired'); + touch($file, time() - 600); + + $this->assertFalse($user->refresh()->sessionTokenIsAlive()); + + // Login kedua harusnya DITERIMA sekarang + $this->post('/login', [ + 'username' => 'bksession', + 'password' => 'rahasia123', + ])->assertRedirect(); + + $this->assertAuthenticated(); + @unlink($file); + } + + public function test_sesi_yang_masih_hidup_dianggap_hidup(): void + { + // Produksi pakai driver file — uji perilaku sesungguhnya + config()->set('session.driver', 'file'); + + $user = $this->makeUser(); + $sessionId = str_repeat('b', 40); + $user->forceFill(['active_session_token' => $sessionId])->save(); + + // File sesi baru (aktivitas sekarang) + $file = storage_path('framework/sessions/'.$sessionId); + file_put_contents($file, 'fresh'); + touch($file); + + $this->assertTrue($user->refresh()->sessionTokenIsAlive()); + @unlink($file); + } }