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

410 lines
10 KiB
JavaScript

const express = require('express');
const { body, validationResult } = require('express-validator');
const { protect, generateToken, hashPassword, comparePassword, authRateLimit, logAuthAttempt } = require('../middleware/auth');
const database = require('../config/database');
const redis = require('../config/redis');
const logger = require('../utils/logger');
const router = express.Router();
// Register new user
router.post('/register', [
body('username').isLength({ min: 3 }).withMessage('Username must be at least 3 characters'),
body('email').isEmail().withMessage('Must be a valid email'),
body('password').isLength({ min: 6 }).withMessage('Password must be at least 6 characters'),
body('role').optional().isIn(['admin', 'operator', 'viewer']).withMessage('Invalid role')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { username, email, password, role = 'viewer' } = req.body;
// Check if user already exists
const existingUsers = await database.query(
'SELECT id FROM users WHERE username = ? OR email = ?',
[username, email]
);
if (existingUsers.length > 0) {
return res.status(400).json({
success: false,
message: 'Username or email already exists'
});
}
// Hash password
const passwordHash = await hashPassword(password);
// Create user
const result = await database.query(
'INSERT INTO users (username, email, password_hash, role) VALUES (?, ?, ?, ?)',
[username, email, passwordHash, role]
);
const userId = result.insertId;
// Generate token
const token = generateToken(userId);
// Store session in Redis
await redis.cacheUserSession(userId, {
userId,
username,
email,
role,
loginTime: new Date().toISOString()
});
logger.logSecurity('user_registered', { username, email, role }, 'info');
res.status(201).json({
success: true,
message: 'User registered successfully',
data: {
user: {
id: userId,
username,
email,
role
},
token
}
});
} catch (error) {
logger.error('Registration error:', error);
res.status(500).json({
success: false,
message: 'Registration failed'
});
}
});
// Login user
router.post('/login', [
body('username').notEmpty().withMessage('Username is required'),
body('password').notEmpty().withMessage('Password is required')
], authRateLimit, logAuthAttempt, async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { username, password } = req.body;
// Find user
const users = await database.query(
'SELECT id, username, email, password_hash, role, status FROM users WHERE username = ? OR email = ?',
[username, username]
);
if (users.length === 0) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
const user = users[0];
// Check if user is active
if (user.status !== 'active') {
return res.status(401).json({
success: false,
message: 'Account is not active'
});
}
// Verify password
const isValidPassword = await comparePassword(password, user.password_hash);
if (!isValidPassword) {
return res.status(401).json({
success: false,
message: 'Invalid credentials'
});
}
// Generate token
const token = generateToken(user.id);
// Store session in Redis
await redis.cacheUserSession(user.id, {
userId: user.id,
username: user.username,
email: user.email,
role: user.role,
loginTime: new Date().toISOString()
});
// Update last login
await database.query(
'UPDATE users SET last_login = NOW() WHERE id = ?',
[user.id]
);
logger.logSecurity('user_login', { username: user.username, userId: user.id }, 'info');
res.json({
success: true,
message: 'Login successful',
data: {
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role
},
token
}
});
} catch (error) {
logger.error('Login error:', error);
res.status(500).json({
success: false,
message: 'Login failed'
});
}
});
// Get current user profile
router.get('/profile', protect, async (req, res) => {
try {
const user = await database.query(
'SELECT id, username, email, role, status, created_at, last_login FROM users WHERE id = ?',
[req.user.id]
);
if (user.length === 0) {
return res.status(404).json({
success: false,
message: 'User not found'
});
}
res.json({
success: true,
data: user[0]
});
} catch (error) {
logger.error('Get profile error:', error);
res.status(500).json({
success: false,
message: 'Failed to get profile'
});
}
});
// Update user profile
router.put('/profile', protect, [
body('email').optional().isEmail().withMessage('Must be a valid email'),
body('currentPassword').optional().notEmpty().withMessage('Current password is required for changes')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { email, currentPassword, newPassword } = req.body;
const updates = {};
const params = [];
// Check if user wants to change password
if (newPassword) {
if (!currentPassword) {
return res.status(400).json({
success: false,
message: 'Current password is required to change password'
});
}
// Verify current password
const user = await database.query(
'SELECT password_hash FROM users WHERE id = ?',
[req.user.id]
);
const isValidPassword = await comparePassword(currentPassword, user[0].password_hash);
if (!isValidPassword) {
return res.status(400).json({
success: false,
message: 'Current password is incorrect'
});
}
// Hash new password
const newPasswordHash = await hashPassword(newPassword);
updates.password_hash = newPasswordHash;
params.push(newPasswordHash);
}
// Update email if provided
if (email) {
// Check if email is already taken
const existingUser = await database.query(
'SELECT id FROM users WHERE email = ? AND id != ?',
[email, req.user.id]
);
if (existingUser.length > 0) {
return res.status(400).json({
success: false,
message: 'Email is already taken'
});
}
updates.email = email;
params.push(email);
}
if (Object.keys(updates).length === 0) {
return res.status(400).json({
success: false,
message: 'No updates provided'
});
}
// Build update query
const updateFields = Object.keys(updates).map(key => `${key} = ?`).join(', ');
params.push(req.user.id);
await database.query(
`UPDATE users SET ${updateFields} WHERE id = ?`,
params
);
logger.logSecurity('profile_updated', { userId: req.user.id }, 'info');
res.json({
success: true,
message: 'Profile updated successfully'
});
} catch (error) {
logger.error('Update profile error:', error);
res.status(500).json({
success: false,
message: 'Failed to update profile'
});
}
});
// Logout user
router.post('/logout', protect, async (req, res) => {
try {
// Invalidate session in Redis
await redis.invalidateUserSession(req.user.id);
logger.logSecurity('user_logout', { userId: req.user.id }, 'info');
res.json({
success: true,
message: 'Logout successful'
});
} catch (error) {
logger.error('Logout error:', error);
res.status(500).json({
success: false,
message: 'Logout failed'
});
}
});
// Refresh token
router.post('/refresh', protect, async (req, res) => {
try {
// Generate new token
const newToken = generateToken(req.user.id);
// Update session in Redis
await redis.cacheUserSession(req.user.id, {
userId: req.user.id,
username: req.user.username,
email: req.user.email,
role: req.user.role,
loginTime: new Date().toISOString()
});
res.json({
success: true,
message: 'Token refreshed successfully',
data: {
token: newToken
}
});
} catch (error) {
logger.error('Token refresh error:', error);
res.status(500).json({
success: false,
message: 'Token refresh failed'
});
}
});
// Change password
router.post('/change-password', protect, [
body('currentPassword').notEmpty().withMessage('Current password is required'),
body('newPassword').isLength({ min: 6 }).withMessage('New password must be at least 6 characters')
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
success: false,
errors: errors.array()
});
}
const { currentPassword, newPassword } = req.body;
// Get current user
const user = await database.query(
'SELECT password_hash FROM users WHERE id = ?',
[req.user.id]
);
// Verify current password
const isValidPassword = await comparePassword(currentPassword, user[0].password_hash);
if (!isValidPassword) {
return res.status(400).json({
success: false,
message: 'Current password is incorrect'
});
}
// Hash new password
const newPasswordHash = await hashPassword(newPassword);
// Update password
await database.query(
'UPDATE users SET password_hash = ? WHERE id = ?',
[newPasswordHash, req.user.id]
);
logger.logSecurity('password_changed', { userId: req.user.id }, 'info');
res.json({
success: true,
message: 'Password changed successfully'
});
} catch (error) {
logger.error('Change password error:', error);
res.status(500).json({
success: false,
message: 'Failed to change password'
});
}
});
module.exports = router;