Add soft account deletion safeguards
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import type { Database } from '@tursodatabase/database';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
export async function softDeleteAccount(
|
||||
database: Database,
|
||||
userId: string,
|
||||
deletedBy: string,
|
||||
reason: string | null
|
||||
): Promise<boolean> {
|
||||
const user = await (await database.prepare(`
|
||||
SELECT users.username,deleted_accounts.deleted_at
|
||||
FROM users LEFT JOIN deleted_accounts ON deleted_accounts.user_id=users.id
|
||||
WHERE users.id=?
|
||||
`)).get(userId);
|
||||
if (!user || user.deleted_at) return false;
|
||||
const now = new Date().toISOString();
|
||||
const deletedUsername = `deleted-agent-${userId.slice(0, 12)}`;
|
||||
|
||||
await (await database.prepare(
|
||||
'INSERT INTO deleted_accounts(user_id,original_username,deleted_at,deleted_by,reason) VALUES(?,?,?,?,?)'
|
||||
)).run(userId, String(user.username), now, deletedBy, reason);
|
||||
await (await database.prepare(
|
||||
`UPDATE users SET username=?,password_hash=NULL,discord_id=NULL,role='user' WHERE id=?`
|
||||
)).run(deletedUsername, userId);
|
||||
await (await database.prepare('DELETE FROM sessions WHERE user_id=?')).run(userId);
|
||||
await (await database.prepare('DELETE FROM oauth_states WHERE user_id=?')).run(userId);
|
||||
await (await database.prepare('DELETE FROM user_roles WHERE user_id=?')).run(userId);
|
||||
await (await database.prepare('DELETE FROM user_permissions WHERE user_id=?')).run(userId);
|
||||
await (await database.prepare(
|
||||
'INSERT INTO audit_log(id,actor_id,entity_type,entity_id,action,before_json,after_json,created_at) VALUES(?,?,?,?,?,?,?,?)'
|
||||
)).run(
|
||||
randomUUID(),
|
||||
deletedBy,
|
||||
'user',
|
||||
userId,
|
||||
'account.soft_deleted',
|
||||
JSON.stringify({ username: user.username }),
|
||||
JSON.stringify({ username: deletedUsername, reason }),
|
||||
now
|
||||
);
|
||||
return true;
|
||||
}
|
||||
@@ -56,10 +56,12 @@ export async function getSessionUser(cookies: Cookies): Promise<AuthUser | null>
|
||||
const database = await getDatabase();
|
||||
const statement = await database.prepare(`
|
||||
SELECT users.id, users.username, users.role, users.discord_id,
|
||||
user_status.last_active_at,user_status.blocked_at,user_status.banned_until
|
||||
user_status.last_active_at,user_status.blocked_at,user_status.banned_until,
|
||||
deleted_accounts.deleted_at
|
||||
FROM sessions
|
||||
JOIN users ON users.id = sessions.user_id
|
||||
LEFT JOIN user_status ON user_status.user_id=users.id
|
||||
LEFT JOIN deleted_accounts ON deleted_accounts.user_id=users.id
|
||||
WHERE sessions.token_hash = ? AND sessions.expires_at > ?
|
||||
`);
|
||||
const user = await statement.get(hashToken(token), new Date().toISOString());
|
||||
@@ -70,7 +72,7 @@ export async function getSessionUser(cookies: Cookies): Promise<AuthUser | null>
|
||||
}
|
||||
const now = new Date();
|
||||
const banned = typeof user.banned_until === 'string' && new Date(user.banned_until) > now;
|
||||
if (user.blocked_at || banned) {
|
||||
if (user.deleted_at || user.blocked_at || banned) {
|
||||
await (await database.prepare('DELETE FROM sessions WHERE token_hash=?')).run(hashToken(token));
|
||||
cookies.delete(COOKIE_NAME, { path: '/' });
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user