mcpField
S
supabase-enterprise-rbac

supabase-enterprise-rbac

github.com/jeremylongshore安全2.9k40 次安装2 天前更新
代码仓库

关于 supabase-enterprise-rbac

面向构建多角色和多租户 Supabase 应用的技能,提供从 JWT 角色声明到数据库与 API 层校验的 RBAC 实现流程。

主要功能

  1. 01通过 app_metadata 分配角色
  2. 02基于角色层级编写 RLS 策略
  3. 03按组织隔离数据访问
  4. 04使用 API 中间件校验角色

适用场景

  1. 01实现管理员、编辑者和查看者权限
  2. 02隔离不同组织的项目数据
  3. 03保护 API 路由和管理操作

安装

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

$npx skills add https://github.com/jeremylongshore/tons-of-skills-marketplace --skill supabase-enterprise-rbac

安装后在 Claude Code 中输入/supabase-enterprise-rbac确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
api-scoping-and-enforcement.md
errors.md
examples.md
rls-policies.md
role-assignment.md
role-implementation.md
sso-integration.md

详细介绍

Supabase 企业级 RBAC

概述

Supabase 支持自定义基于角色的访问控制(RBAC):将角色信息存储在用户 JWT 的 app_metadata 中,然后在 RLS 策略中通过 auth.jwt() ->> 'role' 读取这些声明。本技能实现了一套完整的 RBAC 系统:在 app_metadata 中存储角色、通过 RLS 策略强制执行角色层级、按组织范围限制访问、通过 Admin API 管理角色,以及使用角色检查保护 API 端点——所有操作均使用来自 @supabase/supabase-js 的真实 createClient。

适用场景: 构建包含多种角色(管理员/编辑者/查看者)的应用、实现组织范围内的访问控制、创建超出 Supabase 内置 anon/authenticated 角色能力范围的自定义权限系统,或按用户角色限制 API 操作范围。

前置条件

  • @supabase/supabase-js v2+,以及用于管理操作的服务角色密钥
  • 了解 JWT 声明和 Supabase 的 auth.jwt() SQL 函数
  • 可通过 SQL Editor 或 psql 访问数据库,以创建 RLS 策略
  • 已配置身份验证的 Supabase 项目

使用说明

工作流分为三个步骤:将角色分配到 JWT 中、通过 RLS 在数据库中强制执行角色权限,然后在应用代码中再次强制执行。

第 1 步:通过 app_metadata 和 JWT 声明定义角色

使用 Admin API 将自定义角色存储在用户的 app_metadata 中。这些声明会出现在用户收到的每个 JWT 中,并且可在 RLS 策略中读取。使用服务角色客户端分配角色:

// Define the role hierarchy
type AppRole = 'admin' | 'editor' | 'viewer' | 'member';

// Assign a role to a user (admin operation, service role key required)
async function setUserRole(userId: string, role: AppRole, orgId: string) {
  const { data, error } = await supabase.auth.admin.updateUserById(userId, {
    app_metadata: { role, org_id: orgId },
  });
  if (error) throw new Error(`Failed to set role: ${error.message}`);
  return data.user;
}

在应用代码中使用 supabase.auth.getUser() 读取角色,并将其与数字层级进行比较,以便 hasRole('editor', 'viewer') 返回 true。有关完整的服务角色客户端设置、细粒度权限、批量 assignTeamRoles()、getCurrentUserRole()、getCurrentOrg()、hasRole() 和 requireRole() 守卫,请参阅角色分配和 JWT 提取。

第 2 步:使用 JWT 角色声明编写 RLS 策略

编写行级安全(RLS)策略,以读取 auth.jwt() -> 'app_metadata' ->> 'role' 和 ... ->> 'org_id'。将 JWT 提取逻辑封装在辅助函数中,让每个策略都保持清晰易读:

CREATE OR REPLACE FUNCTION public.get_user_role()
RETURNS text AS $$
  SELECT coalesce(auth.jwt() -> 'app_metadata' ->> 'role', 'viewer');
$$ LANGUAGE sql STABLE SECURITY DEFINER;

CREATE POLICY "editors_create_projects" ON public.projects
  FOR INSERT WITH CHECK (
    org_id = get_user_org_id()
    AND get_user_role() IN ('admin', 'editor')
  );

每项操作(SELECT/INSERT/UPDATE/DELETE)的策略都会将 org_id = get_user_org_id() 租户检查与 get_user_role() 角色检查结合起来。有关这两个辅助函数、适用于 projects/documents/team_members 的完整策略集,以及包含索引的 organizations/team_members/projects 表结构,请参阅 RLS 策略和组织范围内的架构。

第 3 步:API 密钥范围限制和应用代码中的角色强制执行

在应用层再次强制执行角色权限,确保 API 路由绝不只依赖 RLS。有关服务端 withRole() 中间件、按请求创建客户端、管理面板操作(列出成员、邀请用户、更改角色)以及组织管理模式,请参阅 API 密钥范围限制和角色强制执行。

输出

完成本技能后,将实现以下功能:

  • 通过 app_metadata 分配角色 — admin.updateUserById() 在用户 JWT 中设置角色声明
  • 提取 JWT 声明 — get_user_role() 和 get_user_org_id() SQL 辅助函数
  • 基于角色的 RLS 策略 — 按角色层级(管理员 > 编辑者 > 成员 > 查看者)限制 SELECT/INSERT/UPDATE/DELETE 操作
  • 组织范围内的访问控制 — 通过 JWT 声明和 RLS 策略中的 org_id 实现多租户隔离
  • 应用层强制执行 — 用于 API 路由的 withRole() 中间件,并正确返回 401/403 响应
  • 管理面板操作 — 列出成员、邀请用户、更改角色,同时更新数据库和 JWT
  • 角色层级检查 — 支持比较角色权限等级的 hasRole() 函数

错误处理

错误原因解决方案
JWT 中的 app_metadata.role 为 null未设置角色,或用户需要重新登录调用 admin.updateUserById() 设置角色;用户必须刷新会话
RLS 策略返回空结果JWT 声明与策略条件不匹配在 SQL Editor 中检查 auth.jwt() 的输出;确认 app_metadata 设置正确
permission denied for function未创建辅助函数,或架构不正确在 public 架构中使用 SECURITY DEFINER 创建 get_user_role()
用户角色变更未生效JWT 缓存了旧声明用户必须退出登录后重新登录,或调用 supabase.auth.refreshSession()
duplicate key value violates unique constraint用户已在组织中插入前检查 team_members 表中是否已有对应条目
team_members 上出现 foreign key violation用户或组织不存在插入成员关系前,确认 user_id 和 org_id 均存在
绕过角色层级使用服务角色直接访问数据库服务角色按设计会绕过 RLS——仅限在服务端管理操作中使用

示例

示例 1 — 在组件中快速检查角色:

async function canEditProject(): Promise<boolean> {
  const { data: { user } } = await supabase.auth.getUser();
  const role = user?.app_metadata?.role;
  return role === 'admin' || role === 'editor';
}

示例 2 — 验证 RLS 策略是否正常工作:

-- Test as an editor in org-123
SET request.jwt.claims = '{"sub": "user-uuid", "role": "authenticated", "app_metadata": {"role": "editor", "org_id": "org-123"}}';

SELECT * FROM projects;                                                    -- returns only org-123 rows
INSERT INTO projects (org_id, name, created_by) VALUES ('org-123', 'Test', 'user-uuid');  -- succeeds
DELETE FROM projects WHERE id = 'some-project-id';                          -- fails (editors cannot delete)

RESET request.jwt.claims;

完整的入门示例(onboardOrganization()——创建组织、将创建者设为管理员、填充 team_members),请参阅角色分配参考和 API 范围限定参考。

资源

后续步骤

  • 数据库迁移模式请参阅 supabase-migration-deep-dive
  • 安全加固和 API 密钥范围限定请参阅 supabase-security-basics
  • 数据处理和 GDPR 合规请参阅 supabase-data-handling

评论

支持 Markdown