🔐 Muzibu Device Giris/Cikis Sistemi

Kapsamli Analiz, Bug Tespiti & Cozum Onerileri

📅 09 Aralik 2025 🎯 Tenant: muzibu.com 📊 Analiz Kapsamı: DB + Redis + Browser

⚠️ Kritik Ozet

8
Kritik Bug
5
Orta Seviye
6
Eksik Ozellik

🎯 Istenen vs Mevcut Davranis

✅ ISTENEN DAVRANIS

1. Son giren KALSIN

Kullanici B giris yapinca, A cihazi otomatik cikarilmali. B sorunsuz dinlemeye baslamali.

2. Eski cihaz BILGILENDIRILSIN

A cihazinda "Baska cihazdan giris yapildi" modali ciksin. Sayfada gezinmeye devam edebilsin ama dinleyemesin.

3. Logout KARISTIRMASIN

Normal logout yapilinca device limit sistemi dogru calismali. Ghost session olmamali.

4. Cogul limit destegi

1 cihaz yerine 2-3 cihaz limiti olunca sistem dogru calismali (plan bazli).

❌ MEVCUT DAVRANIS

1. Iki cihaz da KALIR

B giris yapinca A cikarilmiyor! Limit asiliyor, her iki cihazda modal cikiyor. Kim cikarilacak belli degil.

2. 30 saniye GECIKME

A cihazi hala 30 saniye dinleyebiliyor. Premium icerik korunmuyor.

3. Ghost session OLUSUYOR

Tarayici kapatilinca DB'de kayit kaliyor. Device limit yanlis hesaplaniyor.

4. Cogul limit TUTARSIZ

2 cihaz limiti olsa bile race condition'lar yuzunden 3+ cihaz aktif olabilir.

🔄 Ideal Akis (LIFO - Last In First Out)

👤
Kullanici B
Chrome'dan giris
📊
Limit Kontrol
1 cihaz var, limit 1
A TERMINATE
En eski session sil
B KAYIT
Yeni session olustur
🔔
A BILGILENDIR
Polling modal goster

📐 Mevcut Sistem Mimarisi

✅ Hybrid Model: Redis + DB

  • Redis: Laravel session driver Gercek session data'si burada
  • user_active_sessions: Device tracking Metadata: IP, User Agent, Device Name
  • Tenant-aware: Her tenant ayri kontrol shouldRun() ile setting kontrolu

🎯 Device Limit Hiyerarsisi (3 Seviye)

1. User Override users.device_limit (VIP/Test/Ban)
2. Subscription Plan subscription_plans.device_limit
3. Tenant Setting settings.auth_device_limit (varsayilan: 1)

🔄 Giris/Cikis Akisi

👤
LOGIN
registerSession()
📊
KONTROL
checkDeviceLimit()
⏱️
POLLING
30 saniye aralik
🚪
LOGOUT
unregisterSession()

🚪 Logout Akisi (3 Farkli Senaryo)

✅ Normal Logout

Kullanici "Cikis Yap" butonuna tiklar

  1. unregisterSession() - DB'den sil
  2. Auth::logout() - Laravel logout
  3. session()->invalidate() - Redis sil
  4. regenerateToken() - CSRF yenile
Sonuc: Temiz cikis, device limiti azalir

⚡ Force Logout (Modal)

Baska cihazdan cikarilma

  1. terminateSession() API cagrisi
  2. invalidateRedisSession() - Redis sil
  3. DB'den session kaydi sil
  4. Eski cihaz: Polling modal gosterir
Sonuc: Eski cihaz navigasyon yapamaz

❌ Tarayici Kapatma

Logout yapmadan cikar

  1. unregisterSession() CALISMIYOR!
  2. Redis: Session timeout (varsayilan)
  3. DB: Kayit 30dk KALIR
  4. Device limit YANLIS sayilir
Bug: Ghost session - limit tukenir

📄 Logout Kod Akisi

// AuthenticatedSessionController.php:253-290
1. Activity log olustur (user bilgisi kaybolmadan)
2. DeviceService->unregisterSession($user)
3. Auth::guard('web')->logout()
4. session()->invalidate()
5. session()->regenerateToken()

⚠️ Kritik: Logout Sirasi Onemli!

unregisterSession() ONCE cagrilmali - session invalidate sonrasi session ID kaybolur!

Mevcut kod dogru sirada calisiyor. Fakat shouldRun() kontrolu kaldirildi - logout HER ZAMAN session silmeli (subscription kapali olsa bile).

🐛 Kritik Bug'lar & Sorunlar

1

Race Condition - Esanli Giris KRITIK

Kullanici A ve B cihazlarindan ayni anda giris yaparsa, her ikisi de limiti karsilar gibi gorunur. handlePostLoginDeviceLimit() otomatik silme yapmiyor - sadece modal gosteriyor.

Senaryo:
  1. Kullanici Chrome'dan giris yapar (Session A kayit edilir)
  2. Ayni anda Safari'den giris yapar (Session B kayit edilir)
  3. Her iki session da aktif - limit asiliyor ama kimse cikarilmiyor
  4. 30 saniye sonra polling her iki cihazda modal gosterir
DeviceService.php:329-341
2

30 Saniye Polling Gecikmesi KRITIK

Yeni cihaz giris yaptiktan sonra eski cihaz 30 saniyeye kadar hala muzik dinleyebilir. Bu sure zamaninda sistem limit > 1 gibi davranir.

Etki:
  • Kullanici B giris yapar - Kullanici A 30 saniye daha dinler
  • Sarki bitene kadar fark etmeyebilir
  • Premium icerik korunmamis oluyor
player-core.js:3281-3284
3

Ghost Session - Tarayici Kapatma Sorunu KRITIK

Kullanici logout yapmadan tarayiciyi kapatirsa:

Redis: Session silinir (expire)
user_active_sessions: Kayit KALIR (30 dk boyunca)

Sonuc: Gercekte aktif olmayan session, device limit'e dahil ediliyor. Kullanici 1 cihaz limitinde ama 0 cihaz kullanabilir duruma dusuyor.

4

Redis Session Invalidation Hatasi KRITIK

invalidateRedisSession() fonksiyonu 3 farkli Redis key pattern deniyor ama hangisinin gercekten var oldugunu bilmiyor:

// Denenen key'ler:
laravel_database_laravel_session:{sessionId}
laravel_database_sessions:{sessionId}
laravel:{sessionId}
// Hepsi ayni anda siliniyor - hangisi dogru?

Eger dogru key silinmezse, Redis'teki session hala gecerli kalir ve kullanici giriste kalir.

5

Stream API - Device Check Bypass KRITIK

SongStreamController::stream() device limit kontrolu yapiyor FAKAT:

  • Sadece getActiveDeviceCount() kontrol ediyor
  • Mevcut session'in DB'de olup olmadigini KONTROL ETMIYOR
  • Yani: Session terminate edilmis ama henuz polling tetiklenmemisse stream calismaya devam eder
SongStreamController.php:62-83
6

checkSession API Tutarsizligi KRITIK

/api/auth/check-session endpoint'i device_limit_exceeded dondugunde:

Beklenen: Session silinmis olmali
Gercek: Session HALA DB'de var!

Frontend modal gosteriyor ama kullanici authenticated kalmaya devam ediyor. Hangi cihaz cikarilacak? Kendisi mi, digeri mi? Karisiklik!

7

Self-Healing Yanlis Cihaz Silme KRITIK

updateSessionActivity() icerisinde session DB'de yoksa otomatik registerSession() yapiliyor ve limit asilirsa EN ESKI session siliniyor.

Problem:
  • En eski = En uzun suredir aktif = Belki de EN ONEMLI cihaz
  • Kullaniciya sormadan karar veriliyor
  • FIFO mantigi her zaman dogru degil
DeviceService.php:193-210
8

Modal Kapatma Yok - UX Sorunu KRITIK

Device Selection Modal'da X butonu yok - kullanici cihaz secip cikis yapmak ZORUNDA.

  • "Mevcut Cihaz" secilemez - yani kendini cikaramaz direkt
  • Kullanici "vazgec, cikis yap" diyemez
  • Sayfa yenileme bile modal'i kapatmaz (meta tag kontrolu)
  • Kullanici sikiip kalabilir

⚠️ Orta Seviye Sorunlar

Coklu Tab Karisikligi

Ayni tarayicida 2 tab = Ayni session ID (1 device)
Farkli tarayicida 2 tab = Farkli session ID (2 device)
Kullanici icin kafa karistirici!

window.location.reload() Kesintisi

Cihaz cikarilinca sayfa yenileniyor - tum player state sifirlanir. Muzik durur, queue kaybolur, kullanici deneyimi bozulur.

localStorage Cross-Domain

device_limit_warning localStorage'da. Farkli subdomain = Farkli localStorage!

Stale Cleanup Zamanlama

cleanupStaleSessions() sadece getActiveDevices() cagrilinca calisiyor. 30 dakika ghost session kalabilir!

Plan Dusurme Senaryosu

Kullanici 3 cihaz limitli plandan 1 cihaz limitli plana gecerse? Fazla cihazlar ne olacak? Tanimsiz!

📋 Eksik Ozellikler

1
WebSocket / Realtime Bildirim 30 saniyelik polling yerine anlik bildirim: "Baska cihazdan giris yapildi"
2
Cihaz Isimlendirme Kullanici kendi cihazina isim verebilmeli: "Evdeki Mac", "Is Telefonu"
3
Guvenilir Cihaz "Bu cihazi hatirla" ozelligi - her login yeni device sayilmamali
4
Admin Panel Device Yonetimi Admin kullanicilarin cihazlarini gorebilmeli, force logout yapabilmeli
5
"Kendimi Cikar" Secenegi Modal'da mevcut cihazi cikarma secenegi olmali (logout)
6
Session Expire Senkronizasyonu Redis session expire olunca user_active_sessions'dan da silinmeli

✅ Cozum Onerileri

🔥 Oncelik 1: Son Giren Kalsin Mantigi (LIFO)

Mevcut sistemde limit asildiginda modal gosteriyor ve kullanici seciyor. Oneri: Son giren otomatik kalsin, eski cihaz cikarilsin.

Mantik:
  1. Yeni giris geldi - registerSession()
  2. Limit kontrolu - limit asiliyor mu?
  3. EVET: En eski session'i terminateSession() ile sil
  4. Eski cihazdaki polling bunu yakalar - modal gosterir
  5. Yeni cihaz sorunsuz devam eder

🔥 Oncelik 2: Polling Suresi Kisaltma

30 saniye cok uzun - 5-10 saniyeye dusur veya WebSocket ekle.

Kisa Vadeli: Polling interval: 30s -> 10s
Uzun Vadeli: Laravel Echo + Pusher/Soketi

🔥 Oncelik 3: Stream API'de Session Kontrolu

SongStreamController'da sadece device count degil, mevcut session'in DB'de olup olmadigini da kontrol et.

// Eklenecek kontrol:
$sessionExists = DB::table('user_active_sessions')
->where('session_id', session()->getId())
->where('user_id', $user->id)
->exists();
if (!$sessionExists) {
return response()->json(['error' => 'session_terminated'], 403);
}

🔥 Oncelik 4: Ghost Session Temizleme

Periyodik olarak Redis'te olmayan session'lari DB'den temizle.

Yontem 1: Scheduled task (her 5 dakika) DB'deki her session icin Redis'te var mi kontrol et, yoksa sil
Yontem 2: Redis key expire event listener Session expire oldugunda otomatik DB cleanup

📁 Kritik Dosyalar

📄 DeviceService.php
Modules/Muzibu/app/Services/DeviceService.php 684 satir - Ana device mantigi
📄 AuthController.php
app/Http/Controllers/Api/Auth/AuthController.php 435 satir - API endpoint'leri
📄 player-core.js
public/themes/muzibu/js/player/core/player-core.js 3600+ satir - Frontend device logic
📄 SongStreamController.php
Modules/Muzibu/app/Http/Controllers/Api/SongStreamController.php 403 satir - Stream device check
📄 device-selection-modal.blade.php
resources/views/themes/muzibu/components/device-selection-modal.blade.php 136 satir - Modal UI
📄 Migration
database/migrations/*user_active_sessions* DB structure