diff --git a/migrations/20260226120000_add_is_owner_to_users.ts b/migrations/20260226120000_add_is_owner_to_users.ts index b82d26fc..3a800d8d 100644 --- a/migrations/20260226120000_add_is_owner_to_users.ts +++ b/migrations/20260226120000_add_is_owner_to_users.ts @@ -6,12 +6,14 @@ export async function up(knex: Knex): Promise { await knex.schema.alterTable("users", (table) => { table.string("is_owner").defaultTo("NO").notNullable(); }); - } - // Set the first user (by id) as owner, if any users exist - const firstUser = await knex("users").orderBy("id", "asc").first(); - if (firstUser) { - await knex("users").where("id", firstUser.id).update({ is_owner: "YES" }); + // Set the first user (by id) as owner, if any users exist. + // This only runs when the column has just been added to avoid + // overwriting an existing owner on migration re-run. + const firstUser = await knex("users").orderBy("id", "asc").first(); + if (firstUser) { + await knex("users").where("id", firstUser.id).update({ is_owner: "YES" }); + } } } diff --git a/src/lib/server/controllers/userController.ts b/src/lib/server/controllers/userController.ts index 9eb2fc38..5077eea0 100644 --- a/src/lib/server/controllers/userController.ts +++ b/src/lib/server/controllers/userController.ts @@ -229,8 +229,10 @@ export const UpdatePassword = async (data: PasswordUpdateInput): Promise }); }; +const VALID_ROLES = ["admin", "editor", "member"] as const; + export const ManualUpdateUserData = async ( - byUser: { role: string; is_owner: string }, + byUser: { id: number; role: string; is_owner: string }, forUserId: number, data: ManualUserUpdateInput, ): Promise => { @@ -242,12 +244,15 @@ export const ManualUpdateUserData = async ( if (byUser.role !== "admin") { throw new Error("You do not have permission to update user"); } - // non-owner admins cannot modify other admins - if (forUser.role === "admin" && byUser.is_owner !== "YES") { + // non-owner admins cannot modify other admins (self-updates are allowed) + if (forUser.role === "admin" && byUser.is_owner !== "YES" && forUser.id !== byUser.id) { throw new Error("Only the owner can modify other admins"); } if (data.updateType == "role") { if (!data.role) throw new Error("Role is required"); + if (!VALID_ROLES.includes(data.role as (typeof VALID_ROLES)[number])) { + throw new Error(`Invalid role. Must be one of: ${VALID_ROLES.join(", ")}`); + } return await db.updateUserRole(forUser.id, data.role); } else if (data.updateType == "is_active") { if (data.is_active === undefined) throw new Error("is_active is required"); @@ -259,6 +264,8 @@ export const ManualUpdateUserData = async ( newPassword: data.password, newPlainPassword: data.passwordPlain, }); + } else { + throw new Error(`Unsupported update type: ${data.updateType}`); } };