Some checks failed
test / Run tests (push) Failing after 15s
build / Build (push) Failing after 23s
263 lines
7.1 KiB
TypeScript
263 lines
7.1 KiB
TypeScript
// gm api 权限验证
|
|
import Cache from "@App/app/cache";
|
|
import { Script } from "@App/app/repo/scripts";
|
|
import { v4 as uuidv4 } from "uuid";
|
|
import { Api, Request } from "./gm_api";
|
|
import Queue from "@App/pkg/utils/queue";
|
|
import CacheKey from "@App/app/cache_key";
|
|
import { Permission, PermissionDAO } from "@App/app/repo/permission";
|
|
|
|
export interface ConfirmParam {
|
|
// 权限名
|
|
permission: string;
|
|
// 权限值
|
|
permissionValue?: string;
|
|
// 确认权限标题
|
|
title?: string;
|
|
// 权限详情内容
|
|
metadata?: { [key: string]: string };
|
|
// 权限描述
|
|
describe?: string;
|
|
// 是否通配
|
|
wildcard?: boolean;
|
|
// 权限内容
|
|
permissionContent?: string;
|
|
}
|
|
|
|
export interface UserConfirm {
|
|
allow: boolean;
|
|
type: number; // 1: 允许一次 2: 临时允许全部 3: 临时允许此 4: 永久允许全部 5: 永久允许此
|
|
}
|
|
|
|
export interface ApiParam {
|
|
// 默认提供的函数
|
|
default?: boolean;
|
|
// 是否只有后台环境中才能执行
|
|
background?: boolean;
|
|
// 是否需要弹出页面让用户进行确认
|
|
confirm?: (request: Request) => Promise<boolean | ConfirmParam>;
|
|
// 监听方法
|
|
listener?: () => void;
|
|
// 别名
|
|
alias?: string[];
|
|
// 关联
|
|
link?: string;
|
|
}
|
|
|
|
export interface ApiValue {
|
|
api: Api;
|
|
param: ApiParam;
|
|
}
|
|
|
|
export interface IPermissionVerify {
|
|
verify(request: Request, api: ApiValue): Promise<boolean>;
|
|
}
|
|
|
|
export default class PermissionVerify {
|
|
static apis: Map<string, ApiValue> = new Map();
|
|
|
|
public static API(param: ApiParam = {}) {
|
|
return (target: any, propertyName: string, descriptor: PropertyDescriptor) => {
|
|
const key = propertyName;
|
|
if (param.listener) {
|
|
param.listener();
|
|
}
|
|
PermissionVerify.apis.set(key, {
|
|
api: descriptor.value,
|
|
param,
|
|
});
|
|
// 兼容GM.*
|
|
const dot = key.replace("_", ".");
|
|
if (dot !== key) {
|
|
PermissionVerify.apis.set(dot, {
|
|
api: descriptor.value,
|
|
param,
|
|
});
|
|
if (param.alias) {
|
|
param.alias.push(dot);
|
|
} else {
|
|
param.alias = [dot];
|
|
}
|
|
}
|
|
|
|
// 处理别名
|
|
if (param.alias) {
|
|
param.alias.forEach((alias) => {
|
|
PermissionVerify.apis.set(alias, {
|
|
api: descriptor.value,
|
|
param,
|
|
});
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
// 确认队列
|
|
confirmQueue: Queue<{
|
|
request: Request;
|
|
confirm: ConfirmParam | boolean;
|
|
resolve: (value: boolean) => void;
|
|
reject: (reason: any) => void;
|
|
}> = new Queue();
|
|
|
|
async removePermissionCache(scriptId: number) {
|
|
// 先删除缓存
|
|
(await Cache.getInstance().list()).forEach((key) => {
|
|
if (key.startsWith(`permission:${scriptId.toString()}:`)) {
|
|
Cache.getInstance().del(key);
|
|
}
|
|
});
|
|
}
|
|
|
|
private permissionDAO: PermissionDAO;
|
|
|
|
constructor() {
|
|
this.permissionDAO = new PermissionDAO();
|
|
this.dealConfirmQueue();
|
|
}
|
|
|
|
// 验证是否有权限
|
|
verify(request: Request, api: ApiValue): Promise<boolean> {
|
|
if (api.param.default) {
|
|
return Promise.resolve(true);
|
|
}
|
|
// 没有其它条件,从metadata.grant中判断
|
|
const { grant } = request.script.metadata;
|
|
if (!grant) {
|
|
return Promise.reject(new Error("grant is undefined"));
|
|
}
|
|
for (let i = 0; i < grant.length; i += 1) {
|
|
if (
|
|
// 名称相等
|
|
grant[i] === request.api ||
|
|
// 别名相等
|
|
(api.param.alias && api.param.alias.includes(grant[i])) ||
|
|
// 有关联的
|
|
grant[i] === api.param.link
|
|
) {
|
|
// 需要用户确认
|
|
if (api.param.confirm) {
|
|
return this.pushConfirmQueue(request, api);
|
|
}
|
|
return Promise.resolve(true);
|
|
}
|
|
}
|
|
return Promise.reject(new Error("permission not requested"));
|
|
}
|
|
|
|
async dealConfirmQueue() {
|
|
// 处理确认队列
|
|
const data = await this.confirmQueue.pop();
|
|
if (!data) {
|
|
this.dealConfirmQueue();
|
|
return;
|
|
}
|
|
try {
|
|
const ret = await this.confirm(data.request, data.confirm);
|
|
data.resolve(ret);
|
|
} catch (e) {
|
|
data.reject(e);
|
|
}
|
|
this.dealConfirmQueue();
|
|
}
|
|
|
|
// 确认队列,为了防止一次性打开过多的窗口
|
|
async pushConfirmQueue(request: Request, api: ApiValue): Promise<boolean> {
|
|
const confirm = await api.param.confirm!(request);
|
|
if (confirm === true) {
|
|
return Promise.resolve(true);
|
|
}
|
|
return new Promise((resolve, reject) => {
|
|
this.confirmQueue.push({ request, confirm, resolve, reject });
|
|
});
|
|
}
|
|
|
|
async confirm(request: Request, confirm: boolean | ConfirmParam): Promise<boolean> {
|
|
if (typeof confirm === "boolean") {
|
|
return confirm;
|
|
}
|
|
const cacheKey = CacheKey.permissionConfirm(request.script.uuid, confirm);
|
|
// 从数据库中查询是否有此权限
|
|
const ret = await Cache.getInstance().getOrSet(cacheKey, async () => {
|
|
let model = await this.permissionDAO.findByKey(request.uuid, confirm.permission, confirm.permissionValue || "");
|
|
if (!model) {
|
|
// 允许通配
|
|
if (confirm.wildcard) {
|
|
model = await this.permissionDAO.findByKey(request.uuid, confirm.permission, confirm.permissionValue || "");
|
|
}
|
|
}
|
|
return Promise.resolve(model);
|
|
});
|
|
// 有查询到结果,进入判断,不再需要用户确认
|
|
if (ret) {
|
|
if (ret.allow) {
|
|
return Promise.resolve(true);
|
|
}
|
|
// 权限拒绝
|
|
return Promise.reject(new Error("permission denied"));
|
|
}
|
|
// 没有权限,则弹出页面让用户进行确认
|
|
const userConfirm = await this.confirmWindow(request.script, confirm);
|
|
// 成功存入数据库
|
|
const model: Permission = {
|
|
uuid: request.uuid,
|
|
permission: confirm.permission,
|
|
permissionValue: "",
|
|
allow: userConfirm.allow,
|
|
createtime: new Date().getTime(),
|
|
updatetime: 0,
|
|
};
|
|
switch (userConfirm.type) {
|
|
case 4:
|
|
case 2: {
|
|
// 通配
|
|
model.permissionValue = "*";
|
|
break;
|
|
}
|
|
case 5:
|
|
case 3: {
|
|
model.permissionValue = confirm.permissionValue || "";
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
// 临时 放入缓存
|
|
if (userConfirm.type >= 2) {
|
|
Cache.getInstance().set(cacheKey, model);
|
|
}
|
|
// 总是 放入数据库
|
|
if (userConfirm.type >= 4) {
|
|
const oldConfirm = await this.permissionDAO.findByKey(request.uuid, model.permission, model.permissionValue);
|
|
if (!oldConfirm) {
|
|
await this.permissionDAO.save(model);
|
|
} else {
|
|
await this.permissionDAO.update(this.permissionDAO.key(model), model);
|
|
}
|
|
}
|
|
if (userConfirm.allow) {
|
|
return Promise.resolve(true);
|
|
}
|
|
return Promise.reject(new Error("permission not allowed"));
|
|
}
|
|
|
|
// 确认map
|
|
confirmMap: Map<
|
|
string,
|
|
{
|
|
confirm: ConfirmParam;
|
|
script: Script;
|
|
resolve: (value: UserConfirm) => void;
|
|
reject: (reason: any) => void;
|
|
}
|
|
> = new Map();
|
|
|
|
// 弹出窗口让用户进行确认
|
|
async confirmWindow(script: Script, confirm: ConfirmParam): Promise<UserConfirm> {
|
|
return Promise.resolve({
|
|
allow: true,
|
|
type: 1,
|
|
});
|
|
}
|
|
}
|