组织

组织插件简化了用户访问和权限管理。分配角色和权限以简化项目管理、团队协作和合作伙伴关系。

安装

将插件添加到您的auth配置中

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
export const auth = betterAuth({
    plugins: [ 
        organization() 
    ] 
})

迁移数据库

运行迁移或生成schema,以向数据库添加必要的字段和表。

npx @better-auth/cli migrate

查看schema部分手动添加字段。

添加客户端插件

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
 
const client = createAuthClient({
    plugins: [ 
        organizationClient() 
    ] 
})

使用方法

安装插件后,您可以开始使用组织插件来管理组织的成员和团队。客户端插件将在organization命名空间下提供方法。服务器api将提供管理组织的必要端点,并为您提供更简便的方式在自己的后端调用这些函数。

组织

创建组织

要创建组织,您需要提供:

  • name:组织的名称。
  • slug:组织的唯一标识符。
  • logo:组织的徽标。(可选)
auth-client.ts
await authClient.organization.create({
    name: "My Organization",
    slug: "my-org",
    logo: "https://example.com/logo.png"
})

限制谁可以创建组织

默认情况下,任何用户都可以创建组织。要限制这一点,将allowUserToCreateOrganization选项设置为返回布尔值的函数,或直接设置为truefalse

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
const auth = betterAuth({
    //...
    plugins: [
        organization({
            allowUserToCreateOrganization: async (user) => { 
                const subscription = await getSubscription(user.id) 
                return subscription.plan === "pro"
            } 
        })
    ]
})

检查组织标识符是否已被使用

要检查组织标识符是否已被使用,您可以使用客户端提供的checkSlug函数。该函数接受一个具有以下属性的对象:

  • slug:组织的标识符。
auth-client.ts
await authClient.organization.checkSlug({
    slug: "my-org",
});

组织创建钩子

您可以使用在组织创建前后运行的钩子自定义组织创建过程。

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
export const auth = betterAuth({
    plugins: [
        organization({
            organizationCreation: {
                disabled: false, // 设置为true以禁用组织创建
                beforeCreate: async ({ organization, user }, request) => {
                    // 在组织创建前运行自定义逻辑
                    // 可选地修改组织数据
                    return {
                        data: {
                            ...organization,
                            metadata: {
                                customField: "value"
                            }
                        }
                    }
                },
                afterCreate: async ({ organization, member, user }, request) => {
                    // 在组织创建后运行自定义逻辑
                    // 例如,创建默认资源,发送通知
                    await setupDefaultResources(organization.id)
                }
            }
        })
    ]
})

beforeCreate钩子在组织创建前运行。它接收:

  • organization:组织数据(不包含ID)
  • user:创建组织的用户
  • request:HTTP请求对象(可选)

返回带有data属性的对象来修改将要创建的组织数据。

afterCreate钩子在组织成功创建后运行。它接收:

  • organization:已创建的组织(包含ID)
  • member:创建者的成员记录
  • user:创建组织的用户
  • request:HTTP请求对象(可选)

列出用户的组织

要列出用户所属的组织,可以使用useListOrganizations钩子。它以响应式方式获取用户所属的组织。

client.tsx
import { client } from "@/auth/client"
 
function App(){
    const { data: organizations } = client.useListOrganizations()
    return (
        <div>
            {organizations.map(org => <p>{org.name}</p>)}
        </div>
    )
}

活跃组织

活跃组织是用户当前正在使用的工作区。默认情况下,当用户登录时,活跃组织设置为null。您可以将活跃组织设置到用户会话中。

并非总是需要在会话中保存活跃组织。您可以只在客户端管理活跃组织。例如,多个标签页可以有不同的活跃组织。

设置活跃组织

您可以通过调用organization.setActive函数来设置活跃组织。它将为用户会话设置活跃组织。

auth-client.ts
import { client } from "@/lib/auth-client";
 
await authClient.organization.setActive({
  organizationId: "organization-id"
})
 
// 您也可以使用organizationSlug代替organizationId
await authClient.organization.setActive({
  organizationSlug: "organization-slug"
})

要在创建会话时设置活跃组织,可以使用数据库钩子

auth.ts
export const auth = betterAuth({
  databaseHooks: {
      session: {
          create: {
              before: async(session)=>{
                  const organization = await getActiveOrganization(session.userId)
                  return {
                    data: {
                      ...session,
                      activeOrganizationId: organization.id
                    }
                  }
              }
          }
      }
  }
})

使用活跃组织

要获取用户的活跃组织,可以调用useActiveOrganization钩子。它返回用户的活跃组织。每当活跃组织变化时,该钩子将重新评估并返回新的活跃组织。

client.tsx
import { client } from "@/auth/client"
 
function App(){
    const { data: activeOrganization } = client.useActiveOrganization()
    return (
        <div>
            {activeOrganization ? <p>{activeOrganization.name}</p> : null}
        </div>
    )
}

获取完整组织信息

要获取组织的完整详情,可以使用客户端提供的getFullOrganization函数。该函数接受一个具有以下属性的对象:

  • organizationId:组织的ID。(可选)– 默认情况下,它将使用活跃组织。
  • organizationSlug:组织的标识符。(可选)– 用于通过标识符获取组织。
auth-client.ts
const organization = await authClient.organization.getFullOrganization({
    organizationId: "organization-id" // 可选,默认将使用活跃组织
})
//您也可以使用organizationSlug代替organizationId
const organization = await authClient.organization.getFullOrganization({
    organizationSlug: "organization-slug"
})

更新组织

要更新组织信息,可以使用organization.update

await client.organization.update({
  data: {
    name: "updated-name",
    logo: "new-logo.url",
    metadata: {
      customerId: "test"
    },
    slug: "updated-slug"
  },
  organizationId: 'org-id' //默认为当前活跃组织
})

删除组织

要删除用户拥有的组织,可以使用organization.delete

org.ts
await authClient.organization.delete({
  organizationId: "test"
});

如果用户在指定组织中拥有必要的权限(默认情况下:角色为owner),所有成员、邀请和组织信息都将被删除。

您可以通过organizationDeletion选项配置组织删除的处理方式:

const auth = betterAuth({
  plugins: [
    organization({
      organizationDeletion: {
        disabled: true, //完全禁用删除功能
        beforeDelete: async (data, request) => {
          // 删除组织前运行的回调
        },
        afterDelete: async (data, request) => {
          // 删除组织后运行的回调
        },
      },
    }),
  ],
});

邀请

要将成员添加到组织中,我们首先需要向用户发送邀请。用户将收到带有邀请链接的电子邮件/短信。一旦用户接受邀请,他们将被添加到组织中。

设置邀请电子邮件

为了使成员邀请功能正常工作,我们首先需要向better-auth实例提供sendInvitationEmail。这个函数负责向用户发送邀请电子邮件。

您需要构建并向用户发送邀请链接。链接应包含邀请ID,当用户点击链接时将与acceptInvitation函数一起使用。

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { sendOrganizationInvitation } from "./email"
export const auth = betterAuth({
	plugins: [
		organization({
			async sendInvitationEmail(data) {
                const inviteLink = `https://example.com/accept-invitation/${data.id}`
				sendOrganizationInvitation({
					    email: data.email,
						invitedByUsername: data.inviter.user.name,
						invitedByEmail: data.inviter.user.email,
						teamName: data.organization.name,
						inviteLink
					})
			},
		}),
	],
});

发送邀请

要邀请用户加入组织,可以使用客户端提供的invite函数。invite函数接受一个具有以下属性的对象:

  • email:用户的电子邮件地址。
  • role:用户在组织中的角色。可以是adminmemberguest
  • organizationId:组织的ID。这是可选的,默认情况下它将使用活跃组织。(可选)
invitation.ts
await authClient.organization.inviteMember({
    email: "[email protected]",
    role: "admin", //这也可以是多个角色的数组(例如 ["admin", "sale"])
})
  • 如果用户已经是组织的成员,邀请将被取消。
  • 如果用户已经被邀请到组织,除非resend设置为true,否则不会再次发送邀请。
  • 如果cancelPendingInvitationsOnReInvite设置为true,如果用户已经被邀请到组织并发送了新邀请,原有邀请将被取消。

接受邀请

当用户收到邀请电子邮件时,他们可以点击邀请链接来接受邀请。邀请链接应包含邀请ID,用于接受邀请。

确保在用户登录后调用acceptInvitation函数。

auth-client.ts
await authClient.organization.acceptInvitation({
    invitationId: "invitation-id"
})

更新邀请状态

要更新邀请的状态,可以使用客户端提供的acceptInvitationcancelInvitationrejectInvitation函数。这些函数接受邀请ID作为参数。

auth-client.ts
//取消邀请
await authClient.organization.cancelInvitation({
    invitationId: "invitation-id"
})
 
//拒绝邀请(需要在收到邀请的用户登录时调用)
await authClient.organization.rejectInvitation({
    invitationId: "invitation-id"
})

获取邀请

要获取邀请,可以使用客户端提供的getInvitation函数。您需要提供邀请ID作为查询参数。

auth-client.ts
client.organization.getInvitation({
    query: {
        id: params.id
    }
})

列出邀请

要列出所有邀请,可以使用客户端提供的listInvitations函数。

auth-client.ts
const invitations = await authClient.organization.listInvitations({
    query: {
        organizationId: "organization-id" // 可选,默认将使用活跃组织
    }
})

Members

移除成员

要移除成员,可以使用organization.removeMember

auth-client.ts
//移除成员
await authClient.organization.removeMember({
    memberIdOrEmail: "member-id", // 这也可以是成员的电子邮件
    organizationId: "organization-id" // 可选,默认将使用活跃组织
})

更新成员角色

要更新组织中成员的角色,可以使用organization.updateMemberRole。如果用户有更新成员角色的权限,角色将被更新。

auth-client.ts
await authClient.organization.updateMemberRole({
    memberId: "member-id",
    role: "admin" // 这也可以是多个角色的数组(例如 ["admin", "sale"])
})

获取活跃成员

要获取组织的当前成员,可以使用organization.getActiveMember函数。此函数将返回当前活跃成员。

auth-client.ts
const member = await authClient.organization.getActiveMember()

添加成员

如果您想直接将成员添加到组织而不发送邀请,可以使用只能在服务器上调用的addMember函数。

api.ts
import { auth } from "@/auth";
 
auth.api.addMember({
  body: {
      userId: "user-id",
      organizationId: "organization-id",
      role: "admin", // 这也可以是多个角色的数组(例如 ["admin", "sale"])
      teamId: "team-id" // 可选指定teamId将成员添加到团队。(需要启用teams)
  }
})

退出组织

要退出组织,可以使用organization.leave函数。此函数将从组织中移除当前用户。

auth-client.ts
await authClient.organization.leave({
    organizationId: "organization-id"
})

访问控制

组织插件提供了非常灵活的访问控制系统。您可以根据用户在组织中的角色控制用户的访问权限。您可以根据用户的角色定义自己的权限集。

角色

默认情况下,组织中有三个角色:

owner:默认情况下创建组织的用户。所有者对组织拥有完全控制权,可以执行任何操作。

admin:拥有admin角色的用户对组织拥有完全控制权,但不能删除组织或更改所有者。

member:拥有member角色的用户对组织有有限的控制权。他们可以创建项目、邀请用户和管理他们创建的项目。

用户可以拥有多个角色。多个角色存储为以逗号(",")分隔的字符串。

权限

默认情况下,有三个资源,这些资源有两到三个操作。

organization:

update delete

member:

create update delete

invitation:

create cancel

所有者对所有资源和操作有完全控制权。管理员对所有资源有完全控制权,但不能删除组织或更改所有者。成员除了读取数据外,对这些操作没有任何控制权。

自定义权限

该插件提供了一种简便的方法来为每个角色定义自己的权限集。

创建访问控制

您首先需要通过调用createAccessControl函数并传递语句对象来创建访问控制器。语句对象应该以资源名称为键,以操作数组为值。

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";
 
/**
 * 确保使用`as const`以便typescript可以正确推断类型
 */
const statement = { 
    project: ["create", "share", "update", "delete"], 
} as const; 
 
const ac = createAccessControl(statement); 

创建角色

一旦创建了访问控制器,您可以使用已定义的权限创建角色。

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";
 
const statement = {
    project: ["create", "share", "update", "delete"],
} as const;
 
const ac = createAccessControl(statement);
 
const member = ac.newRole({ 
    project: ["create"], 
}); 
 
const admin = ac.newRole({ 
    project: ["create", "update"], 
}); 
 
const owner = ac.newRole({ 
    project: ["create", "update", "delete"], 
}); 
 
const myCustomRole = ac.newRole({ 
    project: ["create", "update", "delete"], 
    organization: ["update"], 
}); 

当您为现有角色创建自定义角色时,那些角色的预定义权限将被覆盖。要将现有权限添加到自定义角色,您需要导入defaultStatements并将其与您的新语句合并,同时将角色的权限集与默认角色合并。

permissions.ts
import { createAccessControl } from "better-auth/plugins/access";
import { defaultStatements, adminAc } from 'better-auth/plugins/organization/access'
 
const statement = {
    ...defaultStatements, 
    project: ["create", "share", "update", "delete"],
} as const;
 
const ac = createAccessControl(statement);
 
const admin = ac.newRole({
    project: ["create", "update"],
    ...adminAc.statements, 
});

将角色传递给插件

一旦创建了角色,您可以将它们传递给服务器和客户端上的组织插件。

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { ac, owner, admin, member } from "@/auth/permissions"
 
export const auth = betterAuth({
    plugins: [
        organization({
            ac,
            roles: {
                owner,
                admin,
                member,
                myCustomRole
            }
        }),
    ],
});

您还需要将访问控制器和角色传递给客户端插件。

auth-client
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
import { ac, owner, admin, member, myCustomRole } from "@/auth/permissions"
 
export const client = createAuthClient({
    plugins: [
        organizationClient({
            ac,
            roles: {
                owner,
                admin,
                member,
                myCustomRole
            }
        })
  ]
})

访问控制使用

检查权限:

您可以使用api提供的hasPermission操作来检查用户的权限。

api.ts
import { auth } from "@/auth";
auth.api.hasPermission({
  headers: await headers(),
    body: {
      permissions: {
        project: ["create"] // 这必须匹配您的访问控制中的结构
      }
    }
});
 
// 您也可以同时检查多个资源权限
auth.api.hasPermission({
  headers: await headers(),
    body: {
      permissions: {
        project: ["create"], // 这必须匹配您的访问控制中的结构
        sale: ["create"]
      }
    }
});

如果您想从服务器检查客户端上的用户权限,可以使用客户端提供的hasPermission函数。

auth-client.ts
const canCreateProject = await authClient.organization.hasPermission({
    permissions: {
        project: ["create"]
    }
})
 
// 您也可以同时检查多个资源权限
const canCreateProjectAndCreateSale = await authClient.organization.hasPermission({
    permissions: {
        project: ["create"],
        sale: ["create"]
    }
})

检查角色权限:

一旦定义了角色和权限,为了避免从服务器检查权限,您可以使用客户端提供的checkRolePermission函数。

auth-client.ts
const canCreateProject = client.organization.checkRolePermission({
	permissions: {
		organization: ["delete"],
	},
	role: "admin",
});
 
// 您也可以同时检查多个资源权限
const canCreateProjectAndCreateSale = client.organization.checkRolePermission({
	permissions: {
		organization: ["delete"],
    member: ["delete"]
	},
	role: "admin",
});

团队

团队允许您在组织内分组成员。团队功能提供了额外的组织结构,可用于在更精细的级别管理权限。

启用团队

要启用团队,请将teams配置选项传递给服务器和客户端插件:

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
 
export const auth = betterAuth({
    plugins: [
        organization({
            teams: {
                enabled: true,
                maximumTeams: 10, // 可选:限制每个组织的团队数量
                allowRemovingAllTeams: false // 可选:防止移除最后一个团队
            }
        })
    ]
})
auth-client.ts
import { createAuthClient } from "better-auth/client"
import { organizationClient } from "better-auth/client/plugins"
 
export const client = createAuthClient({
    plugins: [
        organizationClient({
            teams: {
                enabled: true
            }
        })
    ]
})

管理团队

创建团队

在组织内创建新团队:

const team = await client.organization.createTeam({
    name: "Development Team",
    organizationId: "org-id" // 可选:默认为活跃组织
})

列出团队

获取组织中的所有团队:

const teams = await authClient.organization.listTeams({
  query: {
    organizationId: org.id, // 可选:默认为活跃组织
  },
});

更新团队

更新团队的详细信息:

const updatedTeam = await client.organization.updateTeam({
    teamId: "team-id",
    data: {
        name: "Updated Team Name"
    }
})

移除团队

从组织中删除团队:

await client.organization.removeTeam({
    teamId: "team-id",
    organizationId: "org-id" // 可选:默认为活跃组织
})

团队权限

团队遵循组织的权限系统。要管理团队,用户需要以下权限:

  • team:create - 创建新团队
  • team:update - 更新团队详细信息
  • team:delete - 移除团队

默认情况下:

  • 组织所有者和管理员可以管理团队
  • 普通成员不能创建、更新或删除团队

团队配置选项

团队功能支持多种配置选项:

  • maximumTeams:限制每个组织的团队数量

    teams: {
      enabled: true,
      maximumTeams: 10 // 固定数字
      // 或者
      maximumTeams: async ({ organizationId, session }, request) => {
        // 基于组织计划的动态限制
        const plan = await getPlan(organizationId)
        return plan === 'pro' ? 20 : 5
      }
    }
  • allowRemovingAllTeams:控制是否可以移除最后一个团队

    teams: {
      enabled: true,
      allowRemovingAllTeams: false // 防止移除最后一个团队
    }

团队成员

在邀请成员加入组织时,您可以指定一个团队:

await client.organization.inviteMember({
    email: "[email protected]",
    role: "member",
    teamId: "team-id"
})

被邀请的成员在接受邀请后将被添加到指定的团队中。

数据库表

当启用团队功能时,会添加一个新的team表,结构如下:

Field NameTypeKeyDescription
idstring每个团队的唯一标识符
namestring-团队名称
organizationIdstring组织的ID
createdAtDate-团队创建时的时间戳
updatedAtDate-团队最后更新时的时间戳

Schema

组织插件向数据库添加以下表:

组织表

表名: organization

Field NameTypeKeyDescription
idstring每个组织的唯一标识符
namestring-组织名称
slugstring-组织的唯一标识符
logostring组织的logo
metadatastring组织的额外元数据
createdAtDate-组织创建时的时间戳

成员表

表名: member

Field NameTypeKeyDescription
idstring每个成员的唯一标识符
userIdstring用户的ID
organizationIdstring组织的ID
rolestring-用户在组织中的角色
createdAtDate-成员添加到组织时的时间戳

邀请表

表名: invitation

Field NameTypeKeyDescription
idstring每个邀请的唯一标识符
emailstring-用户的电子邮件地址
inviterIdstring邀请者的ID
organizationIdstring组织的ID
rolestring-用户在组织中的角色
statusstring-邀请的状态
expiresAtDate-邀请过期的时间戳
createdAtDate-邀请创建时的时间戳

会话表

表名: session

您需要在会话表中添加一个字段来存储活跃组织的ID。

Field NameTypeKeyDescription
activeOrganizationIdstring活跃组织的ID

团队表(可选)

表名: team

Field NameTypeKeyDescription
idstring每个团队的唯一标识符
namestring-团队名称
organizationIdstring组织的ID
createdAtDate-团队创建时的时间戳
updatedAtDate团队更新时的时间戳

表名: member

Field NameTypeKeyDescription
teamIdstring团队的ID

表名: invitation

Field NameTypeKeyDescription
teamIdstring团队的ID

自定义Schema

要更改表名或字段,您可以向组织插件传递schema选项。

auth.ts
const auth = betterAuth({
  plugins: [organization({
    schema: {
      organization: {
        modelName: "organizations",  //将organization表映射到organizations
        fields: {
          name: "title" //将name字段映射到title
        }
      }
    }
  })]
})

选项

allowUserToCreateOrganization: boolean | ((user: User) => Promise<boolean> | boolean) - 确定用户是否可以创建组织的函数。默认为true。您可以将其设置为false以限制用户创建组织。

organizationLimit: number | ((user: User) => Promise<boolean> | boolean) - 用户允许的最大组织数量。默认为5。您可以将其设置为任何数字或返回布尔值的函数。

creatorRole: admin | owner - 创建组织的用户的角色。默认为owner。您可以将其设置为admin

membershipLimit: number - 组织中允许的最大成员数量。默认为100。您可以将其设置为任何数字。

sendInvitationEmail: async (data) => Promise<void> - 向用户发送邀请电子邮件的函数。

invitationExpiresIn: number - 邀请链接的有效期(秒)。默认为48小时(2天)。

cancelPendingInvitationsOnReInvite: boolean - 如果用户已被邀请到组织,是否取消待处理的邀请。默认为true

invitationLimit: number | ((user: User) => Promise<boolean> | boolean) - 用户允许的最大邀请数量。默认为100。您可以将其设置为任何数字或返回布尔值的函数。