329 lines
11 KiB
JavaScript
329 lines
11 KiB
JavaScript
// Permission section definitions and normalization helpers for RBAC.
|
|
|
|
const PERMISSION_SECTIONS = [
|
|
{
|
|
sectionName: 'Main navigation',
|
|
order: 10,
|
|
permissions: [
|
|
{
|
|
key: 'dashboard',
|
|
order: 10,
|
|
name: 'Dashboard',
|
|
permissions: [
|
|
{ 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',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View connected player clients and live status.' },
|
|
{ key: 'allow', name: 'Allow', description: 'Use the connected client command buttons.' }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
sectionName: 'Content',
|
|
order: 20,
|
|
permissions: [
|
|
{
|
|
key: 'screens',
|
|
order: 10,
|
|
name: 'Screens',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View the screen list and open screen details.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new screens.' },
|
|
{ key: 'update', name: 'Update', description: 'Update screens.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete screens.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'playlists',
|
|
order: 20,
|
|
name: 'Playlists',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View playlists and playlist contents.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new playlists.' },
|
|
{ key: 'update', name: 'Update', description: 'Update playlists and playlist slides.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete playlists.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'slides',
|
|
order: 30,
|
|
name: 'Slides',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View slides.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new slides.' },
|
|
{ key: 'update', name: 'Update', description: 'Update slide content.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete slides.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'templates',
|
|
order: 40,
|
|
name: 'Slide templates',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View slide templates.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new slide templates.' },
|
|
{ key: 'update', name: 'Update', description: 'Update slide templates.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete slide templates.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'canvas-sizes',
|
|
order: 50,
|
|
name: 'Canvas sizes',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View canvas sizes.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new canvas sizes.' },
|
|
{ key: 'update', name: 'Update', description: 'Update canvas sizes.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete canvas sizes.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'announcements',
|
|
order: 60,
|
|
name: 'Announcements',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View announcements.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new announcements.' },
|
|
{ key: 'update', name: 'Update', description: 'Update announcements.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete announcements.' }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
sectionName: 'Data Sources',
|
|
order: 30,
|
|
permissions: [
|
|
{
|
|
key: 'rss-feeds',
|
|
order: 10,
|
|
name: 'RSS feeds',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View configured RSS feeds.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new RSS feeds.' },
|
|
{ key: 'update', name: 'Update', description: 'Update RSS feeds.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete RSS feeds.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'api-sources',
|
|
order: 20,
|
|
name: 'API sources',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View configured API sources.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new API sources.' },
|
|
{ key: 'update', name: 'Update', description: 'Update API sources.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete API sources.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'timetables',
|
|
order: 30,
|
|
name: 'Timetables',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View configured timetable groups.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new timetable groups.' },
|
|
{ key: 'update', name: 'Update', description: 'Update timetable groups and entries.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete timetable groups.' }
|
|
]
|
|
}
|
|
]
|
|
},
|
|
{
|
|
sectionName: 'Settings',
|
|
order: 40,
|
|
permissions: [
|
|
{
|
|
key: 'users',
|
|
order: 10,
|
|
name: 'Users',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View users and role assignments.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new users.' },
|
|
{ key: 'update', name: 'Update', description: 'Update users, passwords, and role assignments.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete users.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'rbac',
|
|
order: 20,
|
|
name: 'Roles and permissions',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View roles and permissions.' },
|
|
{ key: 'create', name: 'Create', description: 'Create new roles.' },
|
|
{ key: 'update', name: 'Update', description: 'Update role details and permissions.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Delete roles.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'fonts',
|
|
order: 30,
|
|
name: 'Fonts',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View managed fonts.' },
|
|
{ key: 'create', name: 'Create', description: 'Upload managed fonts.' },
|
|
{ key: 'delete', name: 'Delete', description: 'Remove managed fonts.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'background-tasks',
|
|
order: 40,
|
|
name: 'Task Queue',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View queued background tasks.' },
|
|
{ key: 'allow', name: 'Allow', description: 'Manage queued background tasks and clear finished items.' }
|
|
]
|
|
},
|
|
{
|
|
key: 'scheduled-tasks',
|
|
order: 50,
|
|
name: 'Scheduled tasks',
|
|
permissions: [
|
|
{ key: 'read', name: 'Read', description: 'View scheduled refresh tasks.' },
|
|
{ key: 'allow', name: 'Allow', description: 'Run scheduled refreshes manually.' }
|
|
]
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
const PERMISSIONS = PERMISSION_SECTIONS.flatMap(function (section) {
|
|
return (Array.isArray(section.permissions) ? section.permissions : []).flatMap(function (resource) {
|
|
return (Array.isArray(resource.permissions) ? resource.permissions : []).map(function (action, index) {
|
|
return {
|
|
key: `${resource.key}.${action.key}`,
|
|
name: resource.name,
|
|
sectionOrder: section.order,
|
|
actionName: action.name,
|
|
permissionOrder: index + 1,
|
|
sectionName: section.sectionName,
|
|
resourceKey: resource.key,
|
|
resourceOrder: resource.order,
|
|
resourceName: resource.name,
|
|
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();
|
|
}
|
|
|
|
const PERMISSION_DENIED_MESSAGE = 'You do not have permissions to do this.';
|
|
|
|
function normalizePermissionKeys(permissionKeys) {
|
|
const normalized = [];
|
|
|
|
(Array.isArray(permissionKeys) ? permissionKeys : []).forEach(function (permissionKey) {
|
|
const normalizedPermissionKey = normalizePermissionKey(permissionKey);
|
|
if (!normalizedPermissionKey) {
|
|
return;
|
|
}
|
|
|
|
const parts = normalizedPermissionKey.split('.');
|
|
if (parts.length !== 2) {
|
|
normalized.push(normalizedPermissionKey);
|
|
return;
|
|
}
|
|
|
|
const sectionKey = parts[0];
|
|
const actionKey = parts[1];
|
|
normalized.push(`${sectionKey}.${actionKey}`);
|
|
|
|
if (actionKey === 'create' || actionKey === 'update' || actionKey === 'delete') {
|
|
normalized.push(`${sectionKey}.read`);
|
|
}
|
|
if (actionKey === 'manage') {
|
|
normalized.push(`${sectionKey}.read`);
|
|
normalized.push(`${sectionKey}.create`);
|
|
normalized.push(`${sectionKey}.update`);
|
|
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 normalizePermissionKeys(permissionKeys).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, options) {
|
|
const normalizedPermissionKey = normalizePermissionKey(permissionKey);
|
|
if (!normalizedPermissionKey) {
|
|
throw new Error('requirePermission requires a permission key.');
|
|
}
|
|
|
|
const setAuthMessageCookie = options && options.setAuthMessageCookie;
|
|
|
|
return function (req, res, next) {
|
|
if (!req.currentUser) {
|
|
if (typeof setAuthMessageCookie === 'function') {
|
|
setAuthMessageCookie(res, 'Please sign in to continue.');
|
|
return res.redirect('/login');
|
|
}
|
|
|
|
return res.redirect('/login');
|
|
}
|
|
|
|
if (hasPermission(req.currentUser, normalizedPermissionKey)) {
|
|
return next();
|
|
}
|
|
|
|
const error = new Error(PERMISSION_DENIED_MESSAGE);
|
|
error.statusCode = 403;
|
|
error.expose = true;
|
|
next(error);
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
PERMISSIONS,
|
|
PERMISSION_SECTIONS,
|
|
DEFAULT_ROLE,
|
|
hasPermission,
|
|
hasAnyPermission,
|
|
requirePermission,
|
|
normalizePermissionKeys,
|
|
PERMISSION_DENIED_MESSAGE
|
|
}; |