Improve ISAC libraries, roles, and public profiles
This commit is contained in:
@@ -8,6 +8,17 @@ import type { Actions, PageServerLoad } from './$types';
|
||||
import { recordIsacAudit } from '$lib/server/audit';
|
||||
|
||||
const filters = ['isac', 'data', 'users', 'audit', 'system'];
|
||||
const seededRoleIds = new Set([
|
||||
'role-data-editor',
|
||||
'role-user-operator',
|
||||
'role-access-manager',
|
||||
'role-admin',
|
||||
'role-moderator',
|
||||
'role-data-viewer',
|
||||
'role-data-contributor',
|
||||
'role-data-approver',
|
||||
'role-auditor'
|
||||
]);
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
const operator = await requirePermission(await getSessionUser(cookies), 'users.roles');
|
||||
@@ -68,7 +79,11 @@ export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
roles: roles.map((role) => ({
|
||||
...role,
|
||||
permissionIds: links.filter((link) => link.role_id === role.id).map((link) => String(link.permission_id)),
|
||||
protected: role.id === 'role-admin'
|
||||
systemRole: seededRoleIds.has(String(role.id)),
|
||||
protected: role.id === 'role-admin',
|
||||
canEdit: role.id !== 'role-admin' || canManageSystemAdmin,
|
||||
canDelete: role.id !== 'role-admin'
|
||||
&& (canManageSystemAdmin || !seededRoleIds.has(String(role.id)))
|
||||
})),
|
||||
permissions,
|
||||
summary,
|
||||
@@ -95,7 +110,9 @@ export const actions: Actions = {
|
||||
const database = await getDatabase();
|
||||
const existing = id ? await (await database.prepare('SELECT id,name,description FROM roles WHERE id=?')).get(id) : null;
|
||||
if (id && !existing) return fail(404, { roleError: 'Role not found.' });
|
||||
if (id === 'role-admin') return fail(403, { roleError: 'The built-in Admin role is protected and cannot be modified.' });
|
||||
if (id === 'role-admin' && !canManageSystemAdmin) {
|
||||
return fail(403, { roleError: 'Only a full administrator can modify the core Admin role.' });
|
||||
}
|
||||
if (await (await database.prepare('SELECT id FROM roles WHERE name=? COLLATE NOCASE AND id<>?')).get(name, id || 'new')) {
|
||||
return fail(409, { roleError: 'A role with that name already exists.' });
|
||||
}
|
||||
@@ -119,5 +136,79 @@ export const actions: Actions = {
|
||||
after: { name, description: description || null, permissions: validPermissions.map((row) => row.name) }
|
||||
});
|
||||
return { roleSaved: true, roleMode: id ? 'edit' : 'add', resultToken: randomUUID() };
|
||||
},
|
||||
delete: async ({ cookies, request }) => {
|
||||
const operator = await requirePermission(await getSessionUser(cookies), 'users.roles');
|
||||
const operatorPermissions = await getPermissions(operator);
|
||||
const canManageSystemAdmin = operatorPermissions.includes('*');
|
||||
const form = await request.formData();
|
||||
const id = String(form.get('id') ?? '').trim();
|
||||
const confirmation = String(form.get('confirmation') ?? '').normalize('NFKC').trim();
|
||||
if (!id) return fail(400, { roleDeleteError: 'Choose a role to delete.' });
|
||||
if (id === 'role-admin') {
|
||||
return fail(403, { roleDeleteError: 'The core Admin role cannot be deleted.' });
|
||||
}
|
||||
if (seededRoleIds.has(id) && !canManageSystemAdmin) {
|
||||
return fail(403, { roleDeleteError: 'Only a full administrator can delete a seeded system role.' });
|
||||
}
|
||||
const database = await getDatabase();
|
||||
const role = await (await database.prepare(
|
||||
'SELECT id,name,description FROM roles WHERE id=?'
|
||||
)).get(id);
|
||||
if (!role) return fail(404, { roleDeleteError: 'Role not found.' });
|
||||
if (confirmation !== String(role.name)) {
|
||||
return fail(400, { roleDeleteError: 'Enter the exact role name to confirm deletion.' });
|
||||
}
|
||||
const [permissions, assignedUsers] = await Promise.all([
|
||||
(await database.prepare(`SELECT permissions.name FROM role_permissions
|
||||
JOIN permissions ON permissions.id=role_permissions.permission_id
|
||||
WHERE role_permissions.role_id=? ORDER BY permissions.name COLLATE NOCASE`)).all(id),
|
||||
(await database.prepare(`SELECT users.id,
|
||||
COALESCE(user_profiles.display_name,users.username) AS display_name
|
||||
FROM user_roles JOIN users ON users.id=user_roles.user_id
|
||||
LEFT JOIN user_profiles ON user_profiles.user_id=users.id
|
||||
WHERE user_roles.role_id=?
|
||||
ORDER BY display_name COLLATE NOCASE,users.id`)).all(id)
|
||||
]);
|
||||
await database.exec('BEGIN IMMEDIATE');
|
||||
try {
|
||||
if (seededRoleIds.has(id)) {
|
||||
await (await database.prepare(`INSERT INTO deleted_roles(role_id,deleted_at,deleted_by)
|
||||
VALUES(?,?,?)
|
||||
ON CONFLICT(role_id) DO UPDATE SET
|
||||
deleted_at=excluded.deleted_at,
|
||||
deleted_by=excluded.deleted_by`))
|
||||
.run(id, new Date().toISOString(), operator.id);
|
||||
}
|
||||
await (await database.prepare('DELETE FROM user_roles WHERE role_id=?')).run(id);
|
||||
await (await database.prepare('DELETE FROM role_permissions WHERE role_id=?')).run(id);
|
||||
const deleted = await (await database.prepare('DELETE FROM roles WHERE id=?')).run(id);
|
||||
if (!deleted.changes) throw new Error('The role changed before it could be deleted.');
|
||||
await recordIsacAudit({
|
||||
actorId: operator.id,
|
||||
category: 'role_management',
|
||||
action: 'role.deleted',
|
||||
entityId: id,
|
||||
before: {
|
||||
name: String(role.name),
|
||||
description: role.description ? String(role.description) : null,
|
||||
permissions: permissions.map((row) => String(row.name)),
|
||||
assignedUsers: assignedUsers.map((row) => ({
|
||||
id: String(row.id),
|
||||
displayName: String(row.display_name)
|
||||
}))
|
||||
},
|
||||
after: {
|
||||
deleted: true,
|
||||
systemRole: seededRoleIds.has(id),
|
||||
removedAssignmentCount: assignedUsers.length
|
||||
}
|
||||
});
|
||||
await database.exec('COMMIT');
|
||||
} catch (error) {
|
||||
await database.exec('ROLLBACK');
|
||||
throw error;
|
||||
}
|
||||
return { roleDeleted: true, resultToken: randomUUID() };
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user