const express = require('express'); const router = express.Router(); const bcrypt = require('bcryptjs'); const { body, validationResult } = require('express-validator'); const { authenticate, requireAdmin } = require('../middleware/auth'); const { modifyLimiter, readLimiter } = require('../middleware/rateLimiter'); const { db } = require('../database/db'); const logger = require('../utils/logger'); const SecurityAuditLogger = require('../utils/securityAudit'); // Get all users (admin only) router.get('/', readLimiter, authenticate, requireAdmin, async (req, res) => { const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; db.all( `SELECT id, username, email, role, is_active, created_at, updated_at, created_by, failed_login_attempts, last_failed_login, locked_until, last_login_at, last_login_ip, password_changed_at, password_expires_at FROM users ORDER BY created_at DESC`, [], async (err, users) => { if (err) { logger.error('Error fetching users:', err); return res.status(500).json({ error: 'Failed to fetch users' }); } // CWE-778: Log sensitive data access await SecurityAuditLogger.logSensitiveDataAccess(req.user.userId, 'user_list', { ip, userAgent, recordCount: users.length, scope: 'all', accessMethod: 'view' }); res.json(users); } ); }); // Get single user (admin only) router.get('/:id', readLimiter, authenticate, requireAdmin, async (req, res) => { const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; db.get( `SELECT id, username, email, role, is_active, created_at, updated_at, created_by FROM users WHERE id = ?`, [req.params.id], async (err, user) => { if (err) { logger.error('Error fetching user:', err); return res.status(500).json({ error: 'Failed to fetch user' }); } if (!user) { return res.status(404).json({ error: 'User not found' }); } // CWE-778: Log sensitive data access await SecurityAuditLogger.logSensitiveDataAccess(req.user.userId, 'user_details', { ip, userAgent, recordCount: 1, scope: 'specific', accessMethod: 'view', filters: { userId: req.params.id } }); res.json(user); } ); }); // Create user (admin only) router.post('/', modifyLimiter, authenticate, requireAdmin, [ body('username').trim().isLength({ min: 3, max: 50 }).isAlphanumeric(), body('email').isEmail().normalizeEmail(), body('password').isLength({ min: 8 }), body('role').isIn(['user', 'admin']) ], async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { username, email, password, role } = req.body; try { const hashedPassword = await bcrypt.hash(password, 10); const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; db.run( `INSERT INTO users (username, email, password, role, must_change_password, created_by) VALUES (?, ?, ?, ?, ?, ?)`, [username, email, hashedPassword, role, 1, req.user.userId], async function(err) { if (err) { if (err.message.includes('UNIQUE')) { return res.status(400).json({ error: 'Username or email already exists' }); } logger.error('User creation error:', err); return res.status(500).json({ error: 'Failed to create user' }); } const newUserId = this.lastID; // CWE-778: Log admin activity await SecurityAuditLogger.logAdminActivity(req.user.userId, 'user_created', { ip, userAgent, targetUserId: newUserId, targetUsername: username, adminUsername: req.user.username || 'admin', changes: { username, email, role } }); db.get( `SELECT id, username, email, role, is_active, created_at, created_by FROM users WHERE id = ?`, [newUserId], (err, user) => { if (err) { return res.status(500).json({ error: 'User created but failed to fetch details' }); } res.status(201).json(user); } ); } ); } catch (error) { logger.error('User creation error:', error); res.status(500).json({ error: 'Failed to create user' }); } } ); // Update user (admin only) router.patch('/:id', modifyLimiter, authenticate, requireAdmin, [ body('username').optional().trim().isLength({ min: 3, max: 50 }).isAlphanumeric(), body('email').optional().isEmail().normalizeEmail(), body('role').optional().isIn(['user', 'admin']), body('is_active').optional().isBoolean() ], async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { id } = req.params; const updates = []; const values = []; // Build dynamic update query if (req.body.username !== undefined) { updates.push('username = ?'); values.push(req.body.username); } if (req.body.email !== undefined) { updates.push('email = ?'); values.push(req.body.email); } // Check if role or is_active is being changed (for audit logging) const isRoleChange = req.body.role !== undefined; const isStatusChange = req.body.is_active !== undefined; if (req.body.role !== undefined) { updates.push('role = ?'); values.push(req.body.role); } if (req.body.is_active !== undefined) { updates.push('is_active = ?'); values.push(req.body.is_active ? 1 : 0); } if (updates.length === 0) { return res.status(400).json({ error: 'No valid fields to update' }); } // Get current user data for audit logging db.get('SELECT role, is_active, username FROM users WHERE id = ?', [id], async (err, existingUser) => { if (err || !existingUser) { return res.status(404).json({ error: 'User not found' }); } updates.push('updated_at = CURRENT_TIMESTAMP'); values.push(id); db.run( `UPDATE users SET ${updates.join(', ')} WHERE id = ?`, values, async function(err) { if (err) { if (err.message.includes('UNIQUE')) { return res.status(400).json({ error: 'Username or email already exists' }); } logger.error('User update error:', err); return res.status(500).json({ error: 'Failed to update user' }); } if (this.changes === 0) { return res.status(404).json({ error: 'User not found' }); } const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; // CWE-778: Log privilege changes if role changed if (isRoleChange && req.body.role !== existingUser.role) { await SecurityAuditLogger.logPrivilegeChange(parseInt(id), 'role_change', { ip, userAgent, previousRole: existingUser.role, newRole: req.body.role, changedBy: req.user.userId, changedByUsername: req.user.username || 'system', targetUsername: existingUser.username }); } // CWE-778: Log account status changes if (isStatusChange && req.body.is_active !== (existingUser.is_active === 1)) { const newStatus = req.body.is_active ? 'active' : 'inactive'; await SecurityAuditLogger.logAccountStatusChange(parseInt(id), newStatus, { ip, userAgent, previousStatus: existingUser.is_active === 1 ? 'active' : 'inactive', changedBy: req.user.userId, changedByUsername: req.user.username || 'system', targetUsername: existingUser.username, reason: 'admin_action' }); } db.get( `SELECT id, username, email, role, is_active, created_at, updated_at, created_by FROM users WHERE id = ?`, [id], (err, user) => { if (err) { return res.status(500).json({ error: 'User updated but failed to fetch details' }); } res.json(user); } ); } ); }); } ); // Reset user password (admin only) router.post('/:id/reset-password', modifyLimiter, authenticate, requireAdmin, [ body('newPassword').isLength({ min: 8 }) ], async (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { id } = req.params; const { newPassword } = req.body; try { const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; // Get user info first db.get('SELECT username FROM users WHERE id = ?', [id], async (err, user) => { if (err || !user) { return res.status(404).json({ error: 'User not found' }); } const hashedPassword = await bcrypt.hash(newPassword, 10); db.run( 'UPDATE users SET password = ?, must_change_password = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [hashedPassword, id], async function(err) { if (err) { logger.error('Password reset error:', err); return res.status(500).json({ error: 'Failed to reset password' }); } if (this.changes === 0) { return res.status(404).json({ error: 'User not found' }); } // CWE-778: Log admin activity await SecurityAuditLogger.logAdminActivity(req.user.userId, 'password_reset', { ip, userAgent, targetUserId: id, targetUsername: user.username, adminUsername: req.user.username || 'admin', reason: 'admin_initiated' }); res.json({ message: 'Password reset successfully. User must change password on next login.' }); } ); }); } catch (error) { logger.error('Password reset error:', error); res.status(500).json({ error: 'Failed to reset password' }); } } ); // Unlock account (admin only) router.post('/:id/unlock', modifyLimiter, authenticate, requireAdmin, async (req, res) => { const { id } = req.params; const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; try { // Get user info first db.get('SELECT username, locked_until FROM users WHERE id = ?', [id], async (err, user) => { if (err || !user) { return res.status(404).json({ error: 'User not found' }); } db.run( 'UPDATE users SET locked_until = NULL, failed_login_attempts = 0 WHERE id = ?', [id], async function(err) { if (err) { logger.error('Account unlock error:', err); return res.status(500).json({ error: 'Failed to unlock account' }); } if (this.changes === 0) { return res.status(404).json({ error: 'User not found' }); } // CWE-778: Log admin activity await SecurityAuditLogger.logAdminActivity(req.user.userId, 'account_unlocked', { ip, userAgent, targetUserId: id, targetUsername: user.username, adminUsername: req.user.username || 'admin', changes: { locked_until: user.locked_until, failed_login_attempts: 0 }, reason: 'admin_unlock' }); logger.info(`Admin ${req.user.userId} unlocked account ${id}`); res.json({ message: 'Account unlocked successfully' }); } ); }); } catch (error) { logger.error('Account unlock error:', error); res.status(500).json({ error: 'Failed to unlock account' }); } }); // Delete user (admin only) router.delete('/:id', modifyLimiter, authenticate, requireAdmin, async (req, res) => { const { id } = req.params; const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; // Prevent deleting yourself if (parseInt(id) === req.user.userId) { return res.status(400).json({ error: 'Cannot delete your own account' }); } // Check if this is the last admin db.get( "SELECT COUNT(*) as count FROM users WHERE role = 'admin' AND is_active = 1", [], (err, result) => { if (err) { logger.error('Error checking admin count:', err); return res.status(500).json({ error: 'Failed to delete user' }); } db.get('SELECT username, email, role FROM users WHERE id = ?', [id], async (err, user) => { if (err || !user) { return res.status(404).json({ error: 'User not found' }); } if (user.role === 'admin' && result.count <= 1) { return res.status(400).json({ error: 'Cannot delete the last admin account' }); } db.run('DELETE FROM users WHERE id = ?', [id], async function(err) { if (err) { logger.error('User deletion error:', err); return res.status(500).json({ error: 'Failed to delete user' }); } if (this.changes === 0) { return res.status(404).json({ error: 'User not found' }); } // CWE-778: Log admin activity - user deletion await SecurityAuditLogger.logAdminActivity(req.user.userId, 'user_deleted', { ip, userAgent, targetUserId: id, targetUsername: user.username, adminUsername: req.user.username || 'admin', changes: { deleted: { username: user.username, email: user.email, role: user.role } }, reason: 'admin_deletion' }); res.json({ message: 'User deleted successfully' }); }); }); } ); }); module.exports = router;