Initial commit: StreamFlow IPTV platform
This commit is contained in:
commit
73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions
326
backend/routes/sessions.js
Normal file
326
backend/routes/sessions.js
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
/**
|
||||
* 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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue