scriptcat-mv3/src/app/service/service_worker/permission_verify.ts
王一之 1e8b5e6453
Some checks failed
test / Run tests (push) Failing after 15s
build / Build (push) Failing after 23s
优化
2025-02-08 17:59:18 +08:00

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,
});
}
}