mcpField
B
better-auth-organization-best-practices

better-auth-organization-best-practices

github.com/pedronauck安全644238 次安装1 周前更新
代码仓库

关于 better-auth-organization-best-practices

面向使用 Better Auth 组织插件的开发者,介绍组织、成员、邀请、团队和动态权限的配置与管理,并说明所有权及删除等安全注意事项。

主要功能

  1. 01配置组织插件与客户端
  2. 02管理组织、成员和邀请
  3. 03设置角色与权限
  4. 04启用团队及成员管理
  5. 05自定义架构与生命周期钩子

适用场景

  1. 01为用户创建或限制组织
  2. 02邀请成员并分配角色
  3. 03管理组织团队和团队成员
  4. 04设计动态权限与组织删除保护

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/pedronauck/skills --skill better-auth-organization-best-practices

安装后在 Claude Code 中输入/better-auth-organization-best-practices确认已加载。

详细介绍

设置

  1. 将 organization() 插件添加到服务端配置中
  2. 将 organizationClient() 插件添加到客户端配置中
  3. 运行 npx @better-auth/cli migrate
  4. 验证:检查数据库中是否存在 organization、member、invitation 表
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      allowUserToCreateOrganization: true,
      organizationLimit: 5, // Max orgs per user
      membershipLimit: 100, // Max members per org
    }),
  ],
});
客户端设置
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";

export const authClient = createAuthClient({
  plugins: [organizationClient()],
});

创建组织

创建者会自动被分配 owner 角色。

const createOrg = async () => {
  const { data, error } = await authClient.organization.create({
    name: "My Company",
    slug: "my-company",
    logo: "https://example.com/logo.png",
    metadata: { plan: "pro" },
  });
};
控制组织创建

根据用户属性限制谁可以创建组织:

organization({
  allowUserToCreateOrganization: async (user) => {
    return user.emailVerified === true;
  },
  organizationLimit: async (user) => {
    // Premium users get more organizations
    return user.plan === "premium" ? 20 : 3;
  },
});
代表用户创建组织

管理员可以为其他用户创建组织(仅限服务端):

await auth.api.createOrganization({
  body: {
    name: "Client Organization",
    slug: "client-org",
    userId: "user-id-who-will-be-owner", // `userId` is required
  },
});

注意:userId 参数不能与会话标头同时使用。

活跃组织

存储在会话中,并限定后续 API 调用的范围。用户选择组织后设置。

const setActive = async (organizationId: string) => {
  const { data, error } = await authClient.organization.setActive({
    organizationId,
  });
};

如果未提供 organizationId,许多端点会使用活跃组织(listMembers、listInvitations、inviteMember 等)。

使用 getFullOrganization() 获取包含所有成员、邀请和团队的活跃组织。

成员

添加成员(服务端)
await auth.api.addMember({
  body: {
    userId: "user-id",
    role: "member",
    organizationId: "org-id",
  },
});

要在客户端添加成员,请改用邀请系统。

分配多个角色
await auth.api.addMember({
  body: {
    userId: "user-id",
    role: ["admin", "moderator"],
    organizationId: "org-id",
  },
});
移除成员

使用 removeMember({ memberIdOrEmail })。无法移除最后一位所有者——请先将所有权转让给其他成员。

更新成员角色

使用 updateMemberRole({ memberId, role })。

成员数量限制
organization({
  membershipLimit: async (user, organization) => {
    if (organization.metadata?.plan === "enterprise") {
      return 1000;
    }
    return 50;
  },
});

邀请

设置邀请邮件
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    organization({
      sendInvitationEmail: async (data) => {
        const { email, organization, inviter, invitation } = data;

        await sendEmail({
          to: email,
          subject: `Join ${organization.name}`,
          html: `
            <p>${inviter.user.name} invited you to join ${organization.name}</p>
            <a href="https://yourapp.com/accept-invite?id=${invitation.id}">
              Accept Invitation
            </a>
          `,
        });
      },
    }),
  ],
});
发送邀请
await authClient.organization.inviteMember({
  email: "[email protected]",
  role: "member",
});
可分享的邀请 URL
const { data } = await authClient.organization.getInvitationURL({
  email: "[email protected]",
  role: "member",
  callbackURL: "https://yourapp.com/dashboard",
});

// Share data.url via any channel

此端点不会调用 sendInvitationEmail——请自行处理发送。

邀请配置
organization({
  invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days (default: 48 hours)
  invitationLimit: 100, // Max pending invitations per org
  cancelPendingInvitationsOnReInvite: true, // Cancel old invites when re-inviting
});

角色与权限

默认角色:owner(完全访问权限)、admin(管理成员/邀请/设置)、member(基本访问权限)。

检查权限
const { data } = await authClient.organization.hasPermission({
  permission: "member:write",
});

if (data?.hasPermission) {
  // User can manage members
}

客户端 UI 渲染可使用 checkRolePermission({ role, permissions })(仅限静态检查)。动态访问控制请使用 hasPermission 端点。

团队

启用团队
import { organization } from "better-auth/plugins";

export const auth = betterAuth({
  plugins: [
    organization({
      teams: {
        enabled: true,
      },
    }),
  ],
});
创建团队
const { data } = await authClient.organization.createTeam({
  name: "Engineering",
});
管理团队成员

使用 addTeamMember({ teamId, userId })(成员必须先加入组织)和 removeTeamMember({ teamId, userId })(仍保留在组织中)。

使用 setActiveTeam({ teamId }) 设置活跃团队。

团队数量限制
organization({
  teams: {
    maximumTeams: 20, // Max teams per org
    maximumMembersPerTeam: 50, // Max members per team
    allowRemovingAllTeams: false, // Prevent removing last team
  },
});

动态访问控制

启用动态访问控制
import { organization } from "better-auth/plugins";
import { dynamicAccessControl } from "@better-auth/organization/addons";

export const auth = betterAuth({
  plugins: [
    organization({
      dynamicAccessControl: {
        enabled: true,
      },
    }),
  ],
});
创建自定义角色
await authClient.organization.createRole({
  role: "moderator",
  permission: {
    member: ["read"],
    invitation: ["read"],
  },
});

使用 updateRole({ roleId, permission }) 和 deleteRole({ roleId })。无法删除预定义角色(owner、admin、member)。在成员重新分配角色之前,无法删除已分配给成员的角色。

生命周期钩子

在组织生命周期的不同阶段执行自定义逻辑:

organization({
  hooks: {
    organization: {
      beforeCreate: async ({ data, user }) => {
        // Validate or modify data before creation
        return {
          data: {
            ...data,
            metadata: { ...data.metadata, createdBy: user.id },
          },
        };
      },
      afterCreate: async ({ organization, member }) => {
        // Post-creation logic (e.g., send welcome email, create default resources)
        await createDefaultResources(organization.id);
      },
      beforeDelete: async ({ organization }) => {
        // Cleanup before deletion
        await archiveOrganizationData(organization.id);
      },
    },
    member: {
      afterCreate: async ({ member, organization }) => {
        await notifyAdmins(organization.id, `New member joined`);
      },
    },
    invitation: {
      afterCreate: async ({ invitation, organization, inviter }) => {
        await logInvitation(invitation);
      },
    },
  },
});

架构自定义

自定义表名和字段名,并添加其他字段:

organization({
  schema: {
    organization: {
      modelName: "workspace", // Rename table
      fields: {
        name: "workspaceName", // Rename fields
      },
      additionalFields: {
        billingId: {
          type: "string",
          required: false,
        },
      },
    },
    member: {
      additionalFields: {
        department: {
          type: "string",
          required: false,
        },
        title: {
          type: "string",
          required: false,
        },
      },
    },
  },
});

安全注意事项

所有者保护
  • 无法从组织中移除最后一位所有者
  • 最后一位所有者无法退出组织
  • 无法移除最后一位所有者的 owner 角色

移除当前所有者之前,务必确保所有权已转让:

// Transfer ownership first
await authClient.organization.updateMemberRole({
  memberId: "new-owner-member-id",
  role: "owner",
});

// Then the previous owner can be demoted or removed
删除组织

删除组织会移除所有关联数据(成员、邀请、团队)。请防止意外删除:

organization({
  disableOrganizationDeletion: true, // Disable via config
});

或者通过钩子实现软删除:

organization({
  hooks: {
    organization: {
      beforeDelete: async ({ organization }) => {
        // Archive instead of delete
        await archiveOrganization(organization.id);
        throw new Error("Organization archived, not deleted");
      },
    },
  },
});
邀请安全
  • 邀请默认在 48 小时后过期
  • 只有受邀的电子邮件地址才能接受邀请
  • 组织管理员可以取消待处理的邀请

完整配置示例

import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
import { sendEmail } from "./email";

export const auth = betterAuth({
  plugins: [
    organization({
      // Organization limits
      allowUserToCreateOrganization: true,
      organizationLimit: 10,
      membershipLimit: 100,
      creatorRole: "owner",

      // Slugs
      defaultOrganizationIdField: "slug",

      // Invitations
      invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days
      invitationLimit: 50,
      sendInvitationEmail: async (data) => {
        await sendEmail({
          to: data.email,
          subject: `Join ${data.organization.name}`,
          html: `<a href="https://app.com/invite/${data.invitation.id}">Accept</a>`,
        });
      },

      // Hooks
      hooks: {
        organization: {
          afterCreate: async ({ organization }) => {
            console.log(`Organization ${organization.name} created`);
          },
        },
      },
    }),
  ],
});

评论

支持 Markdown