From 3af315edbc8a8fe4e4a0f3e6bea56e4617406097 Mon Sep 17 00:00:00 2001 From: Netrifier Date: Fri, 14 Mar 2025 02:14:00 +0530 Subject: [PATCH] fix(organization): multiple role array not referenced properly (#1792) --- docs/content/docs/plugins/organization.mdx | 2 +- .../plugins/organization/organization.test.ts | 42 +++++++++++++++++++ .../src/plugins/organization/organization.ts | 4 ++ .../organization/routes/crud-invites.ts | 28 +++++++++---- .../organization/routes/crud-members.ts | 22 ++++++---- 5 files changed, 80 insertions(+), 18 deletions(-) diff --git a/docs/content/docs/plugins/organization.mdx b/docs/content/docs/plugins/organization.mdx index 1a8d00f73c..fda3ee1c6e 100644 --- a/docs/content/docs/plugins/organization.mdx +++ b/docs/content/docs/plugins/organization.mdx @@ -597,7 +597,7 @@ auth.api.addMember({ body: { userId: "user-id", organizationId: "organization-id", - role: "admin" + role: "admin" // this can also be an array for multiple roles (e.g. ["admin", "sale"]) } }) ``` diff --git a/packages/better-auth/src/plugins/organization/organization.test.ts b/packages/better-auth/src/plugins/organization/organization.test.ts index de993640a7..bf3f8e54d4 100644 --- a/packages/better-auth/src/plugins/organization/organization.test.ts +++ b/packages/better-auth/src/plugins/organization/organization.test.ts @@ -250,6 +250,18 @@ describe("organization", async (it) => { }, ); + it("should create invitation with multiple roles", async () => { + const invite = await client.organization.inviteMember({ + organizationId: organizationId, + email: "test5@test.com", + role: ["admin", "member"], + fetchOptions: { + headers, + }, + }); + expect(invite.data?.role).toBe("admin,member"); + }); + it("should allow getting a member", async () => { const { headers } = await signInWithTestUser(); await client.organization.setActive({ @@ -519,6 +531,36 @@ describe("organization", async (it) => { expect(member?.role).toBe("admin"); }); + it("should add member on the server with multiple roles", async () => { + const newUser = await auth.api.signUpEmail({ + body: { + email: "new-member-mr@email.com", + password: "password", + name: "new member mr", + }, + }); + const session = await auth.api.getSession({ + headers: new Headers({ + Authorization: `Bearer ${newUser?.token}`, + }), + }); + const org = await auth.api.createOrganization({ + body: { + name: "test2", + slug: "test4", + }, + headers, + }); + const member = await auth.api.addMember({ + body: { + organizationId: org?.id, + userId: session?.user.id!, + role: ["admin", "member"], + }, + }); + expect(member?.role).toBe("admin,member"); + }); + it("should respect membershipLimit when adding members to organization", async () => { const org = await auth.api.createOrganization({ body: { diff --git a/packages/better-auth/src/plugins/organization/organization.ts b/packages/better-auth/src/plugins/organization/organization.ts index 59134a4668..c41dc7d64e 100644 --- a/packages/better-auth/src/plugins/organization/organization.ts +++ b/packages/better-auth/src/plugins/organization/organization.ts @@ -44,6 +44,10 @@ import { ORGANIZATION_ERROR_CODES } from "./error-codes"; import { defaultRoles, defaultStatements } from "./access"; import { hasPermission } from "./has-permission"; +export function parseRoles(roles: string | string[]): string { + return Array.isArray(roles) ? roles.join(",") : roles; +} + export interface OrganizationOptions { /** * Configure whether new users are able to create new organizations. diff --git a/packages/better-auth/src/plugins/organization/routes/crud-invites.ts b/packages/better-auth/src/plugins/organization/routes/crud-invites.ts index 4c7db38184..c4897d29ff 100644 --- a/packages/better-auth/src/plugins/organization/routes/crud-invites.ts +++ b/packages/better-auth/src/plugins/organization/routes/crud-invites.ts @@ -5,7 +5,7 @@ import { getOrgAdapter } from "../adapter"; import { orgMiddleware, orgSessionMiddleware } from "../call"; import { type InferRolesFromOption } from "../schema"; import { APIError } from "better-call"; -import type { OrganizationOptions } from "../organization"; +import { parseRoles, type OrganizationOptions } from "../organization"; import { ORGANIZATION_ERROR_CODES } from "../error-codes"; import { hasPermission } from "../has-permission"; @@ -21,9 +21,16 @@ export const createInvitation = ( email: z.string({ description: "The email address of the user to invite", }), - role: z.string({ - description: "The role to assign to the user", - }), + role: z.union([ + z.string({ + description: "The role to assign to the user", + }), + z.array( + z.string({ + description: "The roles to assign to the user", + }), + ), + ]), organizationId: z .string({ description: "The organization ID to invite the user to", @@ -52,7 +59,7 @@ export const createInvitation = ( /** * The role to assign to the user */ - role: InferRolesFromOption; + role: InferRolesFromOption | InferRolesFromOption[]; /** * The organization ID to invite * the user to @@ -166,7 +173,12 @@ export const createInvitation = ( const creatorRole = ctx.context.orgOptions.creatorRole || "owner"; - if (member.role !== creatorRole && ctx.body.role === creatorRole) { + const roles = parseRoles(ctx.body.role as string | string[]); + + if ( + member.role !== creatorRole && + roles.split(",").includes(creatorRole) + ) { throw new APIError("FORBIDDEN", { message: ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE, @@ -195,9 +207,7 @@ export const createInvitation = ( } const invitation = await adapter.createInvitation({ invitation: { - role: Array.isArray(ctx.body.role) - ? ctx.body.role.join(",") - : (ctx.body.role as string), + role: roles, email: ctx.body.email, organizationId: organizationId, ...("teamId" in ctx.body diff --git a/packages/better-auth/src/plugins/organization/routes/crud-members.ts b/packages/better-auth/src/plugins/organization/routes/crud-members.ts index 01137d5119..34027fe59b 100644 --- a/packages/better-auth/src/plugins/organization/routes/crud-members.ts +++ b/packages/better-auth/src/plugins/organization/routes/crud-members.ts @@ -5,7 +5,7 @@ import { orgMiddleware, orgSessionMiddleware } from "../call"; import type { InferRolesFromOption, Member } from "../schema"; import { APIError } from "better-call"; import { generateId } from "../../../utils"; -import type { OrganizationOptions } from "../organization"; +import { parseRoles, type OrganizationOptions } from "../organization"; import { getSessionFromCtx, sessionMiddleware } from "../../../api"; import { ORGANIZATION_ERROR_CODES } from "../error-codes"; import { BASE_ERROR_CODES } from "../../../error/codes"; @@ -16,14 +16,18 @@ export const addMember = () => "/organization/add-member", { method: "POST", - body: z.record(z.string()), + body: z.object({ + userId: z.string(), + role: z.union([z.string(), z.array(z.string())]), + organizationId: z.string().optional(), + }), use: [orgMiddleware], metadata: { SERVER_ONLY: true, $Infer: { body: {} as { userId: string; - role: InferRolesFromOption; + role: InferRolesFromOption | InferRolesFromOption[]; organizationId?: string; }, }, @@ -86,7 +90,7 @@ export const addMember = () => id: generateId(), organizationId: orgId, userId: user.id, - role: ctx.body.role as string, + role: parseRoles(ctx.body.role as string | string[]), createdAt: new Date(), }); @@ -257,7 +261,11 @@ export const updateMemberRole = (option: O) => "/organization/update-member-role", { method: "POST", - body: z.record(z.any()), + body: z.object({ + role: z.union([z.string(), z.array(z.string())]), + memberId: z.string(), + organizationId: z.string().optional(), + }), use: [orgMiddleware, orgSessionMiddleware], metadata: { $Infer: { @@ -380,9 +388,7 @@ export const updateMemberRole = (option: O) => const updatedMember = await adapter.updateMember( ctx.body.memberId, - Array.isArray(ctx.body.role) - ? ctx.body.role?.join(",") - : (ctx.body.role as string), + parseRoles(ctx.body.role as string | string[]), ); if (!updatedMember) { throw new APIError("BAD_REQUEST", {