feat: RBAC

This commit is contained in:
2025-03-11 17:21:40 +08:00
parent 5058aefd66
commit 95de4f6484
8 changed files with 68 additions and 12 deletions

View File

@@ -0,0 +1,5 @@
import { SetMetadata } from '@nestjs/common';
export const PERMISSIONS_KEY = 'permissions';
export const Permissions = (...permissions: string[]) =>
SetMetadata(PERMISSIONS_KEY, permissions);

View File

@@ -1,6 +1,6 @@
export enum Role { export enum Role {
User = 'user',
Admin = 'admin', Admin = 'admin',
User = 'user',
} }
export enum RedisGroup { export enum RedisGroup {

View File

@@ -11,6 +11,9 @@ import { Reflector } from '@nestjs/core';
import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager'; import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager';
import { SKIP_AUTH } from '@/common/decorators/skip-auth.decorator'; import { SKIP_AUTH } from '@/common/decorators/skip-auth.decorator';
import { PERMISSIONS_KEY } from '@/common/decorators/permissions.decorator';
import { PrismaService } from '@/prisma/prisma.service';
import { PermissionType, Role } from '@/common/enum';
@Injectable() @Injectable()
export class PermissionGuard implements CanActivate { export class PermissionGuard implements CanActivate {
@@ -18,16 +21,58 @@ export class PermissionGuard implements CanActivate {
private readonly configService: ConfigService, private readonly configService: ConfigService,
private reflector: Reflector, private reflector: Reflector,
@Inject(CACHE_MANAGER) private cacheService: Cache, @Inject(CACHE_MANAGER) private cacheService: Cache,
private readonly prisma: PrismaService,
) {} ) {}
async canActivate(context: ExecutionContext): Promise<boolean> { async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest(); const request = context.switchToHttp().getRequest();
request['isAdmin'] = request?.user?.account === 'admin'; const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
return true; PERMISSIONS_KEY,
// if (request.isSkipAuth === SKIP_AUTH || request.user.account === 'admin') { [context.getHandler(), context.getClass()],
// return true; );
// }
// throw new ForbiddenException('无权访问!'); // 跳过验证
if (request.isSkipAuth === SKIP_AUTH) return true;
// 查找当前用户拥有的角色
const userRoles = await this.prisma.sysRole.findMany({
select: {
id: true,
code: true,
},
where: {
sysUserRole: {
some: {
userId: request.user.sub,
},
},
},
});
// 所有用户的code
const roleCodes = userRoles.map((item) => item.code);
const roleIds = userRoles.map((item) => item.id);
// 如果是管理员 直接通过验证不需要进行接口鉴权
if (roleCodes.includes(Role.Admin)) {
request['isAdmin'] = true;
return true;
}
console.log(roleIds);
// 已知 权限字符 通过权限字符找出权限id 再根据权限id 找出拥有权限的角色code
const hasPermission = await this.prisma.sysRolePermission.findMany({
where: {
roleId: {
in: roleIds,
},
},
});
// TODO
console.log(hasPermission);
// 判断当前接口是否存在权限
throw new ForbiddenException('无权访问!');
} }
} }

View File

@@ -111,7 +111,7 @@ export class MenuService {
}); });
//step2 删除菜单数据 //step2 删除菜单数据
await prisma.sysResourcePermission.delete({ where: { id } }); await prisma.sysResource.delete({ where: { id } });
}); });
} }
} }

View File

@@ -107,7 +107,7 @@ export class ResourceService {
}); });
//step2 删除资源数据 //step2 删除资源数据
await prisma.sysResourcePermission.delete({ where: { id } }); await prisma.sysResource.delete({ where: { id } });
}); });
} }
} }

View File

@@ -2,9 +2,11 @@ import { Controller, Get } from '@nestjs/common';
import { TestService } from './test.service'; import { TestService } from './test.service';
import { SkipAuth } from '@/common/decorators/skip-auth.decorator'; import { SkipAuth } from '@/common/decorators/skip-auth.decorator';
import { Permissions } from '@/common/decorators/permissions.decorator';
@Controller() @Controller()
@SkipAuth() // @SkipAuth()
@Permissions('test', 'test2')
export class TestController { export class TestController {
constructor(private readonly testService: TestService) {} constructor(private readonly testService: TestService) {}

View File

@@ -1,8 +1,11 @@
import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { PrismaService } from '@/prisma/prisma.service';
@Injectable() @Injectable()
export class TestService { export class TestService {
findAll() { constructor(private readonly prisma: PrismaService) {}
throw new HttpException('用户不存在!', HttpStatus.NOT_FOUND);
async findAll() {
return [];
} }
} }

View File

@@ -33,6 +33,7 @@ export class UserController {
getUserInfo(@Request() req) { getUserInfo(@Request() req) {
const { sub } = req['user']; const { sub } = req['user'];
const { isAdmin } = req; const { isAdmin } = req;
console.log(isAdmin);
return this.userService.getUserInfo(sub, isAdmin); return this.userService.getUserInfo(sub, isAdmin);
} }