diff --git a/keyserver/src/updaters/thread-updaters.js b/keyserver/src/updaters/thread-updaters.js --- a/keyserver/src/updaters/thread-updaters.js +++ b/keyserver/src/updaters/thread-updaters.js @@ -76,13 +76,14 @@ throw new ServerError('not_logged_in'); } - const [memberIDs, hasPermission] = await Promise.all([ + const [memberIDs, hasPermission, fetchThreadResult] = await Promise.all([ verifyUserIDs(request.memberIDs), checkThreadPermission( viewer, request.threadID, threadPermissions.CHANGE_ROLE, ), + fetchThreadInfos(viewer, SQL`t.id = ${request.threadID}`), ]); if (memberIDs.length === 0) { throw new ServerError('invalid_parameters'); @@ -91,6 +92,35 @@ throw new ServerError('invalid_credentials'); } + const threadInfo = fetchThreadResult.threadInfos[request.threadID]; + if (!threadInfo) { + throw new ServerError('invalid_parameters'); + } + + const adminRoleID = Object.keys(threadInfo.roles).find( + roleID => threadInfo.roles[roleID].name === 'Admins', + ); + + // Ensure that there will always still be at least one admin in a community + if (adminRoleID) { + const memberRoles = memberIDs.map( + memberID => + threadInfo.members.find(member => member.id === memberID)?.role, + ); + + const communityAdminsCount = threadInfo.members.filter( + member => member.role === adminRoleID, + ).length; + + const changedAdminsCount = memberRoles.filter( + memberRole => memberRole === adminRoleID, + ).length; + + if (changedAdminsCount >= communityAdminsCount) { + throw new ServerError('invalid_parameters'); + } + } + const query = SQL` SELECT user, role FROM memberships