diff --git a/apps/web/modules/ee/role-management/actions.ts b/apps/web/modules/ee/role-management/actions.ts index 1586680dc4..7149ace62d 100644 --- a/apps/web/modules/ee/role-management/actions.ts +++ b/apps/web/modules/ee/role-management/actions.ts @@ -8,9 +8,11 @@ import { updateMembership } from "@/modules/ee/role-management/lib/membership"; import { ZInviteUpdateInput } from "@/modules/ee/role-management/types/invites"; import { z } from "zod"; import { IS_FORMBRICKS_CLOUD } from "@formbricks/lib/constants"; +import { getMembershipByUserIdOrganizationId } from "@formbricks/lib/membership/service"; import { getOrganization } from "@formbricks/lib/organization/service"; import { ZId, ZUuid } from "@formbricks/types/common"; import { OperationNotAllowedError, ValidationError } from "@formbricks/types/errors"; +import { AuthenticationError } from "@formbricks/types/errors"; import { ZMembershipUpdateInput } from "@formbricks/types/memberships"; export const checkRoleManagementPermission = async (organizationId: string) => { @@ -34,6 +36,14 @@ const ZUpdateInviteAction = z.object({ export const updateInviteAction = authenticatedActionClient .schema(ZUpdateInviteAction) .action(async ({ ctx, parsedInput }) => { + const currentUserMembership = await getMembershipByUserIdOrganizationId( + ctx.user.id, + parsedInput.organizationId + ); + if (!currentUserMembership) { + throw new AuthenticationError("User not a member of this organization"); + } + await checkAuthorizationUpdated({ userId: ctx.user.id, organizationId: parsedInput.organizationId, @@ -51,6 +61,10 @@ export const updateInviteAction = authenticatedActionClient throw new ValidationError("Billing role is not allowed"); } + if (currentUserMembership.role === "manager" && parsedInput.data.role !== "member") { + throw new OperationNotAllowedError("Managers can only invite members"); + } + await checkRoleManagementPermission(parsedInput.organizationId); return await updateInvite(parsedInput.inviteId, parsedInput.data); @@ -65,6 +79,14 @@ const ZUpdateMembershipAction = z.object({ export const updateMembershipAction = authenticatedActionClient .schema(ZUpdateMembershipAction) .action(async ({ ctx, parsedInput }) => { + const currentUserMembership = await getMembershipByUserIdOrganizationId( + ctx.user.id, + parsedInput.organizationId + ); + if (!currentUserMembership) { + throw new AuthenticationError("User not a member of this organization"); + } + await checkAuthorizationUpdated({ userId: ctx.user.id, organizationId: parsedInput.organizationId, @@ -82,6 +104,10 @@ export const updateMembershipAction = authenticatedActionClient throw new ValidationError("Billing role is not allowed"); } + if (currentUserMembership.role === "manager" && parsedInput.data.role !== "member") { + throw new OperationNotAllowedError("Managers can only assign users to the member role"); + } + await checkRoleManagementPermission(parsedInput.organizationId); return await updateMembership(parsedInput.userId, parsedInput.organizationId, parsedInput.data); diff --git a/apps/web/modules/ee/role-management/components/add-member-role.tsx b/apps/web/modules/ee/role-management/components/add-member-role.tsx index 6d0fba778e..3682070ea3 100644 --- a/apps/web/modules/ee/role-management/components/add-member-role.tsx +++ b/apps/web/modules/ee/role-management/components/add-member-role.tsx @@ -10,25 +10,43 @@ import { SelectValue, } from "@/modules/ui/components/select"; import { Muted, P } from "@/modules/ui/components/typography"; -import { OrganizationRole } from "@prisma/client"; import { useTranslate } from "@tolgee/react"; -import type { Control } from "react-hook-form"; -import { Controller } from "react-hook-form"; +import { useMemo } from "react"; +import { type Control, Controller } from "react-hook-form"; +import { getAccessFlags } from "@formbricks/lib/membership/utils"; import { TOrganizationRole } from "@formbricks/types/memberships"; interface AddMemberRoleProps { control: Control<{ name: string; email: string; role: TOrganizationRole; teamIds: string[] }>; canDoRoleManagement: boolean; isFormbricksCloud: boolean; + membershipRole?: TOrganizationRole; } -export function AddMemberRole({ control, canDoRoleManagement, isFormbricksCloud }: AddMemberRoleProps) { - const roles = isFormbricksCloud - ? Object.values(OrganizationRole) - : Object.keys(OrganizationRole).filter((role) => role !== "billing"); +export function AddMemberRole({ + control, + canDoRoleManagement, + isFormbricksCloud, + membershipRole, +}: AddMemberRoleProps) { + const { isMember, isOwner } = getAccessFlags(membershipRole); const { t } = useTranslate(); + const roles = useMemo(() => { + let rolesArray = ["member"]; + + if (isOwner) { + rolesArray.push("owner", "manager"); + if (isFormbricksCloud) { + rolesArray.push("billing"); + } + } + return rolesArray; + }, [isOwner, isFormbricksCloud]); + + if (isMember) return null; + const rolesDescription = { owner: t("environments.settings.teams.owner_role_description"), manager: t("environments.settings.teams.manager_role_description"), @@ -44,7 +62,7 @@ export function AddMemberRole({ control, canDoRoleManagement, isFormbricksCloud