iot-agent/middleware/auth.js
2025-08-03 23:07:33 +05:30

199 lines
4.9 KiB
JavaScript

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const logger = require('../utils/logger');
const database = require('../config/database');
const redis = require('../config/redis');
// Protect routes - require authentication
const protect = async (req, res, next) => {
let token;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
return res.status(401).json({
success: false,
message: 'Not authorized to access this route'
});
}
try {
// Verify token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
// Check if user session exists in Redis
const session = await redis.getUserSession(decoded.id);
if (!session) {
return res.status(401).json({
success: false,
message: 'Session expired, please login again'
});
}
// Get user from database
const [users] = await database.query(
'SELECT id, username, email, role, status, created_at FROM users WHERE id = ? AND status = "active"',
[decoded.id]
);
if (users.length === 0) {
return res.status(401).json({
success: false,
message: 'User not found or inactive'
});
}
req.user = users[0];
next();
} catch (error) {
logger.error('Token verification failed:', error);
return res.status(401).json({
success: false,
message: 'Not authorized to access this route'
});
}
};
// Grant access to specific roles
const authorize = (...roles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({
success: false,
message: 'User not authenticated'
});
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({
success: false,
message: `User role ${req.user.role} is not authorized to access this route`
});
}
next();
};
};
// Optional authentication - doesn't fail if no token
const optionalAuth = async (req, res, next) => {
let token;
if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
token = req.headers.authorization.split(' ')[1];
}
if (!token) {
req.user = null;
return next();
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const session = await redis.getUserSession(decoded.id);
if (session) {
const [users] = await database.query(
'SELECT id, username, email, role, status FROM users WHERE id = ? AND status = "active"',
[decoded.id]
);
if (users.length > 0) {
req.user = users[0];
}
}
} catch (error) {
logger.debug('Optional auth failed:', error.message);
}
next();
};
// Rate limiting for authentication attempts
const authRateLimit = (req, res, next) => {
const key = `auth_attempts:${req.ip}`;
redis.get(key).then(attempts => {
const attemptCount = attempts ? parseInt(attempts) : 0;
if (attemptCount >= 5) {
return res.status(429).json({
success: false,
message: 'Too many authentication attempts. Please try again later.'
});
}
next();
}).catch(error => {
logger.error('Rate limit check failed:', error);
next(); // Continue if Redis is unavailable
});
};
// Log authentication attempts
const logAuthAttempt = (req, res, next) => {
const originalSend = res.send;
res.send = function(data) {
const response = JSON.parse(data);
if (response.success === false && req.path.includes('/login')) {
const key = `auth_attempts:${req.ip}`;
redis.get(key).then(attempts => {
const attemptCount = attempts ? parseInt(attempts) : 0;
redis.set(key, attemptCount + 1, 900); // 15 minutes
}).catch(error => {
logger.error('Failed to log auth attempt:', error);
});
logger.logSecurity('failed_login', {
ip: req.ip,
userAgent: req.get('User-Agent'),
username: req.body.username
}, 'warn');
} else if (response.success === true && req.path.includes('/login')) {
logger.logSecurity('successful_login', {
ip: req.ip,
userAgent: req.get('User-Agent'),
username: req.body.username
}, 'info');
}
originalSend.call(this, data);
};
next();
};
// Generate JWT token
const generateToken = (userId) => {
return jwt.sign(
{ id: userId },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
);
};
// Hash password
const hashPassword = async (password) => {
const salt = await bcrypt.genSalt(10);
return bcrypt.hash(password, salt);
};
// Compare password
const comparePassword = async (password, hashedPassword) => {
return bcrypt.compare(password, hashedPassword);
};
module.exports = {
protect,
authorize,
optionalAuth,
authRateLimit,
logAuthAttempt,
generateToken,
hashPassword,
comparePassword
};