From 2843bcf4f56c7ac5f4eeacd86197426972ed7cde Mon Sep 17 00:00:00 2001 From: Matt Date: Thu, 7 Aug 2025 16:20:05 +0200 Subject: [PATCH] Fix iOS Safari auth loops and simplify admin dashboard - Add session check throttling in useAuth to prevent iOS Safari authentication loops - Simplify admin dashboard by removing complex system metrics and stats - Remove system-metrics utility and streamline stats API endpoint - Update admin interface to focus on core user and role management --- composables/useAuth.ts | 17 +- pages/dashboard/admin.vue | 700 ++++++++++++--------------------- pages/login.vue | 11 +- server/api/admin/stats.get.ts | 81 +--- server/utils/system-metrics.ts | 190 --------- 5 files changed, 288 insertions(+), 711 deletions(-) delete mode 100644 server/utils/system-metrics.ts diff --git a/composables/useAuth.ts b/composables/useAuth.ts index 9fe8e97..3a3fe70 100644 --- a/composables/useAuth.ts +++ b/composables/useAuth.ts @@ -150,9 +150,24 @@ export const useAuth = () => { } }; - // Check authentication status + // Session check throttling to prevent iOS Safari loops + const lastSessionCheck = ref(0); + const SESSION_CHECK_THROTTLE = 5000; // 5 seconds minimum between checks + + // Check authentication status with debouncing const checkAuth = async () => { + const now = Date.now(); + + // Throttle session checks to prevent iOS Safari loops + if (now - lastSessionCheck.value < SESSION_CHECK_THROTTLE) { + console.log('🚫 Session check throttled, using cached result'); + return !!user.value; + } + try { + console.log('🔄 Performing throttled session check...'); + lastSessionCheck.value = now; + const response = await $fetch<{ authenticated: boolean; user: User | null; diff --git a/pages/dashboard/admin.vue b/pages/dashboard/admin.vue index 36ed248..74d30c6 100644 --- a/pages/dashboard/admin.vue +++ b/pages/dashboard/admin.vue @@ -1,483 +1,308 @@ diff --git a/pages/login.vue b/pages/login.vue index cb995a4..e05b547 100644 --- a/pages/login.vue +++ b/pages/login.vue @@ -131,16 +131,16 @@ const { user, login, loading: authLoading, error: authError, checkAuth } = useAu // Import mobile utilities for enhanced debugging const { isMobileDevice, debugMobileLogin, runMobileDiagnostics } = await import('~/utils/mobile-utils'); -// Check if user is already authenticated - with mobile-specific handling +// Check if user is already authenticated - prevent iOS Safari loops if (user.value) { const redirectUrl = '/dashboard'; + // Always use window.location for iOS Safari to prevent loops if (isMobileDevice()) { - console.log('📱 Mobile browser detected, using delayed redirect'); + console.log('📱 Mobile browser detected, using window.location redirect'); debugMobileLogin('Already authenticated redirect'); - setTimeout(() => { - window.location.href = redirectUrl; - }, 100); + // Use window.location.replace to avoid back button issues + window.location.replace(redirectUrl); } else { await navigateTo(redirectUrl); } @@ -259,7 +259,6 @@ onMounted(() => { } .login-card:hover { - transform: translateY(-4px); box-shadow: 0 12px 40px rgba(0, 0, 0, 0.4) !important; } diff --git a/server/api/admin/stats.get.ts b/server/api/admin/stats.get.ts index dbd189f..6719a91 100644 --- a/server/api/admin/stats.get.ts +++ b/server/api/admin/stats.get.ts @@ -1,8 +1,8 @@ export default defineEventHandler(async (event) => { - console.log('📊 Admin stats requested at:', new Date().toISOString()); + console.log('📊 Simple admin stats requested at:', new Date().toISOString()); try { - // Check if user is admin (middleware should handle this, but double-check) + // Check if user is admin const sessionManager = createSessionManager(); const cookieHeader = getHeader(event, 'cookie'); const session = sessionManager.getSession(cookieHeader); @@ -17,78 +17,17 @@ export default defineEventHandler(async (event) => { console.log('✅ Admin access verified for user:', session.user.email); - // Get real system metrics - const { getSystemMetrics, getSystemHealthStatus, formatBytes } = await import('~/server/utils/system-metrics'); - const systemMetrics = await getSystemMetrics(); - const systemHealth = getSystemHealthStatus(systemMetrics); - - // Build comprehensive stats with real data + // Return simple user-focused stats without system metrics const stats = { - // Real system metrics - totalUsers: 156, // TODO: Get from Keycloak API - activeUsers: 45, // TODO: Get from session store - totalSessions: 67, // TODO: Get from session store - systemHealth, - lastBackup: new Date().toISOString(), - diskUsage: `${systemMetrics.disk.usagePercent}%`, - memoryUsage: `${systemMetrics.memory.usagePercent}%`, + // Simple user count (mock data for now - would come from Keycloak API) + userCount: 25, - // Enhanced system metrics with real data - systemMetrics: { - cpu: systemMetrics.cpu.usage, - memory: systemMetrics.memory.usagePercent, - disk: systemMetrics.disk.usagePercent, - uptime: systemMetrics.uptime, - processes: systemMetrics.processes.total, - cpuCores: systemMetrics.cpu.cores, - cpuModel: systemMetrics.cpu.model, - memoryTotal: formatBytes(systemMetrics.memory.total), - memoryUsed: formatBytes(systemMetrics.memory.used), - diskTotal: formatBytes(systemMetrics.disk.total), - diskUsed: formatBytes(systemMetrics.disk.used) - }, - - // Mock data for features not yet implemented - recentActivity: [ - { - action: 'User login', - user: 'john@example.com', - timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), - type: 'info' - }, - { - action: 'Password reset', - user: 'jane@example.com', - timestamp: new Date(Date.now() - 4 * 60 * 60 * 1000).toISOString(), - type: 'warning' - }, - { - action: 'User created', - user: 'admin@monacousa.org', - timestamp: new Date(Date.now() - 6 * 60 * 60 * 1000).toISOString(), - type: 'success' - } - ], - - securityAlerts: [ - { - id: 1, - title: 'Failed Login Attempts', - description: '3 failed login attempts detected', - severity: 'medium', - timestamp: new Date(Date.now() - 1 * 60 * 60 * 1000).toISOString() - }, - { - id: 2, - title: 'System Update Available', - description: 'Security update available for Keycloak', - severity: 'low', - timestamp: new Date(Date.now() - 12 * 60 * 60 * 1000).toISOString() - } - ] + // Basic portal health without system metrics + portalStatus: 'online', + lastUpdate: new Date().toISOString() }; - console.log('✅ Admin stats retrieved successfully with real system metrics'); + console.log('✅ Simple admin stats retrieved successfully'); return stats; } catch (error: any) { @@ -100,7 +39,7 @@ export default defineEventHandler(async (event) => { throw createError({ statusCode: 500, - statusMessage: 'Failed to retrieve system statistics' + statusMessage: 'Failed to retrieve admin statistics' }); } }); diff --git a/server/utils/system-metrics.ts b/server/utils/system-metrics.ts deleted file mode 100644 index 68f3487..0000000 --- a/server/utils/system-metrics.ts +++ /dev/null @@ -1,190 +0,0 @@ -import si from 'systeminformation'; - -interface SystemMetrics { - cpu: { - usage: number; - cores: number; - model: string; - }; - memory: { - total: number; - used: number; - free: number; - usagePercent: number; - }; - disk: { - total: number; - used: number; - free: number; - usagePercent: number; - }; - uptime: string; - processes: { - total: number; - running: number; - sleeping: number; - }; -} - -// Cache system metrics to avoid excessive system calls -let metricsCache: SystemMetrics | null = null; -let lastCacheTime = 0; -const CACHE_DURATION = 30000; // 30 seconds - -export async function getSystemMetrics(): Promise { - const now = Date.now(); - - // Return cached data if still valid - if (metricsCache && (now - lastCacheTime) < CACHE_DURATION) { - console.log('📊 Returning cached system metrics'); - return metricsCache; - } - - try { - console.log('🔄 Fetching fresh system metrics...'); - - // Get all metrics in parallel with individual error handling - const results = await Promise.allSettled([ - si.cpu(), - si.mem(), - si.fsSize(), - si.currentLoad(), - si.processes(), - si.time() - ]); - - // Extract data with fallbacks for failed promises - const cpuData = results[0].status === 'fulfilled' ? results[0].value : { cores: 0, brand: 'Unknown' }; - const memData = results[1].status === 'fulfilled' ? results[1].value : { total: 0, used: 0, free: 0 }; - const fsData = results[2].status === 'fulfilled' ? results[2].value : []; - const currentLoad = results[3].status === 'fulfilled' ? results[3].value : { currentLoad: 0 }; - const processData = results[4].status === 'fulfilled' ? results[4].value : { all: 0, running: 0, sleeping: 0 }; - const uptimeData = results[5].status === 'fulfilled' ? results[5].value : { uptime: 0 }; - - // Log any failures - results.forEach((result, index) => { - if (result.status === 'rejected') { - const names = ['CPU', 'Memory', 'Filesystem', 'CPU Load', 'Processes', 'Uptime']; - console.warn(`⚠️ ${names[index]} data failed:`, result.reason?.message || result.reason); - } - }); - - // Calculate disk totals across all mounted filesystems - const diskTotals = (fsData as any[]).reduce((acc: { total: number; used: number }, fs: any) => { - // Skip special filesystems and focus on main storage - if (fs.type && !['tmpfs', 'devtmpfs', 'proc', 'sysfs'].includes(fs.type.toLowerCase())) { - acc.total += fs.size || 0; - acc.used += fs.used || 0; - } - return acc; - }, { total: 0, used: 0 }); - - const diskFree = diskTotals.total - diskTotals.used; - const diskUsagePercent = diskTotals.total > 0 ? Math.round((diskTotals.used / diskTotals.total) * 100) : 0; - - // Format uptime - const formatUptime = (seconds: number): string => { - const days = Math.floor(seconds / 86400); - const hours = Math.floor((seconds % 86400) / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - - if (days > 0) { - return `${days}d ${hours}h ${minutes}m`; - } else if (hours > 0) { - return `${hours}h ${minutes}m`; - } else { - return `${minutes}m`; - } - }; - - // Build metrics object - const metrics: SystemMetrics = { - cpu: { - usage: Math.round(currentLoad.currentLoad || 0), - cores: cpuData.cores || 0, - model: cpuData.brand || 'Unknown' - }, - memory: { - total: memData.total || 0, - used: memData.used || 0, - free: memData.free || 0, - usagePercent: memData.total > 0 ? Math.round((memData.used / memData.total) * 100) : 0 - }, - disk: { - total: diskTotals.total, - used: diskTotals.used, - free: diskFree, - usagePercent: diskUsagePercent - }, - uptime: formatUptime(uptimeData.uptime || 0), - processes: { - total: processData.all || 0, - running: processData.running || 0, - sleeping: processData.sleeping || 0 - } - }; - - // Cache the results - metricsCache = metrics; - lastCacheTime = now; - - console.log('✅ System metrics fetched successfully:', { - cpu: `${metrics.cpu.usage}%`, - memory: `${metrics.memory.usagePercent}%`, - disk: `${metrics.disk.usagePercent}%`, - uptime: metrics.uptime - }); - - return metrics; - - } catch (error) { - console.error('❌ Failed to fetch system metrics:', error); - - // Return fallback metrics on error - const fallbackMetrics: SystemMetrics = { - cpu: { usage: 0, cores: 0, model: 'Unknown' }, - memory: { total: 0, used: 0, free: 0, usagePercent: 0 }, - disk: { total: 0, used: 0, free: 0, usagePercent: 0 }, - uptime: '0m', - processes: { total: 0, running: 0, sleeping: 0 } - }; - - return fallbackMetrics; - } -} - -// Helper function to format bytes to human readable format -export function formatBytes(bytes: number): string { - if (bytes === 0) return '0 B'; - - const k = 1024; - const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - - return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; -} - -// Helper function to get system health status -export function getSystemHealthStatus(metrics: SystemMetrics): 'healthy' | 'degraded' | 'unhealthy' { - const { cpu, memory, disk } = metrics; - - // Define thresholds - const criticalThresholds = { cpu: 90, memory: 95, disk: 95 }; - const warningThresholds = { cpu: 70, memory: 80, disk: 85 }; - - // Check for critical issues - if (cpu.usage > criticalThresholds.cpu || - memory.usagePercent > criticalThresholds.memory || - disk.usagePercent > criticalThresholds.disk) { - return 'unhealthy'; - } - - // Check for warning issues - if (cpu.usage > warningThresholds.cpu || - memory.usagePercent > warningThresholds.memory || - disk.usagePercent > warningThresholds.disk) { - return 'degraded'; - } - - return 'healthy'; -}