From c051c7589aee23af3f25ecc445a8209f355fc7ad Mon Sep 17 00:00:00 2001 From: Bereket Engida Date: Wed, 5 Mar 2025 13:30:32 +0300 Subject: [PATCH] fix(organization): custom permissions access control type inference breaking on the client --- .../src/plugins/organization/client.ts | 315 +++++++++--------- 1 file changed, 151 insertions(+), 164 deletions(-) diff --git a/packages/better-auth/src/plugins/organization/client.ts b/packages/better-auth/src/plugins/organization/client.ts index 974336db30..22b3c48fa3 100644 --- a/packages/better-auth/src/plugins/organization/client.ts +++ b/packages/better-auth/src/plugins/organization/client.ts @@ -13,14 +13,7 @@ import type { BetterAuthClientPlugin } from "../../client/types"; import type { organization } from "./organization"; import { useAuthQuery } from "../../client"; import { BetterAuthError } from "../../error"; -import { - defaultStatements, - defaultAc, - defaultRoles, - adminAc, - memberAc, - ownerAc, -} from "./access"; +import { defaultStatements, adminAc, memberAc, ownerAc } from "./access"; interface OrganizationClientOptions { ac?: AccessControl; @@ -32,171 +25,165 @@ interface OrganizationClientOptions { }; } -export const organizationClient = Object.assign( - (options?: O) => { - const $listOrg = atom(false); - const $activeOrgSignal = atom(false); - const $activeMemberSignal = atom(false); +export const organizationClient = ( + options?: O, +) => { + const $listOrg = atom(false); + const $activeOrgSignal = atom(false); + const $activeMemberSignal = atom(false); - type DefaultStatements = typeof defaultStatements; - type Statements = O["ac"] extends AccessControl + type DefaultStatements = typeof defaultStatements; + type Statements = O["ac"] extends AccessControl + ? S extends Record> ? S & DefaultStatements - : DefaultStatements; - const roles = { - admin: adminAc, - member: memberAc, - owner: ownerAc, - ...options?.roles, - }; + : DefaultStatements + : DefaultStatements; + const roles = { + admin: adminAc, + member: memberAc, + owner: ownerAc, + ...options?.roles, + }; - type OrganizationReturn = O["teams"] extends { enabled: true } - ? { - members: InferMember[]; - invitations: InferInvitation[]; - teams: Team[]; - } & Organization - : { - members: InferMember[]; - invitations: InferInvitation[]; - } & Organization; - return { - id: "organization", - $InferServerPlugin: {} as ReturnType< - typeof organization<{ - ac: O["ac"] extends AccessControl - ? O["ac"] - : AccessControl; - roles: O["roles"] extends Record - ? O["roles"] - : { - admin: Role; - member: Role; - owner: Role; - }; - teams: { - enabled: O["teams"] extends { enabled: true } ? true : false; - }; - }> - >, - getActions: ($fetch) => ({ - $Infer: { - ActiveOrganization: {} as OrganizationReturn, - Organization: {} as Organization, - Invitation: {} as InferInvitation, - Member: {} as InferInvitation, - Team: {} as Team, - }, - organization: { - checkRolePermission: < - R extends O extends { roles: any } - ? keyof O["roles"] - : "admin" | "member" | "owner", - >(data: { - role: R; - permission: { - //@ts-expect-error fix this later - [key in keyof Statements]?: Statements[key][number][]; + type OrganizationReturn = O["teams"] extends { enabled: true } + ? { + members: InferMember[]; + invitations: InferInvitation[]; + teams: Team[]; + } & Organization + : { + members: InferMember[]; + invitations: InferInvitation[]; + } & Organization; + return { + id: "organization", + $InferServerPlugin: {} as ReturnType< + typeof organization<{ + ac: O["ac"] extends AccessControl + ? O["ac"] + : AccessControl; + roles: O["roles"] extends Record + ? O["roles"] + : { + admin: Role; + member: Role; + owner: Role; }; - }) => { - if (Object.keys(data.permission).length > 1) { - throw new BetterAuthError( - "you can only check one resource permission at a time.", - ); - } - const role = roles[data.role as unknown as "admin"]; - if (!role) { - return false; - } - const isAuthorized = role?.authorize(data.permission as any); - return isAuthorized.success; - }, - }, - }), - getAtoms: ($fetch) => { - const listOrganizations = useAuthQuery( - $listOrg, - "/organization/list", - $fetch, - { - method: "GET", - }, - ); - const activeOrganization = useAuthQuery< - Prettify< - Organization & { - members: (Member & { - user: { - id: string; - name: string; - email: string; - image: string | undefined; - }; - })[]; - invitations: Invitation[]; - } - > - >( - [$activeOrgSignal], - "/organization/get-full-organization", - $fetch, - () => ({ - method: "GET", - }), - ); - - const activeMember = useAuthQuery( - [$activeMemberSignal], - "/organization/get-active-member", - $fetch, - { - method: "GET", - }, - ); - - return { - $listOrg, - $activeOrgSignal, - $activeMemberSignal, - activeOrganization, - listOrganizations, - activeMember, + teams: { + enabled: O["teams"] extends { enabled: true } ? true : false; }; + }> + >, + getActions: ($fetch) => ({ + $Infer: { + ActiveOrganization: {} as OrganizationReturn, + Organization: {} as Organization, + Invitation: {} as InferInvitation, + Member: {} as InferInvitation, + Team: {} as Team, }, - pathMethods: { - "/organization/get-full-organization": "GET", - }, - atomListeners: [ - { - matcher(path) { - return ( - path === "/organization/create" || - path === "/organization/delete" || - path === "/organization/update" + organization: { + checkRolePermission: < + R extends O extends { roles: any } + ? keyof O["roles"] + : "admin" | "member" | "owner", + >(data: { + role: R; + permission: { + //@ts-expect-error fix this later + [key in keyof Statements]?: Statements[key][number][]; + }; + }) => { + if (Object.keys(data.permission).length > 1) { + throw new BetterAuthError( + "you can only check one resource permission at a time.", ); - }, - signal: "$listOrg", + } + const role = roles[data.role as unknown as "admin"]; + if (!role) { + return false; + } + const isAuthorized = role?.authorize(data.permission as any); + return isAuthorized.success; }, + }, + }), + getAtoms: ($fetch) => { + const listOrganizations = useAuthQuery( + $listOrg, + "/organization/list", + $fetch, { - matcher(path) { - return path.startsWith("/organization"); - }, - signal: "$activeOrgSignal", + method: "GET", }, + ); + const activeOrganization = useAuthQuery< + Prettify< + Organization & { + members: (Member & { + user: { + id: string; + name: string; + email: string; + image: string | undefined; + }; + })[]; + invitations: Invitation[]; + } + > + >( + [$activeOrgSignal], + "/organization/get-full-organization", + $fetch, + () => ({ + method: "GET", + }), + ); + + const activeMember = useAuthQuery( + [$activeMemberSignal], + "/organization/get-active-member", + $fetch, { - matcher(path) { - return path.includes("/organization/update-member-role"); - }, - signal: "$activeMemberSignal", + method: "GET", }, - ], - } satisfies BetterAuthClientPlugin; - }, - { - defaultStatements, - defaultRoles, - defaultAc, - ownerAc, - adminAc, - memberAc, - }, -); + ); + + return { + $listOrg, + $activeOrgSignal, + $activeMemberSignal, + activeOrganization, + listOrganizations, + activeMember, + }; + }, + pathMethods: { + "/organization/get-full-organization": "GET", + }, + atomListeners: [ + { + matcher(path) { + return ( + path === "/organization/create" || + path === "/organization/delete" || + path === "/organization/update" + ); + }, + signal: "$listOrg", + }, + { + matcher(path) { + return path.startsWith("/organization"); + }, + signal: "$activeOrgSignal", + }, + { + matcher(path) { + return path.includes("/organization/update-member-role"); + }, + signal: "$activeMemberSignal", + }, + ], + } satisfies BetterAuthClientPlugin; +};