Updated A LOT
This commit is contained in:
102
server/services/roles.js
Normal file
102
server/services/roles.js
Normal file
@@ -0,0 +1,102 @@
|
||||
const { all, audit, json, now, one, parseJson, run } = require('../db');
|
||||
const { CAPABILITY_KEYS, WILDCARD, capabilitySetIncludes, sanitizeCapabilities } = require('./accessControl');
|
||||
|
||||
function slugify(value) {
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, '_')
|
||||
.replace(/^_+|_+$/g, '')
|
||||
.slice(0, 40);
|
||||
}
|
||||
|
||||
function fromRow(row) {
|
||||
if (!row) return null;
|
||||
return {
|
||||
key: row.key,
|
||||
name: row.name,
|
||||
description: row.description || '',
|
||||
capabilities: parseJson(row.capabilities_json, []),
|
||||
is_system: Boolean(row.is_system),
|
||||
locked: Boolean(row.locked),
|
||||
user_count: one('SELECT COUNT(*) AS c FROM users WHERE role = ?', [row.key]).c
|
||||
};
|
||||
}
|
||||
|
||||
function listRoles() {
|
||||
return all('SELECT * FROM roles ORDER BY sort_order, name').map(fromRow);
|
||||
}
|
||||
|
||||
function getRole(key) {
|
||||
return fromRow(one('SELECT * FROM roles WHERE key = ?', [key]));
|
||||
}
|
||||
|
||||
function roleExists(key) {
|
||||
return Boolean(one('SELECT key FROM roles WHERE key = ?', [key]));
|
||||
}
|
||||
|
||||
// Capabilities granted to a role key (empty when the role is unknown).
|
||||
function capabilitiesForRole(key) {
|
||||
const row = one('SELECT capabilities_json FROM roles WHERE key = ?', [key]);
|
||||
return row ? parseJson(row.capabilities_json, []) : [];
|
||||
}
|
||||
|
||||
function roleHasCapability(key, capability) {
|
||||
return capabilitySetIncludes(capabilitiesForRole(key), capability);
|
||||
}
|
||||
|
||||
function createRole(body, userId) {
|
||||
const name = String(body.name || '').trim();
|
||||
if (!name) throw Object.assign(new Error('A role name is required'), { status: 400 });
|
||||
const key = slugify(body.key || name);
|
||||
if (!key) throw Object.assign(new Error('A valid role key is required'), { status: 400 });
|
||||
if (roleExists(key)) throw Object.assign(new Error('A role with that key already exists'), { status: 400 });
|
||||
const capabilities = sanitizeCapabilities(body.capabilities);
|
||||
const ts = now();
|
||||
const sort = (one('SELECT MAX(sort_order) AS m FROM roles').m || 0) + 1;
|
||||
run(
|
||||
'INSERT INTO roles (key, name, description, capabilities_json, is_system, locked, sort_order, created_at, updated_at) VALUES (?, ?, ?, ?, 0, 0, ?, ?, ?)',
|
||||
[key, name, body.description || null, json(capabilities), sort, ts, ts]
|
||||
);
|
||||
audit(userId, 'create', 'role', key, null, { name, capabilities });
|
||||
return getRole(key);
|
||||
}
|
||||
|
||||
function updateRole(key, body, userId) {
|
||||
const before = one('SELECT * FROM roles WHERE key = ?', [key]);
|
||||
if (!before) return null;
|
||||
const name = body.name !== undefined ? String(body.name).trim() || before.name : before.name;
|
||||
// Locked roles (admin) always keep the wildcard capability set.
|
||||
const capabilities = before.locked
|
||||
? parseJson(before.capabilities_json, [WILDCARD])
|
||||
: (body.capabilities !== undefined ? sanitizeCapabilities(body.capabilities) : parseJson(before.capabilities_json, []));
|
||||
run(
|
||||
'UPDATE roles SET name = ?, description = ?, capabilities_json = ?, updated_at = ? WHERE key = ?',
|
||||
[name, body.description ?? before.description, json(capabilities), now(), key]
|
||||
);
|
||||
audit(userId, 'update', 'role', key, fromRow(before), { name, capabilities });
|
||||
return getRole(key);
|
||||
}
|
||||
|
||||
function deleteRole(key, userId) {
|
||||
const before = one('SELECT * FROM roles WHERE key = ?', [key]);
|
||||
if (!before) return { ok: false, status: 404, error: 'Role not found' };
|
||||
if (before.is_system) return { ok: false, status: 400, error: 'Built-in roles cannot be deleted' };
|
||||
const inUse = one('SELECT COUNT(*) AS c FROM users WHERE role = ?', [key]).c;
|
||||
if (inUse) return { ok: false, status: 400, error: `Reassign the ${inUse} user(s) with this role before deleting it` };
|
||||
run('DELETE FROM roles WHERE key = ?', [key]);
|
||||
audit(userId, 'delete', 'role', key, fromRow(before), null);
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
CAPABILITY_KEYS,
|
||||
capabilitiesForRole,
|
||||
createRole,
|
||||
deleteRole,
|
||||
getRole,
|
||||
listRoles,
|
||||
roleExists,
|
||||
roleHasCapability,
|
||||
updateRole
|
||||
};
|
||||
Reference in New Issue
Block a user