add rbac module

This commit is contained in:
liuyi 2025-06-26 15:51:03 +08:00
parent 99cb459fb7
commit 5de299c0c1
3 changed files with 118 additions and 0 deletions

View File

@ -0,0 +1,7 @@
import { SetMetadata } from '@nestjs/common';
import { PERMISSION_CHECKERS } from '../constants';
import { PermissionChecker } from '../types';
export const Permision = (...checkers: PermissionChecker[]) =>
SetMetadata(PERMISSION_CHECKERS, checkers);

View File

@ -0,0 +1,100 @@
import { createMongoAbility } from '@casl/ability';
import { ExecutionContext, ForbiddenException } from '@nestjs/common';
import { ModuleRef, Reflector } from '@nestjs/core';
import { isNil } from 'lodash';
import { JwtAuthGuard } from '@/modules/user/guards';
import { UserRepository } from '@/modules/user/repositories';
import { TokenService } from '@/modules/user/services';
import { PERMISSION_CHECKERS } from '../constants';
import { PermissionEntity } from '../entities/permission.entity';
import { RbacResolver } from '../rbac.resolver';
import { CheckerParams, PermissionChecker } from '../types';
export class RbacGuard extends JwtAuthGuard {
constructor(
protected reflector: Reflector,
protected resolver: RbacResolver,
protected tokenService: TokenService,
protected userRepository: UserRepository,
protected modeleRef: ModuleRef,
) {
super(reflector, tokenService);
}
async canActivate(context: ExecutionContext): Promise<boolean> {
const authCheck = await super.canActivate(context);
if (!authCheck) {
throw new ForbiddenException();
}
const checkers = this.reflector.getAllAndOverride<PermissionChecker[]>(
PERMISSION_CHECKERS,
[context.getHandler(), context.getClass()],
);
if (isNil(checkers) || checkers.length < 1) {
return true;
}
const result = await checkPermissions({
resolver: this.resolver,
repository: this.userRepository,
checkers,
moduleRef: this.modeleRef,
request: context.switchToHttp().getRequest(),
});
if (!result) {
throw new ForbiddenException();
}
return result;
}
}
/**
*
* @param CheckerParams
*/
export async function checkPermissions({
checkers,
moduleRef,
resolver,
repository,
request,
}: CheckerParams) {
if (isNil(request.user)) {
return false;
}
const user = await repository.findOneOrFail({
relations: ['roles.permissions', 'permissions'],
where: { id: request.user.id },
});
let permissions = user.permissions as PermissionEntity[];
for (const role of user.roles) {
permissions = [...permissions, ...role.permissions];
}
permissions = permissions.reduce((o, n) => {
if (o.find(({ name }) => name === n.name)) {
return o;
}
return [...o, n];
}, []);
const ability = createMongoAbility(
permissions.map(({ rule, name }) => {
const resolve = resolver.permissions.find((p) => p.name === name);
if (!isNil(resolve) && !isNil(resolve.rule.conditions)) {
return { ...rule, conditions: resolve.rule.conditions(user) };
}
return rule;
}),
);
const results = await Promise.all(
checkers.map(async (checker) => checker(ability, moduleRef, request)),
);
return results.every((r) => !!r);
}

View File

@ -7,8 +7,11 @@ import { FastifyRequest as Request } from 'fastify';
import { UserEntity } from '../user/entities'; import { UserEntity } from '../user/entities';
import { UserRepository } from '../user/repositories';
import { PermissionEntity } from './entities/permission.entity'; import { PermissionEntity } from './entities/permission.entity';
import { RoleEntity } from './entities/role.entity'; import { RoleEntity } from './entities/role.entity';
import { RbacResolver } from './rbac.resolver';
export type Role = Pick<ClassToPlain<RoleEntity>, 'name' | 'label' | 'description'> & { export type Role = Pick<ClassToPlain<RoleEntity>, 'name' | 'label' | 'description'> & {
permissions: string[]; permissions: string[];
@ -29,3 +32,11 @@ export type PermissionChecker = (
ref?: ModuleRef, ref?: ModuleRef,
request?: Request, request?: Request,
) => Promise<boolean>; ) => Promise<boolean>;
export type CheckerParams = {
resolver: RbacResolver;
repository: UserRepository;
checkers: PermissionChecker[];
moduleRef?: ModuleRef;
request?: any;
};