mirror of
https://github.com/documenso/documenso
synced 2026-04-21 13:27:18 +00:00
fix: prevent managers from deleting admin invitations (#2636)
This commit is contained in:
parent
b2d395e00b
commit
ace472c294
2 changed files with 59 additions and 14 deletions
|
|
@ -11,6 +11,7 @@ import { useUpdateSearchParams } from '@documenso/lib/client-only/hooks/use-upda
|
|||
import { useCurrentOrganisation } from '@documenso/lib/client-only/providers/organisation';
|
||||
import { ORGANISATION_MEMBER_ROLE_MAP } from '@documenso/lib/constants/organisations-translations';
|
||||
import { ZUrlSearchParamsSchema } from '@documenso/lib/types/search-params';
|
||||
import { isOrganisationRoleWithinUserHierarchy } from '@documenso/lib/utils/organisations';
|
||||
import { trpc } from '@documenso/trpc/react';
|
||||
import { AvatarWithText } from '@documenso/ui/primitives/avatar';
|
||||
import type { DataTableColumnDef } from '@documenso/ui/primitives/data-table';
|
||||
|
|
@ -108,7 +109,7 @@ export const OrganisationMemberInvitesTable = () => {
|
|||
avatarClass="h-12 w-12"
|
||||
avatarFallback={row.original.email.slice(0, 1).toUpperCase()}
|
||||
primaryText={
|
||||
<span className="text-foreground/80 font-semibold">{row.original.email}</span>
|
||||
<span className="font-semibold text-foreground/80">{row.original.email}</span>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
|
@ -129,7 +130,7 @@ export const OrganisationMemberInvitesTable = () => {
|
|||
cell: ({ row }) => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<MoreHorizontal className="text-muted-foreground h-5 w-5" />
|
||||
<MoreHorizontal className="h-5 w-5 text-muted-foreground" />
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="w-52" align="start" forceMount>
|
||||
|
|
@ -149,17 +150,22 @@ export const OrganisationMemberInvitesTable = () => {
|
|||
<Trans>Resend</Trans>
|
||||
</DropdownMenuItem>
|
||||
|
||||
<DropdownMenuItem
|
||||
onClick={async () =>
|
||||
deleteOrganisationMemberInvitations({
|
||||
organisationId: organisation.id,
|
||||
invitationIds: [row.original.id],
|
||||
})
|
||||
}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
<Trans>Remove</Trans>
|
||||
</DropdownMenuItem>
|
||||
{isOrganisationRoleWithinUserHierarchy(
|
||||
organisation.currentOrganisationRole,
|
||||
row.original.organisationRole,
|
||||
) && (
|
||||
<DropdownMenuItem
|
||||
onClick={async () =>
|
||||
deleteOrganisationMemberInvitations({
|
||||
organisationId: organisation.id,
|
||||
invitationIds: [row.original.id],
|
||||
})
|
||||
}
|
||||
>
|
||||
<Trash2 className="mr-2 h-4 w-4" />
|
||||
<Trans>Remove</Trans>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
),
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
import { syncMemberCountWithStripeSeatPlan } from '@documenso/ee/server-only/stripe/update-subscription-item-quantity';
|
||||
import { ORGANISATION_MEMBER_ROLE_PERMISSIONS_MAP } from '@documenso/lib/constants/organisations';
|
||||
import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error';
|
||||
import { getMemberOrganisationRole } from '@documenso/lib/server-only/team/get-member-roles';
|
||||
import { validateIfSubscriptionIsRequired } from '@documenso/lib/utils/billing';
|
||||
import { buildOrganisationWhereQuery } from '@documenso/lib/utils/organisations';
|
||||
import {
|
||||
buildOrganisationWhereQuery,
|
||||
isOrganisationRoleWithinUserHierarchy,
|
||||
} from '@documenso/lib/utils/organisations';
|
||||
import { prisma } from '@documenso/prisma';
|
||||
|
||||
import { authenticatedProcedure } from '../trpc';
|
||||
|
|
@ -52,6 +56,41 @@ export const deleteOrganisationMemberInvitesRoute = authenticatedProcedure
|
|||
throw new AppError(AppErrorCode.NOT_FOUND);
|
||||
}
|
||||
|
||||
const currentOrganisationMemberRole = await getMemberOrganisationRole({
|
||||
organisationId: organisation.id,
|
||||
reference: {
|
||||
type: 'User',
|
||||
id: userId,
|
||||
},
|
||||
});
|
||||
|
||||
const invitesToDelete = await prisma.organisationMemberInvite.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: invitationIds,
|
||||
},
|
||||
organisationId: organisation.id,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
organisationRole: true,
|
||||
},
|
||||
});
|
||||
|
||||
const hasUnauthorizedRoleAccess = invitesToDelete.some(
|
||||
(invite) =>
|
||||
!isOrganisationRoleWithinUserHierarchy(
|
||||
currentOrganisationMemberRole,
|
||||
invite.organisationRole,
|
||||
),
|
||||
);
|
||||
|
||||
if (hasUnauthorizedRoleAccess) {
|
||||
throw new AppError(AppErrorCode.UNAUTHORIZED, {
|
||||
message: 'User does not have permission to delete invitations for higher roles',
|
||||
});
|
||||
}
|
||||
|
||||
const { organisationClaim } = organisation;
|
||||
|
||||
const subscription = validateIfSubscriptionIsRequired(organisation.subscription);
|
||||
|
|
|
|||
Loading…
Reference in a new issue