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;