feat: prevent removing the last admin user

Add guard in removeRoleMember that checks countUsersByRole before
demoting an ADMIN user, returning 400 if they are the last one.
This commit is contained in:
Dustin Healy 2026-03-26 17:05:43 -07:00
parent b9e0fa48c6
commit e9572854ef
2 changed files with 43 additions and 0 deletions

View file

@ -1086,6 +1086,42 @@ describe('createAdminRolesHandlers', () => {
expect(deps.updateUser).not.toHaveBeenCalled();
});
it('returns 400 when removing the last admin user', async () => {
const deps = createDeps({
getRoleByName: jest.fn().mockResolvedValue(mockRole({ name: SystemRoles.ADMIN })),
findUser: jest.fn().mockResolvedValue(mockUser({ role: SystemRoles.ADMIN })),
countUsersByRole: jest.fn().mockResolvedValue(1),
});
const handlers = createAdminRolesHandlers(deps);
const { req, res, status, json } = createReqRes({
params: { name: SystemRoles.ADMIN, userId: validUserId },
});
await handlers.removeRoleMember(req, res);
expect(status).toHaveBeenCalledWith(400);
expect(json).toHaveBeenCalledWith({ error: 'Cannot remove the last admin user' });
expect(deps.updateUser).not.toHaveBeenCalled();
});
it('allows removing an admin when multiple admins exist', async () => {
const deps = createDeps({
getRoleByName: jest.fn().mockResolvedValue(mockRole({ name: SystemRoles.ADMIN })),
findUser: jest.fn().mockResolvedValue(mockUser({ role: SystemRoles.ADMIN })),
countUsersByRole: jest.fn().mockResolvedValue(3),
});
const handlers = createAdminRolesHandlers(deps);
const { req, res, status, json } = createReqRes({
params: { name: SystemRoles.ADMIN, userId: validUserId },
});
await handlers.removeRoleMember(req, res);
expect(status).toHaveBeenCalledWith(200);
expect(json).toHaveBeenCalledWith({ success: true });
expect(deps.updateUser).toHaveBeenCalledWith(validUserId, { role: SystemRoles.USER });
});
it('returns 500 on unexpected error', async () => {
const deps = createDeps({
getRoleByName: jest.fn().mockResolvedValue(mockRole()),

View file

@ -337,6 +337,13 @@ export function createAdminRolesHandlers(deps: AdminRolesDeps) {
return res.status(400).json({ error: 'User is not a member of this role' });
}
if (name === SystemRoles.ADMIN) {
const adminCount = await countUsersByRole(SystemRoles.ADMIN);
if (adminCount <= 1) {
return res.status(400).json({ error: 'Cannot remove the last admin user' });
}
}
await updateUser(userId, { role: SystemRoles.USER });
return res.status(200).json({ success: true });
} catch (error) {