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