fix: resolve permission issues (#3029)

This commit is contained in:
David Nguyen
2026-06-26 14:46:30 +10:00
committed by GitHub
parent 241929bb97
commit 96ab78c33f
5 changed files with 447 additions and 7 deletions
@@ -1,8 +1,15 @@
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
import {
ORGANISATION_MEMBER_ROLE_HIERARCHY,
ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP,
} from '@documenso/lib/constants/organisations';
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
import { jobs } from '@documenso/lib/jobs/client';
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
import {
buildOrganisationWhereQuery,
getHighestOrganisationRoleInGroup,
isOrganisationRoleWithinUserHierarchy,
} from '@documenso/lib/utils/organisations';
import { prisma } from '@documenso/prisma';
import { OrganisationMemberInviteStatus } from '@documenso/prisma/client';
@@ -60,9 +67,12 @@ export const deleteOrganisationMembers = async ({
},
},
members: {
select: {
id: true,
userId: true,
include: {
organisationGroupMembers: {
include: {
group: true,
},
},
},
},
invites: {
@@ -84,6 +94,41 @@ export const deleteOrganisationMembers = async ({
const membersToDelete = organisation.members.filter((member) => organisationMemberIds.includes(member.id));
const currentUserMember = organisation.members.find((member) => member.userId === userId);
if (!currentUserMember) {
throw new AppError(AppErrorCode.UNAUTHORIZED);
}
const currentUserOrganisationRole = getHighestOrganisationRoleInGroup(
currentUserMember.organisationGroupMembers.map(({ group }) => group),
);
// The roles the current user is allowed to act on (their own role and below).
const manageableOrganisationRoles = ORGANISATION_MEMBER_ROLE_HIERARCHY[currentUserOrganisationRole];
for (const member of membersToDelete) {
// The organisation owner can never be removed via this route. Ownership must
// be transferred first (mirrors the admin and update-member routes).
if (member.userId === organisation.ownerUserId) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Cannot remove the organisation owner',
});
}
const memberOrganisationRole = getHighestOrganisationRoleInGroup(
member.organisationGroupMembers.map(({ group }) => group),
);
// A user cannot remove a member whose role is higher than their own
// (e.g. a manager removing an admin).
if (!isOrganisationRoleWithinUserHierarchy(currentUserOrganisationRole, memberOrganisationRole)) {
throw new AppError(AppErrorCode.UNAUTHORIZED, {
message: 'Cannot remove a member with a higher role',
});
}
}
const inviteCount = organisation.invites.length;
const newMemberCount = organisation.members.length + inviteCount - membersToDelete.length;
@@ -123,6 +168,18 @@ export const deleteOrganisationMembers = async ({
in: organisationMemberIds,
},
organisationId,
userId: {
not: organisation.ownerUserId,
},
organisationGroupMembers: {
none: {
group: {
organisationRole: {
notIn: manageableOrganisationRoles,
},
},
},
},
},
});
});