add rbac module
This commit is contained in:
parent
99cb459fb7
commit
5de299c0c1
7
src/modules/rbac/decorators/permission.decorator.ts
Normal file
7
src/modules/rbac/decorators/permission.decorator.ts
Normal 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);
|
100
src/modules/rbac/guards/rbac.guard.ts
Normal file
100
src/modules/rbac/guards/rbac.guard.ts
Normal 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);
|
||||||
|
}
|
@ -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;
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user