Implement RBAC roles system
This commit is contained in:
+224
@@ -0,0 +1,224 @@
|
||||
const PERMISSION_SECTIONS = [
|
||||
{
|
||||
key: 'dashboard',
|
||||
order: 10,
|
||||
name: 'Dashboard',
|
||||
sectionName: 'Main navigation',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'Access the dashboard overview.' },
|
||||
{ key: 'allow', name: 'Allow', description: 'Send global player commands.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'clients',
|
||||
order: 20,
|
||||
name: 'Connected clients',
|
||||
sectionName: 'Main navigation',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'View connected player clients and live status.' },
|
||||
{ key: 'allow', name: 'Allow', description: 'Use the connected client command buttons.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'screens',
|
||||
order: 30,
|
||||
name: 'Screens',
|
||||
sectionName: 'Content',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'View the screen list and open screen details.' },
|
||||
{ key: 'create', name: 'Create', description: 'Create new screens.' },
|
||||
{ key: 'edit', name: 'Update', description: 'Edit screens and send screen commands.' },
|
||||
{ key: 'delete', name: 'Delete', description: 'Delete screens.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'playlists',
|
||||
order: 40,
|
||||
name: 'Playlists',
|
||||
sectionName: 'Content',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'View playlists and playlist contents.' },
|
||||
{ key: 'create', name: 'Create', description: 'Create new playlists.' },
|
||||
{ key: 'edit', name: 'Update', description: 'Edit playlists and playlist slides.' },
|
||||
{ key: 'delete', name: 'Delete', description: 'Delete playlists.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'slides',
|
||||
order: 50,
|
||||
name: 'Slides',
|
||||
sectionName: 'Content',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'View slides.' },
|
||||
{ key: 'create', name: 'Create', description: 'Create new slides.' },
|
||||
{ key: 'edit', name: 'Update', description: 'Edit slide content.' },
|
||||
{ key: 'delete', name: 'Delete', description: 'Delete slides.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'templates',
|
||||
order: 60,
|
||||
name: 'Slide templates',
|
||||
sectionName: 'Content',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'View slide templates.' },
|
||||
{ key: 'create', name: 'Create', description: 'Create new slide templates.' },
|
||||
{ key: 'edit', name: 'Update', description: 'Edit slide templates.' },
|
||||
{ key: 'delete', name: 'Delete', description: 'Delete slide templates.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'canvas-sizes',
|
||||
order: 70,
|
||||
name: 'Canvas sizes',
|
||||
sectionName: 'Content',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'View canvas sizes.' },
|
||||
{ key: 'create', name: 'Create', description: 'Create new canvas sizes.' },
|
||||
{ key: 'edit', name: 'Update', description: 'Edit canvas sizes.' },
|
||||
{ key: 'delete', name: 'Delete', description: 'Delete canvas sizes.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'users',
|
||||
order: 80,
|
||||
name: 'Users',
|
||||
sectionName: 'Settings',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'View users and role assignments.' },
|
||||
{ key: 'create', name: 'Create', description: 'Create new users.' },
|
||||
{ key: 'edit', name: 'Update', description: 'Edit users, passwords, and role assignments.' },
|
||||
{ key: 'delete', name: 'Delete', description: 'Delete users.' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'rbac',
|
||||
order: 90,
|
||||
name: 'Roles and permissions',
|
||||
sectionName: 'Settings',
|
||||
actions: [
|
||||
{ key: 'read', name: 'Read', description: 'View roles and permissions.' },
|
||||
{ key: 'create', name: 'Create', description: 'Create new roles.' },
|
||||
{ key: 'edit', name: 'Update', description: 'Edit role details and permissions.' },
|
||||
{ key: 'delete', name: 'Delete', description: 'Delete roles.' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const PERMISSIONS = PERMISSION_SECTIONS.flatMap(function (section) {
|
||||
return section.actions.map(function (action) {
|
||||
return {
|
||||
key: `${section.key}.${action.key}`,
|
||||
name: section.name,
|
||||
sectionOrder: section.order,
|
||||
actionName: action.name,
|
||||
sectionName: section.sectionName,
|
||||
sectionKey: section.key,
|
||||
actionKey: action.key,
|
||||
description: action.description
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const DEFAULT_ROLE = {
|
||||
key: 'administrators',
|
||||
name: 'Administrators',
|
||||
description: 'Full access to the admin interface.'
|
||||
};
|
||||
|
||||
function normalizePermissionKey(permissionKey) {
|
||||
return String(permissionKey || '').trim();
|
||||
}
|
||||
|
||||
function normalizePermissionKeys(permissionKeys) {
|
||||
const normalized = [];
|
||||
|
||||
(Array.isArray(permissionKeys) ? permissionKeys : []).forEach(function (permissionKey) {
|
||||
const normalizedPermissionKey = normalizePermissionKey(permissionKey);
|
||||
if (!normalizedPermissionKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
normalized.push(normalizedPermissionKey);
|
||||
|
||||
const parts = normalizedPermissionKey.split('.');
|
||||
if (parts.length !== 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sectionKey = parts[0];
|
||||
const actionKey = parts[1];
|
||||
if (actionKey === 'create' || actionKey === 'edit' || actionKey === 'delete') {
|
||||
normalized.push(`${sectionKey}.read`);
|
||||
}
|
||||
if (actionKey === 'manage') {
|
||||
normalized.push(`${sectionKey}.read`);
|
||||
normalized.push(`${sectionKey}.create`);
|
||||
normalized.push(`${sectionKey}.edit`);
|
||||
normalized.push(`${sectionKey}.delete`);
|
||||
}
|
||||
if (actionKey === 'view') {
|
||||
normalized.push(`${sectionKey}.read`);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(new Set(normalized));
|
||||
}
|
||||
|
||||
function hasPermission(currentUser, permissionKey) {
|
||||
const normalizedPermissionKey = normalizePermissionKey(permissionKey);
|
||||
if (!normalizedPermissionKey || !currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const permissionKeys = Array.isArray(currentUser.permissionKeys)
|
||||
? currentUser.permissionKeys
|
||||
: Array.isArray(currentUser.permissions)
|
||||
? currentUser.permissions
|
||||
: [];
|
||||
|
||||
return permissionKeys.map(normalizePermissionKey).includes(normalizedPermissionKey);
|
||||
}
|
||||
|
||||
function hasAnyPermission(currentUser, permissionKeys) {
|
||||
const normalizedPermissionKeys = normalizePermissionKeys(permissionKeys);
|
||||
if (!normalizedPermissionKeys.length || !currentUser) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return normalizedPermissionKeys.some(function (permissionKey) {
|
||||
return hasPermission(currentUser, permissionKey);
|
||||
});
|
||||
}
|
||||
|
||||
function requirePermission(permissionKey) {
|
||||
const normalizedPermissionKey = normalizePermissionKey(permissionKey);
|
||||
if (!normalizedPermissionKey) {
|
||||
throw new Error('requirePermission requires a permission key.');
|
||||
}
|
||||
|
||||
return function (req, res, next) {
|
||||
if (!req.currentUser) {
|
||||
return res.redirect('/login?message=' + encodeURIComponent('Please sign in to continue.'));
|
||||
}
|
||||
|
||||
if (hasPermission(req.currentUser, normalizedPermissionKey)) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const error = new Error('You do not have permission to access this area.');
|
||||
error.statusCode = 403;
|
||||
error.expose = true;
|
||||
next(error);
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PERMISSIONS,
|
||||
PERMISSION_SECTIONS,
|
||||
DEFAULT_ROLE,
|
||||
hasPermission,
|
||||
hasAnyPermission,
|
||||
requirePermission,
|
||||
normalizePermissionKeys
|
||||
};
|
||||
Reference in New Issue
Block a user