Add ISAC user operations and access roles
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { getPermissions, requirePermission } from '$lib/server/auth/authorization';
|
||||
import { getSessionUser } from '$lib/server/auth/session';
|
||||
import { getDatabase } from '$lib/server/database';
|
||||
import { error, fail } from '@sveltejs/kit';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import type { Actions, PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async ({ cookies, params }) => {
|
||||
const operator = await requirePermission(await getSessionUser(cookies), 'users.view');
|
||||
const database = await getDatabase();
|
||||
const [profile, roles, permissions, assignedRoles, directPermissions, logins, builds, comments] = await Promise.all([
|
||||
(await database.prepare(`SELECT users.id,users.username,users.role,users.discord_id,users.created_at,user_status.*
|
||||
FROM users LEFT JOIN user_status ON user_status.user_id=users.id WHERE users.id=?`)).get(params.id),
|
||||
(await database.prepare('SELECT * FROM roles ORDER BY name')).all(),
|
||||
(await database.prepare('SELECT * FROM permissions ORDER BY name')).all(),
|
||||
(await database.prepare('SELECT role_id FROM user_roles WHERE user_id=?')).all(params.id),
|
||||
(await database.prepare('SELECT permission_id FROM user_permissions WHERE user_id=?')).all(params.id),
|
||||
(await database.prepare('SELECT provider,succeeded,created_at FROM login_events WHERE user_id=? ORDER BY created_at DESC LIMIT 30')).all(params.id),
|
||||
(await database.prepare('SELECT id,title,slug,visibility,updated_at FROM builds WHERE owner_id=? ORDER BY updated_at DESC LIMIT 50')).all(params.id),
|
||||
(await database.prepare(`SELECT comments.id,comments.body,comments.created_at,builds.title,builds.slug
|
||||
FROM comments JOIN builds ON builds.id=comments.build_id WHERE comments.user_id=? ORDER BY comments.created_at DESC LIMIT 50`)).all(params.id)
|
||||
]);
|
||||
if (!profile) error(404, 'User not found');
|
||||
return {
|
||||
profile,
|
||||
roles,
|
||||
permissions,
|
||||
assignedRoleIds: assignedRoles.map((row) => String(row.role_id)),
|
||||
directPermissionIds: directPermissions.map((row) => String(row.permission_id)),
|
||||
logins, builds, comments,
|
||||
operatorPermissions: await getPermissions(operator),
|
||||
isSelf: operator.id === params.id
|
||||
};
|
||||
};
|
||||
|
||||
export const actions: Actions = {
|
||||
setAccess: async ({ cookies, request, params }) => {
|
||||
const operator = await requirePermission(await getSessionUser(cookies), 'users.roles');
|
||||
const form = await request.formData();
|
||||
const roleIds = [...new Set(form.getAll('roleIds').map(String))];
|
||||
const permissionIds = [...new Set(form.getAll('permissionIds').map(String))];
|
||||
const database = await getDatabase();
|
||||
const target = await (await database.prepare('SELECT role FROM users WHERE id=?')).get(params.id);
|
||||
if (!target) return fail(404, { accessError: 'User not found.' });
|
||||
const operatorPermissions = await getPermissions(operator);
|
||||
const targetAdminRole = await (await database.prepare(`SELECT 1 FROM user_roles WHERE user_id=? AND role_id='role-admin'`)).get(params.id);
|
||||
const changesAdminRole = roleIds.includes('role-admin') !== Boolean(targetAdminRole);
|
||||
if ((target.role === 'admin' || changesAdminRole) && !operatorPermissions.includes('*')) {
|
||||
return fail(403, { accessError: 'Only an administrator can grant, remove, or modify administrator access.' });
|
||||
}
|
||||
const validRoles = roleIds.length ? Number((await (await database.prepare(`SELECT COUNT(*) AS count FROM roles WHERE id IN (${roleIds.map(() => '?').join(',')})`)).get(...roleIds))?.count) : 0;
|
||||
const validPermissions = permissionIds.length ? Number((await (await database.prepare(`SELECT COUNT(*) AS count FROM permissions WHERE id IN (${permissionIds.map(() => '?').join(',')})`)).get(...permissionIds))?.count) : 0;
|
||||
if (validRoles !== roleIds.length || validPermissions !== permissionIds.length) return fail(400, { accessError: 'One or more access selections are invalid.' });
|
||||
await (await database.prepare('DELETE FROM user_roles WHERE user_id=?')).run(params.id);
|
||||
await (await database.prepare('DELETE FROM user_permissions WHERE user_id=?')).run(params.id);
|
||||
for (const roleId of roleIds) await (await database.prepare('INSERT INTO user_roles(user_id,role_id) VALUES(?,?)')).run(params.id, roleId);
|
||||
for (const permissionId of permissionIds) await (await database.prepare('INSERT INTO user_permissions(user_id,permission_id) VALUES(?,?)')).run(params.id, permissionId);
|
||||
await (await database.prepare('INSERT INTO audit_log(id,actor_id,entity_type,entity_id,action,after_json,created_at) VALUES(?,?,?,?,?,?,?)'))
|
||||
.run(randomUUID(), operator.id, 'user', params.id, 'access.updated', JSON.stringify({ roleIds, permissionIds }), new Date().toISOString());
|
||||
return { accessSaved: true };
|
||||
},
|
||||
moderate: async ({ cookies, request, params }) => {
|
||||
const operator = await requirePermission(await getSessionUser(cookies), 'users.moderate');
|
||||
if (operator.id === params.id) return fail(400, { moderationError: 'You cannot restrict your own account.' });
|
||||
const form = await request.formData(), state = String(form.get('state') ?? ''), reason = String(form.get('reason') ?? '').trim();
|
||||
if (!['active', 'blocked', 'banned'].includes(state)) return fail(400, { moderationError: 'Choose a valid access state.' });
|
||||
if (state !== 'active' && reason.length < 3) return fail(400, { moderationError: 'Provide a moderation reason.' });
|
||||
const untilRaw = String(form.get('bannedUntil') ?? '');
|
||||
const bannedUntil = state === 'banned' ? new Date(untilRaw) : null;
|
||||
if (state === 'banned' && (!untilRaw || Number.isNaN(bannedUntil?.getTime()) || bannedUntil! <= new Date())) return fail(400, { moderationError: 'Choose a future ban expiration.' });
|
||||
const now = new Date().toISOString(), database = await getDatabase();
|
||||
const target = await (await database.prepare('SELECT role FROM users WHERE id=?')).get(params.id);
|
||||
if (!target) return fail(404, { moderationError: 'User not found.' });
|
||||
const targetAdminRole = await (await database.prepare(`SELECT 1 FROM user_roles WHERE user_id=? AND role_id='role-admin'`)).get(params.id);
|
||||
if ((target.role === 'admin' || targetAdminRole) && !(await getPermissions(operator)).includes('*')) {
|
||||
return fail(403, { moderationError: 'Only an administrator can moderate another administrator.' });
|
||||
}
|
||||
await (await database.prepare(`INSERT INTO user_status(user_id,blocked_at,blocked_by,banned_until,moderation_reason,updated_at) VALUES(?,?,?,?,?,?)
|
||||
ON CONFLICT(user_id) DO UPDATE SET blocked_at=excluded.blocked_at,blocked_by=excluded.blocked_by,banned_until=excluded.banned_until,moderation_reason=excluded.moderation_reason,updated_at=excluded.updated_at`))
|
||||
.run(params.id, state === 'blocked' ? now : null, state === 'active' ? null : operator.id, state === 'banned' ? bannedUntil!.toISOString() : null, state === 'active' ? null : reason, now);
|
||||
if (state !== 'active') await (await database.prepare('DELETE FROM sessions WHERE user_id=?')).run(params.id);
|
||||
await (await database.prepare('INSERT INTO audit_log(id,actor_id,entity_type,entity_id,action,after_json,created_at) VALUES(?,?,?,?,?,?,?)'))
|
||||
.run(randomUUID(), operator.id, 'user', params.id, `moderation.${state}`, JSON.stringify({ reason, bannedUntil: bannedUntil?.toISOString() }), now);
|
||||
return { moderationSaved: true };
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user