streamflow/backend/routes/sessions.js
2025-12-17 00:42:43 +00:00

326 lines
10 KiB
JavaScript

/**
* 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;