01MVP 标识01MVP
包文档认证与配置Permissions

Permissions

基于 CASL 的角色权限控制系统(RBAC)

@mono/permissions

基于 CASL 的角色权限控制系统(RBAC),提供类型安全的权限检查和资源级别的访问控制。

特性

  • 类型安全 - 完整的 TypeScript 类型支持
  • 角色管理 - 基于角色的访问控制(RBAC)
  • 资源级权限 - 支持字段级和资源级权限检查
  • 易于扩展 - 轻松添加新角色和资源类型
  • CASL 集成 - 基于强大的 CASL 权限库

安装

pnpm add @mono/permissions

快速开始

import { can, Role, Action, Subject } from '@mono/permissions';
import type { AppUser } from '@mono/permissions';

const user: AppUser = {
  id: '123',
  role: Role.USER
};

// 检查权限
if (can(user, Action.CREATE, Subject.PROJECT)) {
  // 用户可以创建项目
}

if (can(user, Action.UPDATE, Subject.USER, { id: user.id })) {
  // 用户可以更新自己的资料
}

API 参考

角色(Roles)

  • Role.USER - 普通用户
  • Role.ADMIN - 管理员,拥有完全访问权限

操作(Actions)

  • Action.CREATE - 创建资源
  • Action.READ - 读取资源
  • Action.UPDATE - 更新资源
  • Action.DELETE - 删除资源
  • Action.MANAGE - 所有操作

资源(Subjects)

  • Subject.USER - 用户资源
  • Subject.PROJECT - 项目资源
  • Subject.SETTINGS - 设置
  • Subject.ALL - 所有资源

函数

can(user, action, subject, data?)

检查用户是否可以对资源执行操作。

参数:

  • user: AppUser - 包含 id 和 role 的用户对象
  • action: Action - 要检查的操作
  • subject: Subject - 要检查的资源
  • data?: any - 可选的资源数据,用于字段级检查

返回: boolean

cannot(user, action, subject, data?)

can() 的反向操作。

getAbility(user)

获取 CASL ability 实例,用于高级用法。

权限规则

管理员(Admin)

  • 可以管理所有资源

普通用户(User)

  • 可以读取、创建、更新项目
  • 可以读取和更新自己的用户资料
  • 可以读取设置
  • 不能删除资源
  • 不能管理其他用户

实战示例

示例 1: API 路由权限检查

// app/api/projects/[id]/route.ts
import { can, Action, Subject } from '@mono/permissions';
import { auth } from '@mono/auth';
import { NextResponse } from 'next/server';

export async function DELETE(
  req: Request,
  { params }: { params: { id: string } }
) {
  // 获取当前用户
  const session = await auth.api.getSession({ headers: req.headers });

  if (!session?.user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // 检查删除权限
  if (!can(session.user, Action.DELETE, Subject.PROJECT)) {
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
  }

  // 删除项目
  await deleteProject(params.id);

  return NextResponse.json({ success: true });
}

export async function PATCH(
  req: Request,
  { params }: { params: { id: string } }
) {
  const session = await auth.api.getSession({ headers: req.headers });

  if (!session?.user) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }

  // 检查更新权限
  if (!can(session.user, Action.UPDATE, Subject.PROJECT)) {
    return NextResponse.json({ error: 'Forbidden' }, { status: 403 });
  }

  const body = await req.json();
  await updateProject(params.id, body);

  return NextResponse.json({ success: true });
}

示例 2: 组件中的权限控制

'use client';

import { can, Action, Subject } from '@mono/permissions';
import { useSession } from '@mono/auth/react';
import { Button } from '@mono/ui';

export function ProjectActions({ projectId }: { projectId: string }) {
  const { data: session } = useSession();
  const user = session?.user;

  if (!user) return null;

  return (
    <div className="flex gap-2">
      {can(user, Action.UPDATE, Subject.PROJECT) && (
        <Button onClick={() => editProject(projectId)}>
          编辑项目
        </Button>
      )}

      {can(user, Action.DELETE, Subject.PROJECT) && (
        <Button
          variant="destructive"
          onClick={() => deleteProject(projectId)}
        >
          删除项目
        </Button>
      )}
    </div>
  );
}

示例 3: 资源所有权检查

import { can, Action, Subject } from '@mono/permissions';
import type { AppUser } from '@mono/permissions';

interface Project {
  id: string;
  ownerId: string;
  name: string;
}

export function canEditProject(user: AppUser, project: Project): boolean {
  // 管理员可以编辑所有项目
  if (can(user, Action.MANAGE, Subject.ALL)) {
    return true;
  }

  // 用户只能编辑自己的项目
  if (can(user, Action.UPDATE, Subject.PROJECT) && project.ownerId === user.id) {
    return true;
  }

  return false;
}

// 使用示例
const user: AppUser = { id: '123', role: Role.USER };
const project: Project = { id: '1', ownerId: '123', name: 'My Project' };

if (canEditProject(user, project)) {
  // 允许编辑
}

示例 4: 服务端组件权限检查

// app/admin/page.tsx
import { redirect } from 'next/navigation';
import { auth } from '@mono/auth';
import { can, Action, Subject } from '@mono/permissions';

export default async function AdminPage() {
  const session = await auth.api.getSession();

  if (!session?.user) {
    redirect('/login');
  }

  // 检查管理员权限
  if (!can(session.user, Action.MANAGE, Subject.ALL)) {
    redirect('/');
  }

  return (
    <div>
      <h1>管理员面板</h1>
      {/* 管理员内容 */}
    </div>
  );
}

示例 5: 中间件权限检查

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { auth } from '@mono/auth';
import { can, Action, Subject } from '@mono/permissions';

export async function middleware(request: NextRequest) {
  const pathname = request.nextUrl.pathname;

  // 管理员路由保护
  if (pathname.startsWith('/admin')) {
    const session = await auth.api.getSession({
      headers: request.headers
    });

    if (!session?.user) {
      return NextResponse.redirect(new URL('/login', request.url));
    }

    if (!can(session.user, Action.MANAGE, Subject.ALL)) {
      return NextResponse.redirect(new URL('/', request.url));
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/admin/:path*']
};

示例 6: 自定义权限钩子

'use client';

import { useMemo } from 'react';
import { useSession } from '@mono/auth/react';
import { can, Action, Subject } from '@mono/permissions';
import type { AppUser } from '@mono/permissions';

export function usePermissions() {
  const { data: session } = useSession();
  const user = session?.user;

  const permissions = useMemo(() => {
    if (!user) {
      return {
        canCreateProject: false,
        canUpdateProject: false,
        canDeleteProject: false,
        canManageUsers: false,
        isAdmin: false,
      };
    }

    return {
      canCreateProject: can(user, Action.CREATE, Subject.PROJECT),
      canUpdateProject: can(user, Action.UPDATE, Subject.PROJECT),
      canDeleteProject: can(user, Action.DELETE, Subject.PROJECT),
      canManageUsers: can(user, Action.MANAGE, Subject.USER),
      isAdmin: can(user, Action.MANAGE, Subject.ALL),
    };
  }, [user]);

  return permissions;
}

// 使用示例
export function ProjectList() {
  const { canCreateProject, canDeleteProject } = usePermissions();

  return (
    <div>
      {canCreateProject && (
        <Button>创建新项目</Button>
      )}

      {/* 项目列表 */}
      {canDeleteProject && (
        <Button variant="destructive">删除</Button>
      )}
    </div>
  );
}

集成指南

1. 定义用户类型

import type { AppUser } from '@mono/permissions';
import { Role } from '@mono/permissions';

// 将数据库用户转换为 AppUser
function toAppUser(dbUser: any): AppUser {
  return {
    id: dbUser.id,
    role: dbUser.role === 'admin' ? Role.ADMIN : Role.USER
  };
}

2. 扩展角色和资源

编辑 src/types.ts 添加新角色或资源:

export enum Role {
  USER = 'USER',
  ADMIN = 'ADMIN',
  MODERATOR = 'MODERATOR', // 新角色
}

export enum Subject {
  USER = 'User',
  PROJECT = 'Project',
  COMMENT = 'Comment', // 新资源
  ALL = 'all',
}

然后更新 src/abilities.ts 定义权限:

if (user.role === Role.MODERATOR) {
  can(Action.READ, Subject.ALL);
  can(Action.UPDATE, Subject.COMMENT);
  can(Action.DELETE, Subject.COMMENT);
}

3. 与认证系统集成

// lib/auth/session.ts
import { auth } from '@mono/auth';
import { Role } from '@mono/permissions';
import type { AppUser } from '@mono/permissions';

export async function getCurrentUser(): Promise<AppUser | null> {
  const session = await auth.api.getSession();

  if (!session?.user) {
    return null;
  }

  return {
    id: session.user.id,
    role: session.user.role === 'admin' ? Role.ADMIN : Role.USER
  };
}

最佳实践

1. 集中权限检查

创建权限检查辅助函数:

// lib/permissions.ts
import { can, Action, Subject } from '@mono/permissions';
import type { AppUser } from '@mono/permissions';

export const permissions = {
  canManageProject: (user: AppUser, projectOwnerId: string) => {
    return can(user, Action.MANAGE, Subject.ALL) ||
           (can(user, Action.UPDATE, Subject.PROJECT) && user.id === projectOwnerId);
  },

  canDeleteUser: (user: AppUser, targetUserId: string) => {
    return can(user, Action.MANAGE, Subject.USER) && user.id !== targetUserId;
  },
};

2. 错误处理

统一处理权限错误:

export class ForbiddenError extends Error {
  constructor(message = 'Forbidden') {
    super(message);
    this.name = 'ForbiddenError';
  }
}

export function requirePermission(
  user: AppUser,
  action: Action,
  subject: Subject
) {
  if (!can(user, action, subject)) {
    throw new ForbiddenError(`Cannot ${action} ${subject}`);
  }
}

3. 类型安全

利用 TypeScript 类型系统:

import type { AppUser } from '@mono/permissions';
import { Role } from '@mono/permissions';

function isAdmin(user: AppUser): user is AppUser & { role: Role.ADMIN } {
  return user.role === Role.ADMIN;
}

// 使用类型守卫
if (isAdmin(user)) {
  // TypeScript 知道 user.role 是 Role.ADMIN
}

故障排查

问题:权限检查总是返回 false

解决方案: 确保用户对象包含正确的 role 字段,并且角色值匹配 Role 枚举。

问题:无法访问受保护的路由

解决方案: 检查中间件配置和权限规则是否正确设置。

问题:类型错误

解决方案: 确保导入了正确的类型:import type { AppUser } from '@mono/permissions'

相关文档

许可证

MIT