Initial commit: StreamFlow IPTV platform
This commit is contained in:
commit
73a8ae9ffd
1240 changed files with 278451 additions and 0 deletions
434
backend/routes/users.js
Normal file
434
backend/routes/users.js
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue