Files
pulse-signage/src/web/lib/auth/rbac-data.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

313 lines
10 KiB
JavaScript

// RBAC data access helpers for roles, permissions, and user-role links.
const { PERMISSIONS, normalizePermissionKeys } = require('#src/rbac');
const { fetchPagedRows } = require('#src/data/utils');
function parseCsvIds(value) {
return String(value || '')
.split(',')
.map(function (item) {
return Number(item);
})
.filter(function (item) {
return Number.isInteger(item) && item > 0;
});
}
async function fetchPermissions(pool) {
const [rows] = await pool.query('SELECT id, permission_key, name, section_name, description FROM a_permissions ORDER BY section_name ASC, name ASC');
return rows || [];
}
async function fetchRoles(pool) {
const [rows] = await pool.query(
`SELECT r.id, r.role_key, r.name, r.description, r.created_at, r.modified_at,
(SELECT COUNT(*) FROM a_user_roles ur WHERE ur.role_id = r.id) AS user_count,
(SELECT COUNT(*) FROM a_role_permissions rp WHERE rp.role_id = r.id) AS permission_count
FROM a_roles r
ORDER BY r.name ASC`
);
return rows || [];
}
async function fetchRolesPage(pool, page, pageSize, searchTerm, sortKey, sortDirection) {
const paged = await fetchPagedRows(pool, {
selectSql: `SELECT r.id, r.role_key, r.name, r.description, r.created_at, r.modified_at,
(SELECT COUNT(*) FROM a_user_roles ur WHERE ur.role_id = r.id) AS user_count,
(SELECT COUNT(*) FROM a_role_permissions rp WHERE rp.role_id = r.id) AS permission_count
FROM a_roles r
ORDER BY r.name ASC`,
countSql: 'SELECT COUNT(*) AS count FROM a_roles',
searchColumns: ['r.role_key', 'r.name', 'r.description'],
searchTerm: searchTerm,
sortColumns: {
role: 'r.name',
description: 'r.description',
users: 'user_count',
permissions: 'permission_count',
created: 'r.created_at',
modified: 'r.modified_at'
},
sortKey: sortKey,
sortDirection: sortDirection,
page: page,
pageSize: pageSize
});
return Object.assign({ roles: paged.rows }, paged);
}
async function fetchRoleById(pool, roleId) {
const [rows] = await pool.query(
`SELECT r.id, r.role_key, r.name, r.description, r.created_at, r.modified_at,
(SELECT COUNT(*) FROM a_user_roles ur WHERE ur.role_id = r.id) AS user_count,
(SELECT COUNT(*) FROM a_role_permissions rp WHERE rp.role_id = r.id) AS permission_count
FROM a_roles r
WHERE r.id = ?
LIMIT 1`,
[roleId]
);
return rows[0] || null;
}
async function fetchRolePermissionKeys(pool, roleId) {
const [rows] = await pool.query(
`SELECT p.permission_key
FROM a_role_permissions rp
JOIN a_permissions p ON p.id = rp.permission_id
WHERE rp.role_id = ?
ORDER BY p.section_name ASC, p.name ASC`,
[roleId]
);
return (rows || []).map(function (row) {
return String(row.permission_key || '').trim();
}).filter(Boolean);
}
async function fetchRoleUserIds(pool, roleId) {
const [rows] = await pool.query(
`SELECT ur.user_id
FROM a_user_roles ur
WHERE ur.role_id = ?
ORDER BY ur.user_id ASC`,
[roleId]
);
return (rows || []).map(function (row) {
return Number(row.user_id);
}).filter(function (userId) {
return Number.isInteger(userId) && userId > 0;
});
}
async function fetchRolesForUser(pool, userId) {
const [rows] = await pool.query(
`SELECT r.id, r.role_key, r.name, r.description
FROM a_user_roles ur
JOIN a_roles r ON r.id = ur.role_id
WHERE ur.user_id = ?
ORDER BY r.name ASC`,
[userId]
);
return rows || [];
}
async function fetchUsersWithRoles(pool) {
const [rows] = await pool.query(
`SELECT u.id, u.name, u.username, u.account_locked, u.created_at, u.modified_at,
COALESCE(role_data.role_names, '') AS role_names,
COALESCE(role_data.role_ids_csv, '') AS role_ids_csv
FROM a_users u
LEFT JOIN (
SELECT ur.user_id,
GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') AS role_names,
GROUP_CONCAT(DISTINCT r.id ORDER BY r.name SEPARATOR ',') AS role_ids_csv
FROM a_user_roles ur
JOIN a_roles r ON r.id = ur.role_id
GROUP BY ur.user_id
) role_data ON role_data.user_id = u.id
ORDER BY u.id ASC`
);
return (rows || []).map(function (row) {
return Object.assign({}, row, {
roleIds: parseCsvIds(row.role_ids_csv),
roleNames: String(row.role_names || '').trim()
});
});
}
async function fetchUsersWithRolesPage(pool, page, pageSize, searchTerm, sortKey, sortDirection, options) {
const excludedUserId = Number(options && options.excludeUserId);
const hasExcludedUserId = Number.isInteger(excludedUserId) && excludedUserId > 0;
const whereSql = hasExcludedUserId ? 'WHERE u.id <> ?' : '';
const queryArgs = hasExcludedUserId ? [excludedUserId] : [];
const paged = await fetchPagedRows(pool, {
selectSql: `SELECT u.id, u.name, u.username, u.account_locked, u.created_at, u.modified_at,
COALESCE(role_data.role_names, '') AS role_names,
COALESCE(role_data.role_ids_csv, '') AS role_ids_csv
FROM a_users u
LEFT JOIN (
SELECT ur.user_id,
GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') AS role_names,
GROUP_CONCAT(DISTINCT r.id ORDER BY r.name SEPARATOR ',') AS role_ids_csv
FROM a_user_roles ur
JOIN a_roles r ON r.id = ur.role_id
GROUP BY ur.user_id
) role_data ON role_data.user_id = u.id
${whereSql}
ORDER BY u.id ASC`,
countSql: `SELECT COUNT(*) AS count FROM a_users u ${whereSql}`,
params: queryArgs,
searchColumns: ['u.name', 'u.username', 'role_data.role_names'],
searchTerm: searchTerm,
sortColumns: {
user: 'u.username',
name: 'u.name',
roles: 'role_names',
created: 'u.created_at',
modified: 'u.modified_at'
},
sortKey: sortKey,
sortDirection: sortDirection,
page: page,
pageSize: pageSize
});
return {
users: (paged.rows || []).map(function (row) {
return Object.assign({}, row, {
roleIds: parseCsvIds(row.role_ids_csv),
roleNames: String(row.role_names || '').trim()
});
}),
totalItems: paged.totalItems,
totalPages: paged.totalPages,
currentPage: paged.currentPage,
pageSize: paged.pageSize
};
}
async function fetchUserWithRoles(pool, userId) {
const [rows] = await pool.query(
`SELECT u.id, u.name, u.username, u.account_locked, u.created_at, u.modified_at,
COALESCE(role_data.role_names, '') AS role_names,
COALESCE(role_data.role_ids_csv, '') AS role_ids_csv
FROM a_users u
LEFT JOIN (
SELECT ur.user_id,
GROUP_CONCAT(DISTINCT r.name ORDER BY r.name SEPARATOR ', ') AS role_names,
GROUP_CONCAT(DISTINCT r.id ORDER BY r.name SEPARATOR ',') AS role_ids_csv
FROM a_user_roles ur
JOIN a_roles r ON r.id = ur.role_id
GROUP BY ur.user_id
) role_data ON role_data.user_id = u.id
WHERE u.id = ?
LIMIT 1`,
[userId]
);
if (!rows.length) {
return null;
}
return Object.assign({}, rows[0], {
roleIds: parseCsvIds(rows[0].role_ids_csv),
roleNames: String(rows[0].role_names || '').trim()
});
}
async function fetchActiveUserSessions(pool, userId) {
const [rows] = await pool.query(
`SELECT id, ip_address, user_agent, created_at, last_used_at, expires_at
FROM a_sessions
WHERE user_id = ? AND expires_at > NOW()
ORDER BY last_used_at DESC`,
[userId]
);
return rows || [];
}
async function syncUserRoles(pool, userId, roleIds) {
const uniqueRoleIds = Array.from(new Set((Array.isArray(roleIds) ? roleIds : []).map(function (roleId) {
return Number(roleId);
}).filter(function (roleId) {
return Number.isInteger(roleId) && roleId > 0;
})));
await pool.query('DELETE FROM a_user_roles WHERE user_id = ?', [userId]);
for (const roleId of uniqueRoleIds) {
await pool.query(
`INSERT INTO a_user_roles (user_id, role_id, created_by, modified_by)
SELECT ?, ?, ?, ?
WHERE NOT EXISTS (
SELECT 1 FROM a_user_roles WHERE user_id = ? AND role_id = ?
)`,
[userId, roleId, null, null, userId, roleId]
);
}
}
async function syncRoleUsers(pool, roleId, userIds) {
const uniqueUserIds = Array.from(new Set((Array.isArray(userIds) ? userIds : []).map(function (userId) {
return Number(userId);
}).filter(function (userId) {
return Number.isInteger(userId) && userId > 0;
})));
await pool.query('DELETE FROM a_user_roles WHERE role_id = ?', [roleId]);
for (const userId of uniqueUserIds) {
await pool.query(
`INSERT INTO a_user_roles (user_id, role_id, created_by, modified_by)
SELECT ?, ?, ?, ?
WHERE NOT EXISTS (
SELECT 1 FROM a_user_roles WHERE user_id = ? AND role_id = ?
)`,
[userId, roleId, null, null, userId, roleId]
);
}
}
async function syncRolePermissions(pool, roleId, permissionKeys) {
const uniquePermissionKeys = normalizePermissionKeys(permissionKeys);
if (!uniquePermissionKeys.length) {
await pool.query('DELETE FROM a_role_permissions WHERE role_id = ?', [roleId]);
return;
}
const [permissionRows] = await pool.query('SELECT id, permission_key FROM a_permissions WHERE permission_key IN (?)', [uniquePermissionKeys]);
if (permissionRows.length !== uniquePermissionKeys.length) {
throw new Error('One or more selected permissions are invalid.');
}
await pool.query('DELETE FROM a_role_permissions WHERE role_id = ?', [roleId]);
for (const permissionRow of permissionRows) {
const permissionId = Number(permissionRow.id);
await pool.query(
`INSERT INTO a_role_permissions (role_id, permission_id, created_by, modified_by)
SELECT ?, ?, ?, ?
WHERE NOT EXISTS (
SELECT 1 FROM a_role_permissions WHERE role_id = ? AND permission_id = ?
)`,
[roleId, permissionId, null, null, roleId, permissionId]
);
}
}
module.exports = {
PERMISSIONS,
fetchPermissions,
fetchRoles,
fetchRolesPage,
fetchRoleById,
fetchRolePermissionKeys,
fetchRoleUserIds,
fetchRolesForUser,
fetchUsersWithRoles,
fetchUsersWithRolesPage,
fetchUserWithRoles,
fetchActiveUserSessions,
syncUserRoles,
syncRoleUsers,
syncRolePermissions
};