generated from tailored/app-template
add permission check
This commit is contained in:
parent
2ff8590ceb
commit
bfe8463212
@ -27,6 +27,8 @@
|
||||
"packageManager": "pnpm@10.7.1",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@kevisual/cache": "^0.0.2",
|
||||
"@kevisual/permission": "^0.0.1",
|
||||
"@kevisual/router": "0.0.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
31
pnpm-lock.yaml
generated
31
pnpm-lock.yaml
generated
@ -8,6 +8,12 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@kevisual/cache':
|
||||
specifier: ^0.0.2
|
||||
version: 0.0.2(rollup@4.39.0)(tslib@2.8.1)(typescript@5.8.2)
|
||||
'@kevisual/permission':
|
||||
specifier: ^0.0.1
|
||||
version: 0.0.1
|
||||
'@kevisual/router':
|
||||
specifier: 0.0.10
|
||||
version: 0.0.10
|
||||
@ -398,6 +404,9 @@ packages:
|
||||
'@kevisual/auth@1.0.5':
|
||||
resolution: {integrity: sha512-GwsLj7unKXi7lmMiIIgdig4LwwLiDJnOy15HHZR5gMbyK6s5/uJiMY5RXPB2+onGzTNDqFo/hXjsD2wkerHPVg==}
|
||||
|
||||
'@kevisual/cache@0.0.2':
|
||||
resolution: {integrity: sha512-2Cl5KF2Gi27uLfhO6CdTMFnRzx9vYnqevAo7d9ab3rOaqTgF8tLeAXglXyRbaWW3WUbHU2XaOb4r98uUsqIQQw==}
|
||||
|
||||
'@kevisual/code-center-module@0.0.18':
|
||||
resolution: {integrity: sha512-BfANmxLEO1AwVmqpa6VDgxk//YN8asf1r5jIPpyKDQm12kyyrYgHND9AgGCDRH8lvq6rYVe0svCZXD5b06UPWQ==}
|
||||
peerDependencies:
|
||||
@ -414,6 +423,9 @@ packages:
|
||||
'@kevisual/mark@0.0.7':
|
||||
resolution: {integrity: sha512-PiEEy4yvWEpixw76PzgrIWeNelzm+FrhtzFmqJU92o5GkgawaFwighcvIxqcVZRKeEFF4uvlTjFrGeQvXw6F4A==}
|
||||
|
||||
'@kevisual/permission@0.0.1':
|
||||
resolution: {integrity: sha512-nSX2LzbPkU3YAMegbUFGU8tfmtFb7dcF5edqzm+gI6crcyCL1JzIB9HAYNEeEVIljLxuREwM/vVg9aFmF4cz9Q==}
|
||||
|
||||
'@kevisual/query@0.0.13':
|
||||
resolution: {integrity: sha512-gSEIDiCvwSaLLAFZv4vam4wSrMsaCuQ3VGjE3kwRwZ8urlVH1TOA+NUO908A22p9m1Iij7Y1Q/JlfSJi2QzuKQ==}
|
||||
|
||||
@ -1747,6 +1759,9 @@ packages:
|
||||
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
idb-keyval@6.2.1:
|
||||
resolution: {integrity: sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==}
|
||||
|
||||
ignore-by-default@1.0.1:
|
||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
||||
|
||||
@ -3347,6 +3362,18 @@ snapshots:
|
||||
|
||||
'@kevisual/auth@1.0.5': {}
|
||||
|
||||
'@kevisual/cache@0.0.2(rollup@4.39.0)(tslib@2.8.1)(typescript@5.8.2)':
|
||||
dependencies:
|
||||
'@rollup/plugin-commonjs': 28.0.3(rollup@4.39.0)
|
||||
'@rollup/plugin-node-resolve': 16.0.1(rollup@4.39.0)
|
||||
'@rollup/plugin-typescript': 12.1.2(rollup@4.39.0)(tslib@2.8.1)(typescript@5.8.2)
|
||||
idb-keyval: 6.2.1
|
||||
rollup-plugin-dts: 6.2.1(rollup@4.39.0)(typescript@5.8.2)
|
||||
transitivePeerDependencies:
|
||||
- rollup
|
||||
- tslib
|
||||
- typescript
|
||||
|
||||
'@kevisual/code-center-module@0.0.18(@kevisual/auth@1.0.5)(@kevisual/router@0.0.10)(@kevisual/use-config@1.0.10(dotenv@16.4.7))(ioredis@5.6.0)(pg@8.14.1)(sequelize@6.37.7(pg-hstore@2.3.4)(pg@8.14.1))':
|
||||
dependencies:
|
||||
'@kevisual/auth': 1.0.5
|
||||
@ -3393,6 +3420,8 @@ snapshots:
|
||||
- tedious
|
||||
- utf-8-validate
|
||||
|
||||
'@kevisual/permission@0.0.1': {}
|
||||
|
||||
'@kevisual/query@0.0.13(ws@8.18.1)(zod@3.24.2)':
|
||||
dependencies:
|
||||
openai: 4.91.1(ws@8.18.1)(zod@3.24.2)
|
||||
@ -4882,6 +4911,8 @@ snapshots:
|
||||
dependencies:
|
||||
safer-buffer: 2.1.2
|
||||
|
||||
idb-keyval@6.2.1: {}
|
||||
|
||||
ignore-by-default@1.0.1: {}
|
||||
|
||||
ignore@5.3.2: {}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Permission } from '@kevisual/permission';
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
// 加密函数
|
||||
@ -73,6 +74,7 @@ export type AIConfig = {
|
||||
description?: string;
|
||||
models: AIModel[];
|
||||
secretKeys: SecretKey[];
|
||||
permission?: Permission;
|
||||
filter?: {
|
||||
objectKey: string;
|
||||
type: 'array' | 'object';
|
||||
|
@ -2,7 +2,7 @@ import { app } from '@/app.ts';
|
||||
import { ChatServices } from './services/chat-services.ts';
|
||||
import { ChatConfigServices } from './services/chat-config-srevices.ts';
|
||||
import { AiChatHistoryModel } from './models/ai-chat-history.ts';
|
||||
|
||||
import { UserPermission } from '@kevisual/permission';
|
||||
app
|
||||
.route({
|
||||
path: 'ai',
|
||||
@ -21,19 +21,20 @@ app
|
||||
if (!aiChatHistory) {
|
||||
ctx.throw(400, 'aiChatHistory not found');
|
||||
}
|
||||
if (aiChatHistory.uid !== tokenUser.uid) {
|
||||
if (aiChatHistory.uid !== tokenUser.id) {
|
||||
ctx.throw(403, 'not permission');
|
||||
}
|
||||
username = username || aiChatHistory.username;
|
||||
username = username || aiChatHistory.username || tokenUsername;
|
||||
model = model || aiChatHistory.model;
|
||||
group = group || aiChatHistory.group;
|
||||
} else {
|
||||
username = username || tokenUsername;
|
||||
}
|
||||
const isSelf = username === tokenUsername;
|
||||
|
||||
if (!Array.isArray(messages)) {
|
||||
ctx.throw(400, 'chat messages is not array');
|
||||
}
|
||||
|
||||
// 初始化服务
|
||||
const chatServices = await ChatServices.createServices({
|
||||
owner: username,
|
||||
@ -41,6 +42,15 @@ app
|
||||
group,
|
||||
username: tokenUsername,
|
||||
});
|
||||
if (!isSelf && username !== 'root') {
|
||||
const aiConfig = chatServices.aiConfig;
|
||||
const permission = new UserPermission({ permission: aiConfig.permission, owner: username });
|
||||
const checkPermission = permission.checkPermissionSuccess({ username: tokenUsername, password: options.password });
|
||||
if (!checkPermission.success) {
|
||||
ctx.throw(403, checkPermission.message);
|
||||
}
|
||||
}
|
||||
|
||||
const chatConfigServices = new ChatConfigServices(username, tokenUsername);
|
||||
await chatConfigServices.checkUserCanChat(tokenUsername);
|
||||
await chatServices.checkCanChat();
|
||||
@ -72,6 +82,10 @@ app
|
||||
prompt_tokens: aiChatHistory.prompt_tokens + usage.prompt_tokens,
|
||||
completion_tokens: aiChatHistory.completion_tokens + usage.completion_tokens,
|
||||
total_tokens: aiChatHistory.total_tokens + usage.total_tokens,
|
||||
version: aiChatHistory.version + 1,
|
||||
model: model,
|
||||
group: group,
|
||||
username: username,
|
||||
};
|
||||
if (hook) {
|
||||
needUpdateData.data = {
|
||||
@ -126,19 +140,46 @@ app
|
||||
.define(async (ctx) => {
|
||||
const username = ctx.query.username || 'root';
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const usernames = ctx.query.data?.usernames || [];
|
||||
const tokenUsername = tokenUser.username;
|
||||
const isSameUser = username === tokenUser.username;
|
||||
const configObject: Record<string, any> = {};
|
||||
const configArray: any[] = [];
|
||||
const services = new ChatConfigServices(username, tokenUser.username);
|
||||
const res = await services.getChatConfig(true, ctx.query.token);
|
||||
configObject[username] = res;
|
||||
configArray.push({
|
||||
username,
|
||||
config: res,
|
||||
});
|
||||
if (!isSameUser) {
|
||||
const selfServices = new ChatConfigServices(tokenUser.username, tokenUser.username);
|
||||
const selfRes = await selfServices.getChatConfig(true, ctx.query.token);
|
||||
configObject['self'] = selfRes;
|
||||
} else {
|
||||
configObject['self'] = res;
|
||||
configArray.push({
|
||||
username: tokenUser.username,
|
||||
self: true,
|
||||
config: selfRes,
|
||||
});
|
||||
}
|
||||
ctx.body = configObject;
|
||||
for (const username of usernames) {
|
||||
const services = new ChatConfigServices(username, tokenUser.username);
|
||||
const res = await services.getChatConfig(true, ctx.query.token);
|
||||
const aiConfig = services.aiConfig;
|
||||
const permission = new UserPermission({ permission: aiConfig.permission, owner: username });
|
||||
const checkPermission = permission.checkPermissionSuccess({ username: tokenUsername, password: '-----------------' });
|
||||
if (!checkPermission.success) {
|
||||
// ctx.throw(403, `[${username}] ${checkPermission.message}`);
|
||||
configArray.push({
|
||||
username,
|
||||
config: null,
|
||||
error: checkPermission.message,
|
||||
});
|
||||
} else {
|
||||
configArray.push({
|
||||
username,
|
||||
config: res,
|
||||
});
|
||||
}
|
||||
}
|
||||
ctx.body = configArray;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
@ -146,7 +187,7 @@ app
|
||||
.route({
|
||||
path: 'ai',
|
||||
key: 'get-chat-usage',
|
||||
description: '获取chat使用情况',
|
||||
description: '获取chat使用情况, 只获取root的使用情况',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
|
@ -12,12 +12,121 @@ app
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const aiChatList = await AiChatHistoryModel.findAll({
|
||||
where: {
|
||||
uid: tokenUser.uid,
|
||||
uid: tokenUser.id,
|
||||
},
|
||||
order: [['createdAt', 'DESC']],
|
||||
order: [['updatedAt', 'DESC']],
|
||||
});
|
||||
ctx.body = {
|
||||
list: aiChatList,
|
||||
};
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'ai',
|
||||
key: 'update-chat',
|
||||
description: '更新chat',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const uid = tokenUser.id;
|
||||
const { id, data, prompt_tokens, total_tokens, completion_tokens, createdAt, updatedAt, ...rest } = ctx.query.data || {};
|
||||
let aiChat: AiChatHistoryModel | null = null;
|
||||
if (id) {
|
||||
aiChat = await AiChatHistoryModel.findByPk(id);
|
||||
if (!aiChat) {
|
||||
ctx.throw(404, 'chat not found');
|
||||
}
|
||||
if (aiChat.uid !== uid) {
|
||||
ctx.throw(403, 'no permission');
|
||||
}
|
||||
await aiChat.update({ data: { ...aiChat.data, ...data }, ...rest, version: aiChat.version + 1 });
|
||||
} else {
|
||||
aiChat = await AiChatHistoryModel.create({
|
||||
...rest,
|
||||
uid: uid,
|
||||
});
|
||||
}
|
||||
ctx.body = aiChat;
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'ai',
|
||||
key: 'get-chat',
|
||||
description: '获取chat',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const { id } = ctx.query.data || {};
|
||||
if (!id) {
|
||||
ctx.throw(400, 'id is required');
|
||||
}
|
||||
const aiChat = await AiChatHistoryModel.findByPk(id);
|
||||
if (!aiChat) {
|
||||
ctx.throw(404, 'chat not found');
|
||||
}
|
||||
if (aiChat.uid !== tokenUser.id) {
|
||||
ctx.throw(403, 'no permission');
|
||||
}
|
||||
ctx.body = aiChat;
|
||||
})
|
||||
.addTo(app);
|
||||
app
|
||||
.route({
|
||||
path: 'ai',
|
||||
key: 'get-chat-version',
|
||||
description: '获取chat版本',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const { id } = ctx.query.data || {};
|
||||
if (!id) {
|
||||
ctx.throw(400, 'id is required');
|
||||
}
|
||||
const aiChat = await AiChatHistoryModel.findByPk(id);
|
||||
if (!aiChat) {
|
||||
ctx.throw(404, 'chat not found');
|
||||
}
|
||||
if (aiChat.uid !== tokenUser.id) {
|
||||
ctx.throw(403, 'no permission');
|
||||
}
|
||||
ctx.body = {
|
||||
id: aiChat.id,
|
||||
version: aiChat.version,
|
||||
};
|
||||
})
|
||||
.addTo(app);
|
||||
|
||||
app
|
||||
.route({
|
||||
path: 'ai',
|
||||
key: 'delete-chat',
|
||||
description: '删除chat',
|
||||
middleware: ['auth'],
|
||||
})
|
||||
.define(async (ctx) => {
|
||||
const tokenUser = ctx.state.tokenUser;
|
||||
const { id } = ctx.query.data || {};
|
||||
if (!id) {
|
||||
ctx.throw(400, 'id is required');
|
||||
}
|
||||
const aiChat = await AiChatHistoryModel.findByPk(id);
|
||||
if (!aiChat) {
|
||||
ctx.throw(404, 'chat not found');
|
||||
}
|
||||
if (aiChat.uid !== tokenUser.id) {
|
||||
ctx.throw(403, 'no permission');
|
||||
}
|
||||
await aiChat.destroy();
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
};
|
||||
})
|
||||
.addTo(app);
|
||||
|
@ -33,6 +33,8 @@ export class AiChatHistoryModel extends Model {
|
||||
declare total_tokens: number;
|
||||
declare completion_tokens: number;
|
||||
|
||||
declare version: number;
|
||||
|
||||
declare createdAt: Date;
|
||||
declare updatedAt: Date;
|
||||
}
|
||||
@ -47,14 +49,17 @@ AiChatHistoryModel.init(
|
||||
username: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: '',
|
||||
},
|
||||
model: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: '',
|
||||
},
|
||||
group: {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: false,
|
||||
defaultValue: '',
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.STRING,
|
||||
@ -82,6 +87,10 @@ AiChatHistoryModel.init(
|
||||
type: DataTypes.JSONB,
|
||||
defaultValue: {},
|
||||
},
|
||||
version: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0,
|
||||
},
|
||||
uid: {
|
||||
type: DataTypes.UUID,
|
||||
allowNull: true,
|
||||
|
@ -8,6 +8,8 @@ export class ChatConfigServices {
|
||||
owner: string;
|
||||
// 使用者
|
||||
username: string;
|
||||
aiConfig?: AIConfig;
|
||||
|
||||
/**
|
||||
* username 是使用的模型的用户名,使用谁的模型
|
||||
* @param username
|
||||
@ -50,6 +52,7 @@ export class ChatConfigServices {
|
||||
const cacheTime = 60 * 60 * 24 * 40; // 1天
|
||||
await redis.set(key, JSON.stringify(modelConfig), 'EX', cacheTime);
|
||||
}
|
||||
this.aiConfig = modelConfig;
|
||||
if (needClearSecret) {
|
||||
modelConfig = this.filterApiKey(modelConfig);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { AIConfigParser, ProviderResult } from '@/provider/utils/parse-config.ts';
|
||||
import { AIConfig, AIConfigParser, ProviderResult } from '@/provider/utils/parse-config.ts';
|
||||
import { ProviderManager, ChatMessage, BaseChat, ChatMessageOptions } from '@/provider/index.ts';
|
||||
import { redis } from '@/modules/db.ts';
|
||||
import { CustomError } from '@kevisual/router';
|
||||
@ -36,6 +36,7 @@ export class ChatServices {
|
||||
* 模型配置
|
||||
*/
|
||||
modelConfig?: ProviderResult;
|
||||
aiConfig?: AIConfig;
|
||||
chatProvider?: BaseChat;
|
||||
constructor(opts: ChatServicesConfig) {
|
||||
this.owner = opts.owner;
|
||||
@ -65,6 +66,7 @@ export class ChatServices {
|
||||
},
|
||||
});
|
||||
that.modelConfig = { ...providerResult, apiKey };
|
||||
that.aiConfig = config;
|
||||
return that.modelConfig;
|
||||
}
|
||||
/**
|
||||
@ -108,7 +110,7 @@ export class ChatServices {
|
||||
async createNewMessage(messages: ChastHistoryMessage[]) {
|
||||
return messages.map((item) => {
|
||||
if (!item.id) {
|
||||
item.id = 'chat' + nanoid();
|
||||
item.id = 'chat-' + nanoid();
|
||||
item.createdAt = Date.now();
|
||||
item.updatedAt = Date.now();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user