Rename admin routes to ISAC
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
import { getPermissions, requirePermission } from '$lib/server/auth/authorization';
|
||||
import { getSessionUser } from '$lib/server/auth/session';
|
||||
import { getDatabase } from '$lib/server/database';
|
||||
import { getPagination } from '$lib/server/pagination';
|
||||
import { fail } from '@sveltejs/kit';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
import { recordIsacAudit } from '$lib/server/audit';
|
||||
|
||||
const filters = ['isac', 'data', 'users', 'audit'];
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, url }) => {
|
||||
const operator = await requirePermission(await getSessionUser(cookies), 'users.roles');
|
||||
const operatorPermissions = await getPermissions(operator);
|
||||
const canManageSystemAdmin = operatorPermissions.includes('*');
|
||||
const database = await getDatabase();
|
||||
const filter = filters.includes(url.searchParams.get('filter') ?? '') ? String(url.searchParams.get('filter')) : '';
|
||||
const permissionPrefix = filter === 'isac' ? 'isac.%' : filter === 'data' ? 'data.%' : filter === 'users' ? 'users.%' : filter === 'audit' ? 'audit.%' : '';
|
||||
const where = permissionPrefix ? `WHERE EXISTS (
|
||||
SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id
|
||||
WHERE rp.role_id=roles.id AND p.name LIKE ?
|
||||
)` : '';
|
||||
const filterArgs = permissionPrefix ? [permissionPrefix] : [];
|
||||
const [summary, filtered, permissions] = await Promise.all([
|
||||
(await database.prepare(`SELECT COUNT(*) AS total,
|
||||
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND p.name LIKE 'isac.%') THEN 1 ELSE 0 END) AS isac,
|
||||
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND p.name LIKE 'data.%') THEN 1 ELSE 0 END) AS data,
|
||||
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND p.name LIKE 'users.%') THEN 1 ELSE 0 END) AS users,
|
||||
SUM(CASE WHEN EXISTS (SELECT 1 FROM role_permissions rp JOIN permissions p ON p.id=rp.permission_id WHERE rp.role_id=roles.id AND p.name LIKE 'audit.%') THEN 1 ELSE 0 END) AS audit
|
||||
FROM roles`)).get(),
|
||||
(await database.prepare(`SELECT COUNT(*) AS total FROM roles ${where}`)).get(...filterArgs),
|
||||
(await database.prepare(`SELECT id,name,description FROM permissions
|
||||
${canManageSystemAdmin ? '' : "WHERE name<>'system.admin'"} ORDER BY name COLLATE NOCASE`)).all()
|
||||
]);
|
||||
const pagination = getPagination(url, Number(filtered?.total ?? 0));
|
||||
const roles = await (await database.prepare(`SELECT roles.*,
|
||||
(SELECT COUNT(*) FROM user_roles WHERE role_id=roles.id) AS user_count
|
||||
FROM roles ${where} ORDER BY CASE WHEN roles.id='role-admin' THEN 0 ELSE 1 END,name COLLATE NOCASE
|
||||
LIMIT ? OFFSET ?`)).all(...filterArgs, pagination.perPage, pagination.offset);
|
||||
const roleIds = roles.map((role) => String(role.id));
|
||||
const links = roleIds.length ? await (await database.prepare(`SELECT role_id,permission_id FROM role_permissions
|
||||
WHERE role_id IN (${roleIds.map(() => '?').join(',')})`)).all(...roleIds) : [];
|
||||
return {
|
||||
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'
|
||||
})),
|
||||
permissions,
|
||||
summary,
|
||||
filter,
|
||||
pagination,
|
||||
canManageSystemAdmin
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
save: 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') ?? '');
|
||||
const name = String(form.get('name') ?? '').normalize('NFKC').trim();
|
||||
const description = String(form.get('description') ?? '').normalize('NFKC').trim();
|
||||
const permissionIds = [...new Set(form.getAll('permissionIds').map(String))];
|
||||
if (name.length < 2 || name.length > 60) return fail(400, { roleError: 'Role names must be 2–60 characters.' });
|
||||
if (description.length > 240) return fail(400, { roleError: 'Descriptions must be 240 characters or fewer.' });
|
||||
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 (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.' });
|
||||
}
|
||||
const validPermissions = permissionIds.length ? await (await database.prepare(`SELECT id,name FROM permissions WHERE id IN (${permissionIds.map(() => '?').join(',')})`)).all(...permissionIds) : [];
|
||||
if (validPermissions.length !== permissionIds.length) return fail(400, { roleError: 'One or more permissions are invalid.' });
|
||||
if (!canManageSystemAdmin && validPermissions.some((permission) => permission.name === 'system.admin')) {
|
||||
return fail(403, { roleError: 'Only a full administrator can grant unrestricted system access.' });
|
||||
}
|
||||
const roleId = id || randomUUID();
|
||||
const beforeLinks = id ? await (await database.prepare(`SELECT permissions.name FROM role_permissions
|
||||
JOIN permissions ON permissions.id=role_permissions.permission_id WHERE role_id=? ORDER BY permissions.name`)).all(id) : [];
|
||||
if (id) await (await database.prepare('UPDATE roles SET name=?,description=? WHERE id=?')).run(name, description || null, id);
|
||||
else await (await database.prepare('INSERT INTO roles(id,name,description) VALUES(?,?,?)')).run(roleId, name, description || null);
|
||||
await (await database.prepare('DELETE FROM role_permissions WHERE role_id=?')).run(roleId);
|
||||
for (const permissionId of permissionIds) {
|
||||
await (await database.prepare('INSERT INTO role_permissions(role_id,permission_id) VALUES(?,?)')).run(roleId, permissionId);
|
||||
}
|
||||
await recordIsacAudit({
|
||||
actorId: operator.id, category: 'role_management', action: id ? 'role.updated' : 'role.created', entityId: roleId,
|
||||
before: id ? { name: existing?.name, description: existing?.description, permissions: beforeLinks.map((row) => row.name) } : null,
|
||||
after: { name, description: description || null, permissions: validPermissions.map((row) => row.name) }
|
||||
});
|
||||
return { roleSaved: true, roleMode: id ? 'edit' : 'add', resultToken: randomUUID() };
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user