From f6f1902b59bd2cb5f5cce69167d4161b287c0587 Mon Sep 17 00:00:00 2001 From: Fathi <33077644+fathisiddiqi@users.noreply.github.com> Date: Wed, 10 Sep 2025 04:48:55 +0700 Subject: [PATCH] feat: add getActiveRoleMember (#4484) Co-authored-by: KinfeMichael Tariku <65047246+Kinfe123@users.noreply.github.com> Co-authored-by: Alex Yang --- demo/nextjs/lib/auth-client.ts | 2 + docs/content/docs/plugins/organization.mdx | 15 +++ .../src/plugins/organization/client.ts | 18 ++++ .../src/plugins/organization/organization.ts | 17 ++++ .../organization/routes/crud-members.test.ts | 97 +++++++++++++++++++ .../organization/routes/crud-members.ts | 57 +++++++++++ 6 files changed, 206 insertions(+) diff --git a/demo/nextjs/lib/auth-client.ts b/demo/nextjs/lib/auth-client.ts index 2c73ce343c..2be85bc8af 100644 --- a/demo/nextjs/lib/auth-client.ts +++ b/demo/nextjs/lib/auth-client.ts @@ -56,4 +56,6 @@ export const { organization, useListOrganizations, useActiveOrganization, + useActiveMember, + useActiveMemberRole, } = client; diff --git a/docs/content/docs/plugins/organization.mdx b/docs/content/docs/plugins/organization.mdx index e2cad6e46a..78e3186c17 100644 --- a/docs/content/docs/plugins/organization.mdx +++ b/docs/content/docs/plugins/organization.mdx @@ -1162,6 +1162,21 @@ To get the current member of the active organization you can use the `organizati ``` +### Get Active Member Role +To get the current role member of the active organization you can use the `organization.getActiveMemberRole` function. This function will return the user's member role in their active organization. + + + ```ts + type getActiveMemberRole = { + } + ``` + + ### Add Member If you want to add a member directly to an organization without sending an invitation, you can use the `addMember` function which can only be invoked on the server. diff --git a/packages/better-auth/src/plugins/organization/client.ts b/packages/better-auth/src/plugins/organization/client.ts index 6bd92bc7c4..7ecd3fb890 100644 --- a/packages/better-auth/src/plugins/organization/client.ts +++ b/packages/better-auth/src/plugins/organization/client.ts @@ -64,6 +64,7 @@ export const organizationClient = ( const $listOrg = atom(false); const $activeOrgSignal = atom(false); const $activeMemberSignal = atom(false); + const $activeMemberRoleSignal = atom(false); type DefaultStatements = typeof defaultStatements; type Statements = CO["ac"] extends AccessControl @@ -204,13 +205,24 @@ export const organizationClient = ( }, ); + const activeMemberRole = useAuthQuery<{ role: string }>( + [$activeMemberRoleSignal], + "/organization/get-active-member-role", + $fetch, + { + method: "GET", + }, + ); + return { $listOrg, $activeOrgSignal, $activeMemberSignal, + $activeMemberRoleSignal, activeOrganization, listOrganizations, activeMember, + activeMemberRole, }; }, pathMethods: { @@ -246,6 +258,12 @@ export const organizationClient = ( }, signal: "$activeMemberSignal", }, + { + matcher(path) { + return path.includes("/organization/update-member-role"); + }, + signal: "$activeMemberRoleSignal", + }, ], } satisfies BetterAuthClientPlugin; }; diff --git a/packages/better-auth/src/plugins/organization/organization.ts b/packages/better-auth/src/plugins/organization/organization.ts index ab38f691ac..400aeb8432 100644 --- a/packages/better-auth/src/plugins/organization/organization.ts +++ b/packages/better-auth/src/plugins/organization/organization.ts @@ -25,6 +25,7 @@ import { listMembers, removeMember, updateMemberRole, + getActiveMemberRole, } from "./routes/crud-members"; import { checkOrganizationSlug, @@ -405,6 +406,22 @@ export const organization = (options?: O) => { * `authClient.organization.listMembers` */ listMembers: listMembers(options as O), + /** + * ### Endpoint + * + * GET `/organization/get-active-member-role` + * + * ### API Methods + * + * **server:** + * `auth.api.getActiveMemberRole` + * + * **client:** + * `authClient.organization.getActiveMemberRole` + * + * @see [Read our docs to learn more.](https://better-auth.com/docs/plugins/organization#api-method-organization-get-active-member-role) + */ + getActiveMemberRole: getActiveMemberRole(options as O), }; const teamSupport = options?.teams?.enabled; const teamEndpoints = { diff --git a/packages/better-auth/src/plugins/organization/routes/crud-members.test.ts b/packages/better-auth/src/plugins/organization/routes/crud-members.test.ts index ecc827d2f8..9d9cfc1939 100644 --- a/packages/better-auth/src/plugins/organization/routes/crud-members.test.ts +++ b/packages/better-auth/src/plugins/organization/routes/crud-members.test.ts @@ -333,3 +333,100 @@ describe("updateMemberRole", async () => { ); }); }); + +describe("activeMemberRole", async () => { + const { auth, signInWithTestUser, cookieSetter } = await getTestInstance({ + plugins: [organization()], + }); + const ctx = await auth.$context; + const { headers } = await signInWithTestUser(); + const client = createAuthClient({ + plugins: [organizationClient()], + baseURL: "http://localhost:3000/api/auth", + fetchOptions: { + customFetchImpl: async (url, init) => { + return auth.handler(new Request(url, init)); + }, + }, + }); + const org = await client.organization.create({ + name: "test", + slug: "test", + metadata: { + test: "test", + }, + fetchOptions: { + headers, + }, + }); + const secondOrg = await client.organization.create({ + name: "test-second", + slug: "test-second", + metadata: { + test: "second-org", + }, + fetchOptions: { + headers, + }, + }); + + let selectedUserId = ""; + for (let i = 0; i < 10; i++) { + const user = await ctx.adapter.create({ + model: "user", + data: { + email: `test${i}@test.com`, + name: `test${i}`, + }, + }); + + if (i == 0) { + selectedUserId = user.id; + } + + await auth.api.addMember({ + body: { + organizationId: org.data?.id as string, + userId: user.id, + role: "member", + }, + }); + } + + it("should return the active member role on active organization", async () => { + await client.organization.setActive({ + organizationId: org.data?.id as string, + fetchOptions: { + headers, + }, + }); + + const activeMember = await client.organization.getActiveMemberRole({ + fetchOptions: { + headers, + }, + }); + + expect(activeMember.data?.role).toBe("owner"); + }); + + it("should return active member role on organization", async () => { + await client.organization.setActive({ + organizationId: org.data?.id as string, + fetchOptions: { + headers, + }, + }); + + const activeMember = await client.organization.getActiveMemberRole({ + query: { + userId: selectedUserId, + }, + fetchOptions: { + headers, + }, + }); + + expect(activeMember.data?.role).toBe("member"); + }); +}); 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 5e535c3b49..2d1e4678c4 100644 --- a/packages/better-auth/src/plugins/organization/routes/crud-members.ts +++ b/packages/better-auth/src/plugins/organization/routes/crud-members.ts @@ -904,3 +904,60 @@ export const listMembers = (options: O) => }); }, ); + +export const getActiveMemberRole = ( + options: O, +) => + createAuthEndpoint( + "/organization/get-active-member-role", + { + method: "GET", + query: z + .object({ + userId: z + .string() + .meta({ + description: + "The user ID to get the role for. If not provided, will default to the current user's", + }) + .optional(), + organizationId: z + .string() + .meta({ + description: + 'The organization ID to list members for. If not provided, will default to the user\'s active organization. Eg: "organization-id"', + }) + .optional(), + }) + .optional(), + use: [orgMiddleware, orgSessionMiddleware], + }, + async (ctx) => { + const session = ctx.context.session; + const organizationId = + ctx.query?.organizationId || session.session.activeOrganizationId; + if (!organizationId) { + throw new APIError("BAD_REQUEST", { + message: ORGANIZATION_ERROR_CODES.NO_ACTIVE_ORGANIZATION, + }); + } + const userId = ctx.query?.userId || session.user.id; + + const adapter = getOrgAdapter(ctx.context, options); + + const member = await adapter.findMemberByOrgId({ + userId, + organizationId, + }); + if (!member) { + throw new APIError("FORBIDDEN", { + message: + ORGANIZATION_ERROR_CODES.YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION, + }); + } + + return ctx.json({ + role: member?.role, + }); + }, + );