621 lines
20 KiB
JavaScript
621 lines
20 KiB
JavaScript
|
|
const express = require('express');
|
||
|
|
const router = express.Router();
|
||
|
|
const { body, validationResult } = require('express-validator');
|
||
|
|
const { authenticate, requireAdmin } = require('../middleware/auth');
|
||
|
|
const { requirePermission, requireAllPermissions, PERMISSIONS, DEFAULT_ROLES, clearAllPermissionCache, clearUserPermissionCache, logPermissionAction, getUserPermissions } = require('../middleware/rbac');
|
||
|
|
const { modifyLimiter, readLimiter } = require('../middleware/rateLimiter');
|
||
|
|
const { db } = require('../database/db');
|
||
|
|
const logger = require('../utils/logger');
|
||
|
|
const SecurityAuditLogger = require('../utils/securityAudit');
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all available permissions
|
||
|
|
* Returns the complete permission catalog
|
||
|
|
*/
|
||
|
|
router.get('/permissions', authenticate, requirePermission('users.manage_roles'), readLimiter, (req, res) => {
|
||
|
|
res.json({
|
||
|
|
permissions: Object.entries(PERMISSIONS).map(([key, description]) => ({
|
||
|
|
key,
|
||
|
|
description
|
||
|
|
})),
|
||
|
|
categories: {
|
||
|
|
'User Management': Object.keys(PERMISSIONS).filter(k => k.startsWith('users.')),
|
||
|
|
'Session Management': Object.keys(PERMISSIONS).filter(k => k.startsWith('sessions.')),
|
||
|
|
'Content Management': Object.keys(PERMISSIONS).filter(k => k.startsWith('playlists.') || k.startsWith('channels.') || k.startsWith('favorites.') || k.startsWith('history.')),
|
||
|
|
'System & Settings': Object.keys(PERMISSIONS).filter(k => k.startsWith('settings.') || k.startsWith('stats.') || k.startsWith('backup.')),
|
||
|
|
'Security Management': Object.keys(PERMISSIONS).filter(k => k.startsWith('security.')),
|
||
|
|
'Search & Discovery': Object.keys(PERMISSIONS).filter(k => k.startsWith('search.')),
|
||
|
|
'VPN & Network': Object.keys(PERMISSIONS).filter(k => k.startsWith('vpn.'))
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get all roles
|
||
|
|
*/
|
||
|
|
router.get('/roles', authenticate, requirePermission('users.view'), readLimiter, (req, res) => {
|
||
|
|
db.all(
|
||
|
|
`SELECT id, role_key, name, description, permissions, is_system_role, created_at, updated_at
|
||
|
|
FROM roles
|
||
|
|
ORDER BY is_system_role DESC, name ASC`,
|
||
|
|
[],
|
||
|
|
(err, roles) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching roles:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to fetch roles' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse permissions JSON
|
||
|
|
const rolesWithParsedPermissions = roles.map(role => ({
|
||
|
|
...role,
|
||
|
|
permissions: JSON.parse(role.permissions || '[]'),
|
||
|
|
is_system_role: Boolean(role.is_system_role)
|
||
|
|
}));
|
||
|
|
|
||
|
|
res.json(rolesWithParsedPermissions);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get single role by key
|
||
|
|
*/
|
||
|
|
router.get('/roles/:roleKey', authenticate, requirePermission('users.view'), readLimiter, (req, res) => {
|
||
|
|
const { roleKey } = req.params;
|
||
|
|
|
||
|
|
db.get(
|
||
|
|
`SELECT id, role_key, name, description, permissions, is_system_role, created_at, updated_at
|
||
|
|
FROM roles WHERE role_key = ?`,
|
||
|
|
[roleKey],
|
||
|
|
(err, role) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching role:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to fetch role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!role) {
|
||
|
|
return res.status(404).json({ error: 'Role not found' });
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
...role,
|
||
|
|
permissions: JSON.parse(role.permissions || '[]'),
|
||
|
|
is_system_role: Boolean(role.is_system_role)
|
||
|
|
});
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create custom role
|
||
|
|
* Only admins with users.manage_roles permission
|
||
|
|
*/
|
||
|
|
router.post('/roles',
|
||
|
|
authenticate,
|
||
|
|
requireAllPermissions(['users.manage_roles', 'users.create']),
|
||
|
|
modifyLimiter,
|
||
|
|
[
|
||
|
|
body('role_key').trim().isLength({ min: 2, max: 50 }).matches(/^[a-z_]+$/).withMessage('Role key must be lowercase with underscores only'),
|
||
|
|
body('name').trim().isLength({ min: 2, max: 100 }),
|
||
|
|
body('description').optional().trim().isLength({ max: 500 }),
|
||
|
|
body('permissions').isArray().withMessage('Permissions must be an array'),
|
||
|
|
body('permissions.*').isString().isIn(Object.keys(PERMISSIONS)).withMessage('Invalid permission')
|
||
|
|
],
|
||
|
|
async (req, res) => {
|
||
|
|
const errors = validationResult(req);
|
||
|
|
if (!errors.isEmpty()) {
|
||
|
|
return res.status(400).json({ errors: errors.array() });
|
||
|
|
}
|
||
|
|
|
||
|
|
const { role_key, name, description, permissions } = req.body;
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Check if role key already exists
|
||
|
|
db.get('SELECT id FROM roles WHERE role_key = ?', [role_key], (err, existing) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error checking role existence:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to create role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (existing) {
|
||
|
|
return res.status(409).json({ error: 'Role key already exists' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Create new role
|
||
|
|
db.run(
|
||
|
|
`INSERT INTO roles (role_key, name, description, permissions, is_system_role)
|
||
|
|
VALUES (?, ?, ?, ?, 0)`,
|
||
|
|
[role_key, name, description || '', JSON.stringify(permissions)],
|
||
|
|
function(err) {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error creating role:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to create role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log action
|
||
|
|
logPermissionAction(
|
||
|
|
req.user.userId,
|
||
|
|
'role_created',
|
||
|
|
'role',
|
||
|
|
this.lastID,
|
||
|
|
null,
|
||
|
|
{ role_key, name, permissions },
|
||
|
|
req
|
||
|
|
);
|
||
|
|
|
||
|
|
logger.info(`Role created: ${role_key} by user ${req.user.userId}`);
|
||
|
|
|
||
|
|
// Fetch and return the created role
|
||
|
|
db.get(
|
||
|
|
'SELECT id, role_key, name, description, permissions, is_system_role, created_at FROM roles WHERE id = ?',
|
||
|
|
[this.lastID],
|
||
|
|
(err, role) => {
|
||
|
|
if (err) {
|
||
|
|
return res.status(500).json({ error: 'Role created but failed to fetch details' });
|
||
|
|
}
|
||
|
|
res.status(201).json({
|
||
|
|
...role,
|
||
|
|
permissions: JSON.parse(role.permissions),
|
||
|
|
is_system_role: Boolean(role.is_system_role)
|
||
|
|
});
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Role creation error:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to create role' });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Update role permissions
|
||
|
|
* Cannot modify system roles
|
||
|
|
*/
|
||
|
|
router.patch('/roles/:roleKey',
|
||
|
|
authenticate,
|
||
|
|
requirePermission('users.manage_roles'),
|
||
|
|
modifyLimiter,
|
||
|
|
[
|
||
|
|
body('name').optional().trim().isLength({ min: 2, max: 100 }),
|
||
|
|
body('description').optional().trim().isLength({ max: 500 }),
|
||
|
|
body('permissions').optional().isArray(),
|
||
|
|
body('permissions.*').optional().isString().isIn(Object.keys(PERMISSIONS))
|
||
|
|
],
|
||
|
|
async (req, res) => {
|
||
|
|
const errors = validationResult(req);
|
||
|
|
if (!errors.isEmpty()) {
|
||
|
|
return res.status(400).json({ errors: errors.array() });
|
||
|
|
}
|
||
|
|
|
||
|
|
const { roleKey } = req.params;
|
||
|
|
const { name, description, permissions } = req.body;
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Check if role exists and is not a system role
|
||
|
|
db.get('SELECT * FROM roles WHERE role_key = ?', [roleKey], (err, role) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching role:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to update role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!role) {
|
||
|
|
return res.status(404).json({ error: 'Role not found' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (role.is_system_role) {
|
||
|
|
return res.status(403).json({ error: 'Cannot modify system roles' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Build update query
|
||
|
|
const updates = [];
|
||
|
|
const params = [];
|
||
|
|
|
||
|
|
if (name !== undefined) {
|
||
|
|
updates.push('name = ?');
|
||
|
|
params.push(name);
|
||
|
|
}
|
||
|
|
if (description !== undefined) {
|
||
|
|
updates.push('description = ?');
|
||
|
|
params.push(description);
|
||
|
|
}
|
||
|
|
if (permissions !== undefined) {
|
||
|
|
updates.push('permissions = ?');
|
||
|
|
params.push(JSON.stringify(permissions));
|
||
|
|
}
|
||
|
|
|
||
|
|
if (updates.length === 0) {
|
||
|
|
return res.status(400).json({ error: 'No fields to update' });
|
||
|
|
}
|
||
|
|
|
||
|
|
updates.push('updated_at = CURRENT_TIMESTAMP');
|
||
|
|
params.push(roleKey);
|
||
|
|
|
||
|
|
// Update role
|
||
|
|
db.run(
|
||
|
|
`UPDATE roles SET ${updates.join(', ')} WHERE role_key = ?`,
|
||
|
|
params,
|
||
|
|
function(err) {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error updating role:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to update role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.changes === 0) {
|
||
|
|
return res.status(404).json({ error: 'Role not found' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log action
|
||
|
|
logPermissionAction(
|
||
|
|
req.user.userId,
|
||
|
|
'role_updated',
|
||
|
|
'role',
|
||
|
|
role.id,
|
||
|
|
{ name: role.name, description: role.description, permissions: JSON.parse(role.permissions) },
|
||
|
|
{ name, description, permissions },
|
||
|
|
req
|
||
|
|
);
|
||
|
|
|
||
|
|
// Clear permission cache as role permissions changed
|
||
|
|
clearAllPermissionCache();
|
||
|
|
|
||
|
|
logger.info(`Role updated: ${roleKey} by user ${req.user.userId}`);
|
||
|
|
|
||
|
|
// Fetch and return updated role
|
||
|
|
db.get(
|
||
|
|
'SELECT id, role_key, name, description, permissions, is_system_role, updated_at FROM roles WHERE role_key = ?',
|
||
|
|
[roleKey],
|
||
|
|
(err, updatedRole) => {
|
||
|
|
if (err) {
|
||
|
|
return res.status(500).json({ error: 'Role updated but failed to fetch details' });
|
||
|
|
}
|
||
|
|
res.json({
|
||
|
|
...updatedRole,
|
||
|
|
permissions: JSON.parse(updatedRole.permissions),
|
||
|
|
is_system_role: Boolean(updatedRole.is_system_role)
|
||
|
|
});
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Role update error:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to update role' });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Delete custom role
|
||
|
|
* Cannot delete system roles or roles assigned to users
|
||
|
|
*/
|
||
|
|
router.delete('/roles/:roleKey',
|
||
|
|
authenticate,
|
||
|
|
requirePermission('users.manage_roles'),
|
||
|
|
modifyLimiter,
|
||
|
|
async (req, res) => {
|
||
|
|
const { roleKey } = req.params;
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Check if role exists
|
||
|
|
db.get('SELECT * FROM roles WHERE role_key = ?', [roleKey], (err, role) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching role:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to delete role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!role) {
|
||
|
|
return res.status(404).json({ error: 'Role not found' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (role.is_system_role) {
|
||
|
|
return res.status(403).json({ error: 'Cannot delete system roles' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if role is assigned to any users
|
||
|
|
db.get('SELECT COUNT(*) as count FROM users WHERE role = ?', [roleKey], (err, result) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error checking role usage:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to delete role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (result.count > 0) {
|
||
|
|
return res.status(409).json({
|
||
|
|
error: 'Cannot delete role that is assigned to users',
|
||
|
|
users_count: result.count
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Delete role
|
||
|
|
db.run('DELETE FROM roles WHERE role_key = ?', [roleKey], function(err) {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error deleting role:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to delete role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Log action
|
||
|
|
logPermissionAction(
|
||
|
|
req.user.userId,
|
||
|
|
'role_deleted',
|
||
|
|
'role',
|
||
|
|
role.id,
|
||
|
|
{ role_key: roleKey, name: role.name },
|
||
|
|
null,
|
||
|
|
req
|
||
|
|
);
|
||
|
|
|
||
|
|
logger.info(`Role deleted: ${roleKey} by user ${req.user.userId}`);
|
||
|
|
|
||
|
|
res.json({ message: 'Role deleted successfully' });
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Role deletion error:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to delete role' });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get user's current permissions
|
||
|
|
*/
|
||
|
|
router.get('/my-permissions', authenticate, readLimiter, async (req, res) => {
|
||
|
|
try {
|
||
|
|
const permissions = await getUserPermissions(req.user.userId);
|
||
|
|
|
||
|
|
// Get role info
|
||
|
|
db.get(
|
||
|
|
'SELECT u.role, r.name as role_name, r.description as role_description FROM users u LEFT JOIN roles r ON u.role = r.role_key WHERE u.id = ?',
|
||
|
|
[req.user.userId],
|
||
|
|
(err, roleInfo) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching role info:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to fetch permissions' });
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
role: roleInfo?.role || 'unknown',
|
||
|
|
role_name: roleInfo?.role_name || 'Unknown',
|
||
|
|
role_description: roleInfo?.role_description || '',
|
||
|
|
permissions,
|
||
|
|
permission_details: permissions.map(p => ({
|
||
|
|
key: p,
|
||
|
|
description: PERMISSIONS[p] || 'Unknown permission'
|
||
|
|
}))
|
||
|
|
});
|
||
|
|
}
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Error fetching user permissions:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to fetch permissions' });
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Assign role to user
|
||
|
|
* Requires users.manage_roles permission
|
||
|
|
*/
|
||
|
|
router.post('/users/:userId/role',
|
||
|
|
authenticate,
|
||
|
|
requirePermission('users.manage_roles'),
|
||
|
|
modifyLimiter,
|
||
|
|
[
|
||
|
|
body('role').trim().notEmpty().withMessage('Role is required')
|
||
|
|
],
|
||
|
|
async (req, res) => {
|
||
|
|
const errors = validationResult(req);
|
||
|
|
if (!errors.isEmpty()) {
|
||
|
|
return res.status(400).json({ errors: errors.array() });
|
||
|
|
}
|
||
|
|
|
||
|
|
const { userId } = req.params;
|
||
|
|
const { role } = req.body;
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Check if role exists
|
||
|
|
db.get('SELECT role_key FROM roles WHERE role_key = ?', [role], (err, roleExists) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error checking role:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to assign role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!roleExists) {
|
||
|
|
return res.status(404).json({ error: 'Role not found' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if user exists
|
||
|
|
db.get('SELECT id, username, role FROM users WHERE id = ?', [userId], (err, user) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching user:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to assign role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!user) {
|
||
|
|
return res.status(404).json({ error: 'User not found' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Prevent modifying own role
|
||
|
|
if (parseInt(userId) === req.user.userId) {
|
||
|
|
return res.status(403).json({ error: 'Cannot modify your own role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
const oldRole = user.role;
|
||
|
|
|
||
|
|
// Update user role
|
||
|
|
db.run(
|
||
|
|
'UPDATE users SET role = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?',
|
||
|
|
[role, userId],
|
||
|
|
async function(err) {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error updating user role:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to assign role' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clear user's permission cache
|
||
|
|
clearUserPermissionCache(parseInt(userId));
|
||
|
|
|
||
|
|
const ip = req.ip || req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||
|
|
const userAgent = req.headers['user-agent'];
|
||
|
|
|
||
|
|
// CWE-778: Log comprehensive privilege change
|
||
|
|
await SecurityAuditLogger.logPrivilegeChange(parseInt(userId), 'role_change', {
|
||
|
|
ip,
|
||
|
|
userAgent,
|
||
|
|
previousRole: oldRole,
|
||
|
|
newRole: role,
|
||
|
|
changedBy: req.user.userId,
|
||
|
|
changedByUsername: req.user.username || 'system',
|
||
|
|
targetUsername: user.username
|
||
|
|
});
|
||
|
|
|
||
|
|
// Log action
|
||
|
|
logPermissionAction(
|
||
|
|
req.user.userId,
|
||
|
|
'role_assigned',
|
||
|
|
'user',
|
||
|
|
parseInt(userId),
|
||
|
|
{ role: oldRole },
|
||
|
|
{ role },
|
||
|
|
req
|
||
|
|
);
|
||
|
|
|
||
|
|
logger.info(`Role assigned: ${role} to user ${userId} by ${req.user.userId}`);
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
message: 'Role assigned successfully',
|
||
|
|
user_id: userId,
|
||
|
|
old_role: oldRole,
|
||
|
|
new_role: role
|
||
|
|
});
|
||
|
|
}
|
||
|
|
);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Role assignment error:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to assign role' });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get permission audit log
|
||
|
|
* Admin only
|
||
|
|
*/
|
||
|
|
router.get('/audit-log',
|
||
|
|
authenticate,
|
||
|
|
requirePermission('security.view_audit'),
|
||
|
|
readLimiter,
|
||
|
|
async (req, res) => {
|
||
|
|
const { limit = 100, offset = 0, userId, action, targetType } = req.query;
|
||
|
|
|
||
|
|
try {
|
||
|
|
let query = `
|
||
|
|
SELECT pal.*, u.username
|
||
|
|
FROM permission_audit_log pal
|
||
|
|
JOIN users u ON pal.user_id = u.id
|
||
|
|
WHERE 1=1
|
||
|
|
`;
|
||
|
|
const params = [];
|
||
|
|
|
||
|
|
if (userId) {
|
||
|
|
query += ' AND pal.user_id = ?';
|
||
|
|
params.push(userId);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (action) {
|
||
|
|
query += ' AND pal.action = ?';
|
||
|
|
params.push(action);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (targetType) {
|
||
|
|
query += ' AND pal.target_type = ?';
|
||
|
|
params.push(targetType);
|
||
|
|
}
|
||
|
|
|
||
|
|
query += ' ORDER BY pal.created_at DESC LIMIT ? OFFSET ?';
|
||
|
|
params.push(parseInt(limit), parseInt(offset));
|
||
|
|
|
||
|
|
db.all(query, params, (err, logs) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching audit log:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to fetch audit log' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Parse JSON fields
|
||
|
|
const parsedLogs = logs.map(log => ({
|
||
|
|
...log,
|
||
|
|
old_value: log.old_value ? JSON.parse(log.old_value) : null,
|
||
|
|
new_value: log.new_value ? JSON.parse(log.new_value) : null
|
||
|
|
}));
|
||
|
|
|
||
|
|
res.json({ logs: parsedLogs, limit: parseInt(limit), offset: parseInt(offset) });
|
||
|
|
});
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Audit log fetch error:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to fetch audit log' });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get permission statistics
|
||
|
|
* Shows which permissions are most used
|
||
|
|
*/
|
||
|
|
router.get('/stats',
|
||
|
|
authenticate,
|
||
|
|
requirePermission('security.view_audit'),
|
||
|
|
readLimiter,
|
||
|
|
async (req, res) => {
|
||
|
|
try {
|
||
|
|
// Get role distribution
|
||
|
|
db.all(
|
||
|
|
`SELECT r.name, r.role_key, COUNT(u.id) as user_count
|
||
|
|
FROM roles r
|
||
|
|
LEFT JOIN users u ON r.role_key = u.role
|
||
|
|
GROUP BY r.role_key
|
||
|
|
ORDER BY user_count DESC`,
|
||
|
|
[],
|
||
|
|
(err, roleStats) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching role stats:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to fetch statistics' });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get recent permission actions
|
||
|
|
db.all(
|
||
|
|
`SELECT action, COUNT(*) as count
|
||
|
|
FROM permission_audit_log
|
||
|
|
WHERE created_at >= datetime('now', '-30 days')
|
||
|
|
GROUP BY action
|
||
|
|
ORDER BY count DESC`,
|
||
|
|
[],
|
||
|
|
(err, actionStats) => {
|
||
|
|
if (err) {
|
||
|
|
logger.error('Error fetching action stats:', err);
|
||
|
|
return res.status(500).json({ error: 'Failed to fetch statistics' });
|
||
|
|
}
|
||
|
|
|
||
|
|
res.json({
|
||
|
|
role_distribution: roleStats,
|
||
|
|
recent_actions: actionStats,
|
||
|
|
total_permissions: Object.keys(PERMISSIONS).length,
|
||
|
|
total_roles: roleStats.length
|
||
|
|
});
|
||
|
|
}
|
||
|
|
);
|
||
|
|
}
|
||
|
|
);
|
||
|
|
} catch (error) {
|
||
|
|
logger.error('Stats fetch error:', error);
|
||
|
|
res.status(500).json({ error: 'Failed to fetch statistics' });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
);
|
||
|
|
|
||
|
|
module.exports = router;
|