Files
pulse-signage/src/rbac.js
T
lzstealth a5bf8e6f7f
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile, web, pulse-signage-web) (push) Successful in 1m16s
Publish Docker Image / build-and-push-existing-registry (./build/Dockerfile.player, player, pulse-signage-player) (push) Successful in 33s
Release 2.8.1
2026-08-16 22:08:32 +01:00

350 lines
12 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: 'system-settings',
order: 70,
name: 'System settings',
permissions: [
{ key: 'read', name: 'Read', description: 'View global application settings.' },
{ key: 'update', name: 'Update', description: 'Update global application settings.' }
]
},
{
key: 'audit-log',
order: 60,
name: 'Audit log',
permissions: [
{ key: 'read', name: 'Read', description: 'View security and session audit events.' },
{ key: 'allow', name: 'Allow', description: 'Download filtered audit events.' }
]
},
{
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, sectionIndex) {
return (Array.isArray(section.permissions) ? section.permissions : []).flatMap(function (resource, resourceIndex) {
return (Array.isArray(resource.permissions) ? resource.permissions : []).map(function (action, actionIndex) {
return {
key: `${resource.key}.${action.key}`,
name: resource.name,
sectionOrder: section.order,
sectionIndex: sectionIndex,
actionName: action.name,
permissionOrder: actionIndex + 1,
permissionIndex: actionIndex,
sectionName: section.sectionName,
resourceKey: resource.key,
resourceOrder: resource.order,
resourceIndex: resourceIndex,
resourceName: resource.name,
actionKey: action.key,
description: action.description
};
});
});
});
const DEFAULT_ROLE = {
key: 'super-admin',
name: 'Super Admin',
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
};