/** * Session Management Routes * Handles active session viewing, management, and termination */ const express = require('express'); const router = express.Router(); const { authenticate, requireAdmin } = require('../middleware/auth'); const { readLimiter, modifyLimiter } = require('../middleware/rateLimiter'); const { db } = require('../database/db'); const logger = require('../utils/logger'); const SecurityAuditLogger = require('../utils/securityAudit'); /** * Get all active sessions for current user */ router.get('/my-sessions', authenticate, readLimiter, async (req, res) => { try { const userId = req.user.userId; const currentToken = req.headers.authorization?.split(' ')[1]; db.all( `SELECT id, session_token, ip_address, user_agent, created_at, last_activity, expires_at FROM active_sessions WHERE user_id = ? AND expires_at > ? ORDER BY last_activity DESC`, [userId, new Date().toISOString()], (err, sessions) => { if (err) { logger.error('Error fetching sessions:', err); return res.status(500).json({ error: 'Failed to fetch sessions' }); } // Mark current session const sessionsWithCurrent = sessions.map(session => ({ ...session, isCurrent: session.session_token === currentToken, // Don't expose full token to client session_token: undefined })); res.json(sessionsWithCurrent); } ); } catch (error) { logger.error('Session fetch error:', error); res.status(500).json({ error: 'Failed to fetch sessions' }); } }); /** * Get all active sessions (admin only) */ router.get('/all', authenticate, requireAdmin, readLimiter, async (req, res) => { try { db.all( `SELECT s.id, s.user_id, s.ip_address, s.user_agent, s.created_at, s.last_activity, s.expires_at, u.username, u.email FROM active_sessions s JOIN users u ON s.user_id = u.id WHERE s.expires_at > ? ORDER BY s.last_activity DESC`, [new Date().toISOString()], (err, sessions) => { if (err) { logger.error('Error fetching all sessions:', err); return res.status(500).json({ error: 'Failed to fetch sessions' }); } res.json(sessions); } ); } catch (error) { logger.error('Session fetch error:', error); res.status(500).json({ error: 'Failed to fetch sessions' }); } }); /** * Terminate a specific session */ router.delete('/:sessionId', authenticate, modifyLimiter, async (req, res) => { try { const { sessionId } = req.params; const userId = req.user.userId; const currentToken = req.headers.authorization?.split(' ')[1]; // Validate session ID if (isNaN(parseInt(sessionId))) { return res.status(400).json({ error: 'Invalid session ID' }); } // Get session details first db.get( 'SELECT * FROM active_sessions WHERE id = ?', [sessionId], async (err, session) => { if (err) { logger.error('Error fetching session:', err); return res.status(500).json({ error: 'Failed to terminate session' }); } if (!session) { return res.status(404).json({ error: 'Session not found' }); } // Only allow users to terminate their own sessions (or admins to terminate any) if (session.user_id !== userId && req.user.role !== 'admin') { return res.status(403).json({ error: 'Permission denied' }); } // Prevent terminating current session if (session.session_token === currentToken) { return res.status(400).json({ error: 'Cannot terminate current session. Use logout instead.' }); } // Terminate session db.run( 'DELETE FROM active_sessions WHERE id = ?', [sessionId], async (err) => { if (err) { logger.error('Error terminating session:', err); return res.status(500).json({ error: 'Failed to terminate session' }); } // Log the event await SecurityAuditLogger.logSessionEvent('SESSION_TERMINATED', userId, { ip: req.ip || req.headers['x-forwarded-for'], userAgent: req.headers['user-agent'], sessionId: sessionId, terminatedSessionIp: session.ip_address }); logger.info(`Session ${sessionId} terminated by user ${userId}`); res.json({ message: 'Session terminated successfully' }); } ); } ); } catch (error) { logger.error('Session termination error:', error); res.status(500).json({ error: 'Failed to terminate session' }); } }); /** * Terminate all other sessions (keep current) */ router.post('/terminate-all-others', authenticate, modifyLimiter, async (req, res) => { try { const userId = req.user.userId; const currentToken = req.headers.authorization?.split(' ')[1]; db.run( 'DELETE FROM active_sessions WHERE user_id = ? AND session_token != ?', [userId, currentToken], async function(err) { if (err) { logger.error('Error terminating sessions:', err); return res.status(500).json({ error: 'Failed to terminate sessions' }); } const terminatedCount = this.changes; // Log the event await SecurityAuditLogger.logSessionEvent('SESSIONS_TERMINATED_BULK', userId, { ip: req.ip || req.headers['x-forwarded-for'], userAgent: req.headers['user-agent'], count: terminatedCount }); logger.info(`User ${userId} terminated ${terminatedCount} other sessions`); res.json({ message: `${terminatedCount} session(s) terminated successfully`, count: terminatedCount }); } ); } catch (error) { logger.error('Bulk session termination error:', error); res.status(500).json({ error: 'Failed to terminate sessions' }); } }); /** * Force logout user (admin only) - terminates all sessions */ router.post('/force-logout/:userId', authenticate, requireAdmin, modifyLimiter, async (req, res) => { try { const { userId } = req.params; // Validate user ID if (isNaN(parseInt(userId))) { return res.status(400).json({ error: 'Invalid user ID' }); } // Get user info db.get( 'SELECT username FROM users WHERE id = ?', [userId], async (err, user) => { if (err || !user) { return res.status(404).json({ error: 'User not found' }); } // Terminate all sessions for this user db.run( 'DELETE FROM active_sessions WHERE user_id = ?', [userId], async function(err) { if (err) { logger.error('Error force logging out user:', err); return res.status(500).json({ error: 'Failed to force logout' }); } const terminatedCount = this.changes; // Log the event await SecurityAuditLogger.logSessionEvent('FORCE_LOGOUT', userId, { ip: req.ip || req.headers['x-forwarded-for'], userAgent: req.headers['user-agent'], adminId: req.user.userId, count: terminatedCount }); logger.warn(`Admin ${req.user.userId} force logged out user ${userId} (${user.username}), terminated ${terminatedCount} sessions`); res.json({ message: `User ${user.username} has been logged out`, count: terminatedCount }); } ); } ); } catch (error) { logger.error('Force logout error:', error); res.status(500).json({ error: 'Failed to force logout' }); } }); /** * Get session statistics (admin only) */ router.get('/stats', authenticate, requireAdmin, readLimiter, async (req, res) => { try { const now = new Date().toISOString(); // Total active sessions db.get( 'SELECT COUNT(*) as total FROM active_sessions WHERE expires_at > ?', [now], (err, totalResult) => { if (err) { logger.error('Error fetching session stats:', err); return res.status(500).json({ error: 'Failed to fetch statistics' }); } // Sessions by user db.all( `SELECT u.username, u.email, COUNT(s.id) as session_count FROM users u LEFT JOIN active_sessions s ON u.id = s.user_id AND s.expires_at > ? GROUP BY u.id ORDER BY session_count DESC LIMIT 10`, [now], (err, userSessions) => { if (err) { logger.error('Error fetching user sessions:', err); return res.status(500).json({ error: 'Failed to fetch statistics' }); } // Recent sessions db.all( `SELECT s.*, u.username FROM active_sessions s JOIN users u ON s.user_id = u.id WHERE s.expires_at > ? ORDER BY s.created_at DESC LIMIT 20`, [now], (err, recentSessions) => { if (err) { logger.error('Error fetching recent sessions:', err); return res.status(500).json({ error: 'Failed to fetch statistics' }); } res.json({ totalActiveSessions: totalResult.total, topUsers: userSessions.filter(u => u.session_count > 0), recentSessions: recentSessions.map(s => ({ username: s.username, ip: s.ip_address, created: s.created_at, lastActive: s.last_activity })) }); } ); } ); } ); } catch (error) { logger.error('Session stats error:', error); res.status(500).json({ error: 'Failed to fetch statistics' }); } }); module.exports = router;