Concurrent Session Sistemi Analizi

Muzibu.com.tr - Aynı Anda Tek Oturum (LIFO) Mekanizması

19 Aralık 2025
Sistem Analizi
LIFO Session Control

Basit Anlatım (Herkes İçin)

Ne yapıldı, neden yapıldı, nasıl çalışıyor?

Sistem Ne Yapıyor?

Muzibu.com.tr'de bir kullanıcı hesabına aynı anda sadece 1 cihazdan giriş yapılabilir. Kullanıcı telefondan giriş yaptıktan sonra bilgisayardan tekrar giriş yaparsa, telefonundaki oturum otomatik kapanır.

Günlük Hayattan Örnek: Sanki banka hesabınız gibi - aynı anda hem ATM'den hem de bankacılık uygulamasından para çekemezsiniz. Son giriş yapan kazanır, önceki oturum kapanır.

Kullanıcı Deneyimi

  • 1. Kullanıcı telefondan giriş yapar → Müzik dinlemeye başlar
  • 2. Aynı kullanıcı bilgisayardan giriş yapar → Yeni oturum açılır
  • 3. Telefonundaki müzik aniden durur → "Başka bir cihazdan giriş yapıldı" uyarısı çıkar
  • 4. Telefon otomatik çıkış yapar → Tekrar giriş yapması gerekir

Neden Önemli?

Güvenlik

Şifre paylaşımı engellenir. Hesabı çalan birisi giriş yaparsa, gerçek kullanıcı anında fark eder.

Gelir Artışı

Premium üyelik satan platformlar için kritik. Kullanıcı sadece kendi hesabını kullanabilir, arkadaşlarıyla paylaşamaz.

Sistem Performansı

Sunucu yükü azalır. Aynı anda 10.000 kullanıcı varsa, max 10.000 aktif oturum vardır, daha fazla değil.

Kullanıcı Kontrol

Kullanıcı hangi cihazlardan bağlı olduğunu görür, istediğini kapatabilir (gelecek özellik).

Teknik Detaylar (Geliştiriciler İçin)

Dosya yapısı, mimari, algoritma, teknolojiler

Sistem Mimarisi

Database Layer

  • user_active_sessions tablosu (tenant DB)
  • Session ID, IP, User Agent, Device Type
  • Login Token (cookie ile sync - Livewire regenerate için)
  • Last Activity timestamp (stale session cleanup)

Backend Layer

  • Laravel Session Driver (Redis/Database/File)
  • DeviceService (session tracking)
  • AuthController (login/logout API)
  • Sanctum stateful authentication (web guard)

Frontend Layer

  • MuzibuSession JavaScript module
  • Polling interval: 5 saniye (test) / 5 dakika (canlı)
  • Fetch API (/api/auth/check-session)
  • Session termination modal (automatic logout)

Settings Integration

  • auth_subscription = true (subscription sistemi)
  • auth_device = true (device limit aktif)
  • auth_device_limit = 1 (tenant fallback)
  • User/Plan override supported (3-tier hierarchy)

LIFO (Last In, First Out) Algoritması

Yeni giriş yapıldığında en eski oturum otomatik silinir. Kullanıcı limit aşarsa sistem en eski session'ı temizler.

// DeviceService.php:468 - enforceDeviceLimit()

protected function enforceDeviceLimit(User $user, string $currentSessionId): void
{
    $limit = $this->getDeviceLimit($user); // 1 (default)

    $sessions = DB::table($this->table)
        ->where('user_id', $user->id)
        ->orderBy('last_activity', 'asc') // En eski önce (LIFO)
        ->get();

    $count = $sessions->count();

    if ($count > $limit) {
        $toRemove = $count - $limit; // Kaç session silinecek

        // En eski session'ları sil (mevcut hariç)
        $oldSessions = $sessions
            ->filter(fn($s) => $s->session_id !== $currentSessionId)
            ->take($toRemove);

        foreach ($oldSessions as $old) {
            DB::table($this->table)->where('id', $old->id)->delete();
            // Bu session'a ait kullanıcı polling ile fark eder
        }
    }
}

Session Tracking (Login Token Approach)

Livewire session ID'yi regenerate edebilir, bu yüzden login_token kullanılır (cookie + DB).

1. Login İşlemi (registerSession)

// DeviceService.php:43 - registerSession()

$loginToken = bin2hex(random_bytes(32)); // 64 char benzersiz token

DB::table('user_active_sessions')->insert([
    'user_id' => $user->id,
    'session_id' => session()->getId(),
    'login_token' => $loginToken,  // 🔥 SABİT TOKEN
    'ip_address' => request()->ip(),
    'user_agent' => request()->userAgent(),
    'device_type' => 'desktop|mobile|tablet',
    'last_activity' => now(),
]);

// Cookie'ye 7 gün süreyle kaydet (HttpOnly, Secure)
cookie()->queue(cookie('mzb_login_token', $loginToken, 60*24*7));

// LIFO kontrolü: Limit aşılırsa en eski session'ı sil
$this->enforceDeviceLimit($user, session()->getId());

2. Session Kontrolü (sessionExists)

// DeviceService.php:186 - sessionExists()

// 1. Cookie'den login_token al
$cookieToken = request()->cookie('mzb_login_token');

// 2. DB'de bu token var mı kontrol et
$session = DB::table('user_active_sessions')
    ->where('user_id', $user->id)
    ->where('login_token', $cookieToken)
    ->first();

if ($session) {
    // Session ID değişmişse güncelle (Livewire regenerate)
    if (session()->getId() !== $session->session_id) {
        DB::table('user_active_sessions')
            ->where('id', $session->id)
            ->update(['session_id' => session()->getId()]);
    }
    return true; // ✅ Session geçerli
}

return false; // ❌ Token DB'de yok = LIFO tarafından silindi

Frontend Polling System

Her 5 saniyede (test) / 5 dakikada (canlı) bir backend'e session durumu sorulur.

// session.js:18 - startSessionPolling()

const SESSION_POLL_INTERVAL = 5000; // 5 saniye (test) | 300000 (5 dk - canlı)

// Login sonrası 2 saniye bekle (race condition önleme)
setTimeout(() => this.checkSessionValidity(), 2000);

// Periyodik kontrol başlat
this.sessionPollInterval = setInterval(() => {
    this.checkSessionValidity();
}, SESSION_POLL_INTERVAL);


// session.js:58 - checkSessionValidity()

async checkSessionValidity() {
    const response = await fetch('/api/auth/check-session', {
        method: 'GET',
        headers: {
            'Accept': 'application/json',
            'Referer': window.location.origin + '/',  // Sanctum için zorunlu
        },
        credentials: 'same-origin'
    });

    const data = await response.json();

    if (!data.valid) {
        if (data.reason === 'session_terminated') {
            // ⚠️ Başka cihazdan giriş yapıldı
            this.handleSessionTerminated(data.message);
        }
    }
}

// session.js:149 - handleSessionTerminated()

handleSessionTerminated(message) {
    // Polling durdur
    this.stopSessionPolling();

    // Müzik/oynatıcı durdur
    this.stopCurrentPlayback();

    // API logout çağır
    fetch('/api/auth/logout', {method: 'POST'});

    // Hard redirect (Livewire intercept edemez)
    window.location.href = '/login?session_terminated=1';
}

Complete Flow (Step by Step)

1

Login (Device A - Phone)

AuthController::login()DeviceService::registerSession() → DB'ye session kaydı (login_token oluştur) → Cookie'ye token kaydet → Frontend polling başlar

2

Login (Device B - Desktop)

Aynı kullanıcı tekrar giriş yapar → Yeni session kaydı oluşur → enforceDeviceLimit() çağrılır → Limit 1, mevcut 2 → En eski session (Device A) DB'den SİLİNİR

3

Polling (Device A)

5 saniye sonra checkSessionValidity() çağrılır → Backend sessionExists() kontrol eder → Cookie'de login_token var ama DB'de YOKreturn false

4

Automatic Logout (Device A)

Frontend "session_terminated" alır → handleSessionTerminated() çağrılır → Müzik durdurulur → Polling durdurulur → Logout API → window.location.href = '/login?session_terminated=1'

Dosya Yapısı

Backend (PHP/Laravel)

  • Modules/Muzibu/app/Services/DeviceService.php - Session tracking (LIFO, registration, termination)
  • app/Http/Controllers/Api/Auth/AuthController.php - Login/logout/checkSession endpoints
  • app/Http/Controllers/Auth/AuthenticatedSessionController.php - Web login (Blade + Livewire)
  • Modules/Muzibu/app/Http/Controllers/Api/DeviceController.php - Device management API
  • app/Http/Middleware/CheckDeviceLimit.php - Device limit middleware (route protection)

Frontend (JavaScript)

  • public/themes/muzibu/js/player/features/session.js - MuzibuSession module (polling, logout)
  • public/themes/muzibu/js/player/core/player-core.js - Main player (session integration)
  • public/themes/muzibu/js/player/features/api.js - API client (auth requests)

Database

  • database/migrations/tenant/2025_12_07_182016_create_user_active_sessions_table.php - Migration
  • tenant_muzibu_1528d0.user_active_sessions - Session tracking table (tenant DB)

Views (Blade Components)

  • resources/views/themes/muzibu/components/device-limit-modal.blade.php - Device selection modal
  • resources/views/themes/muzibu/components/device-limit-warning-modal.blade.php - Warning modal

Device Limit Settings (3-Tier Hierarchy)

Device limit 3 yerden alınır (öncelik sırasıyla):

1. ÖNCE

User Override

users.device_limit - Kullanıcıya özel limit (admin panelden ayarlanır)

2. SONRA

Subscription Plan

subscription_plans.device_limit - Plan'a göre limit (Basic: 1, Premium: 3, Enterprise: 10)

3. FALLBACK

Tenant Setting

setting('auth_device_limit') - Tenant genelinde default limit (varsayılan: 1)

// DeviceService.php:432 - getDeviceLimit()

public function getDeviceLimit(User $user): int
{
    // 1. User override
    if ($user->device_limit !== null && $user->device_limit > 0) {
        return $user->device_limit;
    }

    // 2. Subscription Plan
    $subscription = $user->subscriptions()
        ->whereIn('status', ['active', 'trial'])
        ->with('plan')
        ->first();

    if ($subscription && $subscription->plan->device_limit) {
        return (int) $subscription->plan->device_limit;
    }

    // 3. Tenant setting fallback
    return (int) setting('auth_device_limit', 1);
}

Performans & Scalability

Polling Interval

  • ⚠️ Test: 5 saniye - 10.000 kullanıcı → 2.000 req/s (sunucu patlar!)
  • Canlı: 5 dakika (300s) - 10.000 kullanıcı → 33 req/s (kabul edilebilir)

Database Optimization

  • Index: ['user_id', 'last_activity'] - Fast lookup
  • Stale cleanup: 60 dakikadan eski session'lar otomatik silinir
  • LIFO: Max session sayısı = aktif kullanıcı sayısı (çok verimli)

Cache Strategy

  • Session data Redis'te tutulabilir (Laravel session driver)
  • Login token cookie'de 7 gün saklanır (HttpOnly + Secure)
  • DB query: WHERE user_id + login_token (indexed, fast)

Edge Cases

  • Livewire session regenerate → login_token ile handle edilir
  • Network error → Polling devam eder, logout YAPILMAZ
  • Race condition → 2 saniye delay (registerSession tamamlansın)

Önemli Notlar & Kısıtlamalar

Sistem Kapatılabilir

setting('auth_device') = false olursa sistem çalışmaz. shouldRun() kontrolü her metotta var.

Tenant-Specific

Sadece Tenant 1001 (Muzibu) için aktif. Diğer tenant'larda tenant() kontrolü ile devre dışı.

Polling Delay

Kullanıcı başka cihazdan giriş yapsa bile, eski cihaz max 5 saniye (test) / 5 dakika (canlı) içinde fark eder.

Login Token Cookie

mzb_login_token cookie'si HttpOnly + Secure. JavaScript erişemez, sadece backend okur.