130 lines
4.2 KiB
JavaScript
130 lines
4.2 KiB
JavaScript
|
|
const jwt = require('jsonwebtoken');
|
||
|
|
const logger = require('../utils/logger');
|
||
|
|
const db = require('../database/db').db;
|
||
|
|
const { SESSION_POLICY } = require('../utils/passwordPolicy');
|
||
|
|
|
||
|
|
const JWT_SECRET = process.env.JWT_SECRET || 'change_this_in_production';
|
||
|
|
|
||
|
|
const authenticate = (req, res, next) => {
|
||
|
|
// Check Authorization header first, then query parameter
|
||
|
|
let token = req.headers.authorization?.split(' ')[1];
|
||
|
|
|
||
|
|
if (!token && req.query.token) {
|
||
|
|
token = req.query.token;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!token) {
|
||
|
|
logger.info('[AUTH] No token provided');
|
||
|
|
return res.status(401).json({ error: 'Authentication required' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// CWE-532: Do not log tokens or token details - they are credentials
|
||
|
|
logger.info('[AUTH] Verifying authentication token');
|
||
|
|
|
||
|
|
try {
|
||
|
|
const decoded = jwt.verify(token, JWT_SECRET);
|
||
|
|
logger.info(`[AUTH] Token verified for user ${decoded.userId}`);
|
||
|
|
|
||
|
|
// Check session activity and idle timeout
|
||
|
|
db.get(
|
||
|
|
'SELECT * FROM active_sessions WHERE session_token = ? AND user_id = ?',
|
||
|
|
[token, decoded.userId],
|
||
|
|
(err, session) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Session check error:', err);
|
||
|
|
return res.status(500).json({ error: 'Session validation failed' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!session) {
|
||
|
|
logger.info('[AUTH] Session not found for token in database');
|
||
|
|
return res.status(401).json({ error: 'Session not found or expired', sessionExpired: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
logger.info('[AUTH] Session found, checking expiry');
|
||
|
|
|
||
|
|
// Check if session has expired (absolute timeout)
|
||
|
|
const now = new Date();
|
||
|
|
const expiresAt = new Date(session.expires_at);
|
||
|
|
|
||
|
|
if (now >= expiresAt) {
|
||
|
|
// Delete expired session
|
||
|
|
db.run('DELETE FROM active_sessions WHERE id = ?', [session.id]);
|
||
|
|
return res.status(401).json({ error: 'Session expired', sessionExpired: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check idle timeout (2 hours by default)
|
||
|
|
const lastActivity = new Date(session.last_activity);
|
||
|
|
const idleTimeMs = now - lastActivity;
|
||
|
|
const idleTimeoutMs = SESSION_POLICY.idleTimeout * 60 * 60 * 1000; // Convert hours to ms
|
||
|
|
|
||
|
|
if (idleTimeMs > idleTimeoutMs) {
|
||
|
|
// Session idle for too long - terminate it
|
||
|
|
db.run('DELETE FROM active_sessions WHERE id = ?', [session.id]);
|
||
|
|
logger.info(`Session ${session.id} terminated due to idle timeout (${idleTimeMs}ms idle)`);
|
||
|
|
return res.status(401).json({ error: 'Session expired due to inactivity', sessionExpired: true });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update last activity
|
||
|
|
db.run(
|
||
|
|
'UPDATE active_sessions SET last_activity = ? WHERE id = ?',
|
||
|
|
[now.toISOString(), session.id],
|
||
|
|
(updateErr) => {
|
||
|
|
if (updateErr) {
|
||
|
|
logger.error('Failed to update session activity:', updateErr);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
req.user = decoded;
|
||
|
|
req.sessionId = session.id;
|
||
|
|
next();
|
||
|
|
}
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Authentication error:', error);
|
||
|
|
logger.error(`[AUTH] JWT Verification Failed: ${error.name} - ${error.message}`);
|
||
|
|
|
||
|
|
// Provide more specific error messages
|
||
|
|
let errorMessage = 'Invalid or expired token';
|
||
|
|
if (error.name === 'TokenExpiredError') {
|
||
|
|
errorMessage = 'Token has expired';
|
||
|
|
} else if (error.name === 'JsonWebTokenError') {
|
||
|
|
errorMessage = 'Invalid token';
|
||
|
|
}
|
||
|
|
|
||
|
|
res.status(401).json({
|
||
|
|
error: errorMessage,
|
||
|
|
sessionExpired: true // This triggers automatic logout on frontend
|
||
|
|
});
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
const authorize = (...roles) => {
|
||
|
|
return (req, res, next) => {
|
||
|
|
if (!req.user) {
|
||
|
|
return res.status(401).json({ error: 'Authentication required' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!roles.includes(req.user.role)) {
|
||
|
|
return res.status(403).json({ error: 'Insufficient permissions' });
|
||
|
|
}
|
||
|
|
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
// Convenience middleware for admin-only routes
|
||
|
|
const requireAdmin = (req, res, next) => {
|
||
|
|
if (!req.user) {
|
||
|
|
return res.status(401).json({ error: 'Authentication required' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (req.user.role !== 'admin') {
|
||
|
|
return res.status(403).json({ error: 'Admin access required' });
|
||
|
|
}
|
||
|
|
|
||
|
|
next();
|
||
|
|
};
|
||
|
|
|
||
|
|
module.exports = { authenticate, authorize, requireAdmin };
|