Add files via upload

This commit is contained in:
Kosztyk 2026-01-12 23:05:36 +02:00 committed by GitHub
parent 8baa7c2c55
commit 6722907b74
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -936,36 +936,46 @@ export const user = new Elysia()
return redirect(`${WEBROOT}/account`, 302); return redirect(`${WEBROOT}/account`, 302);
} }
// Prevent demoting the last admin // Atomic last-admin protection: concurrent demotions must not be able to leave zero admins.
if (targetUser.role === "admin" && role !== "admin") { // Use a write transaction to serialize changes and a conditional UPDATE for demotion.
const adminCountRow = db db.exec("BEGIN IMMEDIATE");
.query("SELECT COUNT(*) AS cnt FROM users WHERE role = 'admin'") try {
.get() as { cnt: number }; // Role change
if (adminCountRow.cnt <= 1) { if (role === "admin") {
db.query("UPDATE users SET role = 'admin' WHERE id = ?").run(targetId);
} else if (role === "user") {
// If demoting an admin, only allow if there is more than 1 admin at the time of the update.
const demoteRes = db
.query(
`UPDATE users
SET role = 'user'
WHERE id = ?
AND role = 'admin'
AND (SELECT COUNT(*) FROM users WHERE role = 'admin') > 1`,
)
.run(targetId);
if (targetUser.role === "admin" && demoteRes.changes === 0) {
db.exec("ROLLBACK");
set.status = 400; set.status = 400;
return { message: "You cannot demote the last remaining admin." }; return { message: "You cannot demote the last remaining admin." };
} }
} }
const fields: string[] = []; // Password change (optional)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const values: any[] = [];
if (role === "admin" || role === "user") {
fields.push("role");
values.push(role);
}
if (newPassword && newPassword.trim().length > 0) { if (newPassword && newPassword.trim().length > 0) {
fields.push("password"); const hashed = await Bun.password.hash(newPassword);
values.push(await Bun.password.hash(newPassword)); db.query("UPDATE users SET password = ? WHERE id = ?").run(hashed, targetId);
} }
if (fields.length > 0) { db.exec("COMMIT");
db.query(`UPDATE users SET ${fields.map((f) => `${f}=?`).join(", ")} WHERE id=?`).run( } catch (e) {
...values, try {
targetId, db.exec("ROLLBACK");
); } catch (rollbackErr) {
console.warn("[user/edit-user] ROLLBACK failed:", rollbackErr);
}
throw e;
} }
return redirect(`${WEBROOT}/account`, 302); return redirect(`${WEBROOT}/account`, 302);