prepare( 'DELETE FROM refresh_tokens WHERE user_id = ? AND (expires_at < NOW()' . ' OR (replaced_at IS NOT NULL AND replaced_at < DATE_SUB(NOW(), INTERVAL ' . JWTAuth::getGracePeriod() . ' SECOND)))' ); $stmt->execute([$userId]); // Jen aktivní sessions (nereplacované) $stmt = $pdo->prepare(' SELECT id, ip_address, user_agent, created_at, expires_at, token_hash FROM refresh_tokens WHERE user_id = ? AND replaced_at IS NULL ORDER BY created_at DESC '); $stmt->execute([$userId]); $sessions = $stmt->fetchAll(); // Process sessions to add is_current flag and parse user agent $processedSessions = array_map(function ($session) use ($currentTokenHash) { return [ 'id' => (int) $session['id'], 'ip_address' => $session['ip_address'], 'user_agent' => $session['user_agent'], 'device_info' => parseUserAgent($session['user_agent']), 'created_at' => $session['created_at'], 'expires_at' => $session['expires_at'], 'is_current' => $currentTokenHash && $session['token_hash'] === $currentTokenHash, ]; }, $sessions); successResponse([ 'sessions' => $processedSessions, 'total' => count($processedSessions), ]); } /** * DELETE - Delete a specific session */ function handleDeleteSession(PDO $pdo, int $sessionId, int $userId, ?string $currentTokenHash): void { // Verify the session belongs to the current user $stmt = $pdo->prepare('SELECT token_hash FROM refresh_tokens WHERE id = ? AND user_id = ?'); $stmt->execute([$sessionId, $userId]); $session = $stmt->fetch(); if (!$session) { errorResponse('Relace nebyla nalezena', 404); } // Check if trying to delete current session if ($currentTokenHash && $session['token_hash'] === $currentTokenHash) { // Check if force parameter is set $input = getJsonInput(); if (!($input['force'] ?? false)) { errorResponse('Nelze smazat aktuální relaci. Použijte tlačítko odhlášení.', 400); } } // Delete the session $stmt = $pdo->prepare('DELETE FROM refresh_tokens WHERE id = ? AND user_id = ?'); $stmt->execute([$sessionId, $userId]); successResponse(null, 'Relace byla úspěšně ukončena'); } /** * DELETE - Delete all sessions except current */ function handleDeleteAllSessions(PDO $pdo, int $userId, ?string $currentTokenHash): void { if (!$currentTokenHash) { $stmt = $pdo->prepare('DELETE FROM refresh_tokens WHERE user_id = ?'); $stmt->execute([$userId]); $deleted = $stmt->rowCount(); } else { // Ponechat aktuální session, smazat ostatní (včetně replaced) $stmt = $pdo->prepare('DELETE FROM refresh_tokens WHERE user_id = ? AND token_hash != ?'); $stmt->execute([$userId, $currentTokenHash]); $deleted = $stmt->rowCount(); } successResponse([ 'deleted' => $deleted, ], $deleted > 0 ? 'Ostatní relace byly úspěšně ukončeny' : 'Žádné další relace k ukončení'); } /** * Parse user agent string to extract device/browser info * * @return array{browser: string, os: string} */ function parseUserAgent(?string $userAgent): array { if (empty($userAgent)) { return [ 'browser' => 'Neznámý prohlížeč', 'os' => 'Neznámý systém', 'device' => 'Neznámé zařízení', 'icon' => 'device', ]; } $browser = 'Neznámý prohlížeč'; $os = 'Neznámý systém'; $device = 'desktop'; $icon = 'desktop'; // Detect browser if (preg_match('/Edg(e|A|iOS)?\/[\d.]+/i', $userAgent)) { $browser = 'Microsoft Edge'; } elseif (preg_match('/OPR\/[\d.]+|Opera/i', $userAgent)) { $browser = 'Opera'; } elseif (preg_match('/Chrome\/[\d.]+/i', $userAgent) && !preg_match('/Chromium/i', $userAgent)) { $browser = 'Google Chrome'; } elseif (preg_match('/Firefox\/[\d.]+/i', $userAgent)) { $browser = 'Mozilla Firefox'; } elseif (preg_match('/Safari\/[\d.]+/i', $userAgent) && !preg_match('/Chrome/i', $userAgent)) { $browser = 'Safari'; } elseif (preg_match('/MSIE|Trident/i', $userAgent)) { $browser = 'Internet Explorer'; } // Detect OS if (preg_match('/Windows NT 10/i', $userAgent)) { $os = 'Windows 10/11'; } elseif (preg_match('/Windows NT 6\.3/i', $userAgent)) { $os = 'Windows 8.1'; } elseif (preg_match('/Windows NT 6\.2/i', $userAgent)) { $os = 'Windows 8'; } elseif (preg_match('/Windows NT 6\.1/i', $userAgent)) { $os = 'Windows 7'; } elseif (preg_match('/Windows/i', $userAgent)) { $os = 'Windows'; } elseif (preg_match('/Macintosh|Mac OS X/i', $userAgent)) { $os = 'macOS'; } elseif (preg_match('/Linux/i', $userAgent) && !preg_match('/Android/i', $userAgent)) { $os = 'Linux'; } elseif (preg_match('/iPhone/i', $userAgent)) { $os = 'iOS'; $device = 'mobile'; $icon = 'smartphone'; } elseif (preg_match('/iPad/i', $userAgent)) { $os = 'iPadOS'; $device = 'tablet'; $icon = 'tablet'; } elseif (preg_match('/Android/i', $userAgent)) { $os = 'Android'; if (preg_match('/Mobile/i', $userAgent)) { $device = 'mobile'; $icon = 'smartphone'; } else { $device = 'tablet'; $icon = 'tablet'; } } return [ 'browser' => $browser, 'os' => $os, 'device' => $device, 'icon' => $icon, ]; }