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