Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ KIRAKIRA RBAC & 权限控制 #27

Draft
wants to merge 10 commits into
base: develop
Choose a base branch
from
25 changes: 20 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"koa-bodyparser": "^4.4.0",
"koa-router": "^12.0.0",
"mongoose": "^8.5.2",
"otplib": "^12.0.1"
"otplib": "^12.0.1",
"uuid": "^11.0.5"
}
}
19 changes: 19 additions & 0 deletions src/common/ObjectTool.ts
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
/**
* 判断一个对象是否为空
*/
export const isEmptyObject = (obj: object) => typeof obj === 'object' && !(Array.isArray(obj)) && Object.keys(obj).length === 0

/**
* 删除一个对象中值为 undefined 的元素,返回一个新对象
* 底层原理是(浅)拷贝所有不为 undefined 的元素到新对象
* @param obj 需要被清理的存在元素的值为 undefined 的对象
* @returns 清理了值为 undefined 的元素的对象
*/
export const clearUndefinedItemInObject = <T extends Record<string, any> >(obj: T): Partial<T> => {
const newObj: Partial<T> = {};
(Object.keys(obj) as (keyof T)[]).forEach(key => {
if (obj[key] !== undefined) {
newObj[key] = obj[key];
}
});
return newObj;
}
7 changes: 7 additions & 0 deletions src/controller/DanmakuController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { emitDanmakuService, getDanmakuListByKvidService } from '../service/DanmakuService.js'
import { isPassRbacCheck } from '../service/RbacService.js'
import { koaCtx, koaNext } from '../type/koaTypes.js'
import { EmitDanmakuRequestDto, GetDanmakuByKvidRequestDto } from './DanmakuControllerDto.js'

Expand All @@ -12,6 +13,12 @@ export const emitDanmakuController = async (ctx: koaCtx, next: koaNext) => {
const data = ctx.request.body as Partial<EmitDanmakuRequestDto>
const uid = parseInt(ctx.cookies.get('uid'), 10)
const token = ctx.cookies.get('token')

// RBAC 权限验证
if (!await isPassRbacCheck({ uid, apiPath: ctx.path }, ctx)) {
return
}

const emitDanmakuRequest: EmitDanmakuRequestDto = {
/** 非空 - KVID 视频 ID */
videoId: data.videoId,
Expand Down
250 changes: 250 additions & 0 deletions src/controller/RbacController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
import { isPassRbacCheck, createRbacApiPathService, createRbacRoleService, updateApiPathPermissionsForRoleService, getRbacApiPathService, deleteRbacApiPathService, deleteRbacRoleService, getRbacRoleService, adminGetUserRolesByUidService, adminUpdateUserRoleService } from '../service/RbacService.js'
import { koaCtx, koaNext } from '../type/koaTypes.js'
import { AdminGetUserRolesByUidRequestDto, AdminUpdateUserRoleRequestDto, CreateRbacApiPathRequestDto, CreateRbacRoleRequestDto, DeleteRbacApiPathRequestDto, DeleteRbacRoleRequestDto, GetRbacApiPathRequestDto, GetRbacRoleRequestDto, UpdateApiPathPermissionsForRoleRequestDto } from './RbacControllerDto.js'

/**
* 创建 RBAC API 路径
* @param ctx context
* @param next context
*/
export const createRbacApiPathController = async (ctx: koaCtx, next: koaNext) => {
const data = ctx.request.body as Partial<CreateRbacApiPathRequestDto>
const uuid = ctx.cookies.get('uuid') ?? ''
const token = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid, apiPath: ctx.path }, ctx)) {
return
}

const createRbacApiPathRequest: CreateRbacApiPathRequestDto = {
apiPath: data.apiPath ?? '',
apiPathType: data.apiPathType,
apiPathColor: data.apiPathColor,
apiPathDescription: data.apiPathDescription,
}
const createRbacApiPathResponse = await createRbacApiPathService(createRbacApiPathRequest, uuid, token)
ctx.body = createRbacApiPathResponse
await next()
}

/**
* 删除 RBAC API 路径
* @param ctx context
* @param next context
*/
export const deleteRbacApiPathController = async (ctx: koaCtx, next: koaNext) => {
const data = ctx.request.body as Partial<DeleteRbacApiPathRequestDto>
const uuid = ctx.cookies.get('uuid') ?? ''
const token = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid, apiPath: ctx.path }, ctx)) {
return
}

const deleteRbacApiPathRequest: DeleteRbacApiPathRequestDto = {
apiPath: data.apiPath ?? '',
}
const deleteRbacApiPathResponse = await deleteRbacApiPathService(deleteRbacApiPathRequest, uuid, token)
ctx.body = deleteRbacApiPathResponse
await next()
}

/**
* 获取 RBAC API 路径
* @param ctx context
* @param next context
*/
export const getRbacApiPathController = async (ctx: koaCtx, next: koaNext) => {
const apiPath = ctx.query.apiPath as string
const apiPathType = ctx.query.apiPathType as string
const apiPathColor = ctx.query.apiPathColor as string
const apiPathDescription = ctx.query.apiPathDescription as string
const page = parseInt(ctx.query.page as string, 10)
const pageSize = parseInt(ctx.query.pageSize as string, 10)

const uuid = ctx.cookies.get('uuid') ?? ''
const token = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid, apiPath: ctx.path }, ctx)) {
return
}

const getRbacApiPathRequest: GetRbacApiPathRequestDto = {
search: {
apiPath,
apiPathType,
apiPathColor,
apiPathDescription,
},
pagination: {
page,
pageSize,
},
}
const getRbacApiPathResponse = await getRbacApiPathService(getRbacApiPathRequest, uuid, token)
ctx.body = getRbacApiPathResponse
await next()
}

/**
* 创建 RBAC 角色
* @param ctx context
* @param next context
*/
export const createRbacRoleController = async (ctx: koaCtx, next: koaNext) => {
const data = ctx.request.body as Partial<CreateRbacRoleRequestDto>
const uuid = ctx.cookies.get('uuid') ?? ''
const token = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid, apiPath: ctx.path }, ctx)) {
return
}

const createRbacRoleRequest: CreateRbacRoleRequestDto = {
roleName: data.roleName ?? '',
roleType: data.roleType,
roleColor: data.roleColor,
roleDescription: data.roleDescription,
}
const createRbacRoleResponse = await createRbacRoleService(createRbacRoleRequest, uuid, token)
ctx.body = createRbacRoleResponse
await next()
}

/**
* 删除 RBAC 角色
* @param ctx context
* @param next context
*/
export const deleteRbacRoleController = async (ctx: koaCtx, next: koaNext) => {
const data = ctx.request.body as Partial<DeleteRbacRoleRequestDto>
const uuid = ctx.cookies.get('uuid') ?? ''
const token = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid, apiPath: ctx.path }, ctx)) {
return
}

const deleteRbacRoleRequest: DeleteRbacRoleRequestDto = {
roleName: data.roleName ?? '',
}
const deleteRbacRoleResponse = await deleteRbacRoleService(deleteRbacRoleRequest, uuid, token)
ctx.body = deleteRbacRoleResponse
await next()
}

/**
* 获取 RBAC 角色
* @param ctx context
* @param next context
*/
export const getRbacRoleController = async (ctx: koaCtx, next: koaNext) => {
const roleName = ctx.query.roleName as string
const roleType = ctx.query.roleType as string
const roleColor = ctx.query.roleColor as string
const roleDescription = ctx.query.roleDescription as string
const page = parseInt(ctx.query.page as string, 10)
const pageSize = parseInt(ctx.query.pageSize as string, 10)

const uuid = ctx.cookies.get('uuid') ?? ''
const token = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid, apiPath: ctx.path }, ctx)) {
return
}

const getRbacRoleRequest: GetRbacRoleRequestDto = {
search: {
roleName,
roleType,
roleColor,
roleDescription,
},
pagination: {
page,
pageSize,
},
}
const getRbacRoleResponse = await getRbacRoleService(getRbacRoleRequest, uuid, token)
ctx.body = getRbacRoleResponse
await next()
}

/**
* 为角色更新 API 路径权限
* @param ctx context
* @param next context
*/
export const updateApiPathPermissionsForRoleController = async (ctx: koaCtx, next: koaNext) => {
const data = ctx.request.body as Partial<UpdateApiPathPermissionsForRoleRequestDto>
const uuid = ctx.cookies.get('uuid') ?? ''
const token = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid, apiPath: ctx.path }, ctx)) {
return
}

const updateApiPathPermissionsForRoleRequest: UpdateApiPathPermissionsForRoleRequestDto = {
roleName: data.roleName ?? '',
apiPathPermissions: data.apiPathPermissions ?? []
}
const updateApiPathPermissionsForRoleResponse = await updateApiPathPermissionsForRoleService(updateApiPathPermissionsForRoleRequest, uuid, token)
ctx.body = updateApiPathPermissionsForRoleResponse
await next()
}

/**
* 管理员更新用户角色
* @param ctx context
* @param next context
*/
export const adminUpdateUserRoleController = async (ctx: koaCtx, next: koaNext) => {
const data = ctx.request.body as Partial<AdminUpdateUserRoleRequestDto>

const adminUuid = ctx.cookies.get('uuid') ?? ''
const adminToken = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid: adminUuid, apiPath: ctx.path }, ctx)) {
return
}

const adminUpdateUserRoleRequest: AdminUpdateUserRoleRequestDto = {
uuid: data.uuid ?? '',
newRoles: data.newRoles ?? []
}
const adminUpdateUserRoleResponseDto = await adminUpdateUserRoleService(adminUpdateUserRoleRequest, adminUuid, adminToken)
ctx.body = adminUpdateUserRoleResponseDto
await next()
}

/**
* 通过 uid 获取一个用户的角色
* @param ctx context
* @param next context
*/
export const adminGetUserRolesByUidController = async (ctx: koaCtx, next: koaNext) => {
const uid = parseInt(ctx.query.uid as string, 10)

const adminUuid = ctx.cookies.get('uuid') ?? ''
const adminToken = ctx.cookies.get('token') ?? ''

// RBAC 权限验证
if (!await isPassRbacCheck({ uuid: adminUuid, apiPath: ctx.path }, ctx)) {
return
}

const adminGetUserRolesByUidRequest: AdminGetUserRolesByUidRequestDto = {
uid,
}
const adminGetUserRolesByUidResponse = await adminGetUserRolesByUidService(adminGetUserRolesByUidRequest, adminUuid, adminToken)
ctx.body = adminGetUserRolesByUidResponse
await next()
}
Loading