Commit 210ab7ce by Xianquan Committed by GitHub

fix(chat): allow users to update own history metadata (#7325)

parent 70e837d4
...@@ -3,25 +3,28 @@ import { UpdateHistoryBodySchema } from '@fastgpt/global/openapi/core/chat/histo ...@@ -3,25 +3,28 @@ import { UpdateHistoryBodySchema } from '@fastgpt/global/openapi/core/chat/histo
import { MongoChat } from '@fastgpt/service/core/chat/chatSchema'; import { MongoChat } from '@fastgpt/service/core/chat/chatSchema';
import { NextAPI } from '@/service/middleware/entry'; import { NextAPI } from '@/service/middleware/entry';
import { type ApiRequestProps } from '@fastgpt/next/type'; import { type ApiRequestProps } from '@fastgpt/next/type';
import { WritePermissionVal } from '@fastgpt/global/support/permission/constant'; import { ReadPermissionVal, WritePermissionVal } from '@fastgpt/global/support/permission/constant';
import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError'; import { parseApiInput } from '@fastgpt/service/common/zod/requestParseError';
import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat'; import { ChatErrEnum } from '@fastgpt/global/common/error/code/chat';
import { buildChatHistoryMatch } from '@/service/core/chat/history'; import { buildChatHistoryMatch } from '@/service/core/chat/history';
import { ChatSourceTypeEnum } from '@fastgpt/global/core/chat/constants';
/* update chat history: title, customTitle, top */ /** 更新会话标题、用户自定义标题或置顶状态,并限制操作范围为当前用户有权访问的会话。 */
export async function handler(req: ApiRequestProps, _res: NextApiResponse) { export async function handler(req: ApiRequestProps, _res: NextApiResponse) {
const { sourceType, sourceId, chatId, title, customTitle, top, outLinkAuthData } = parseApiInput({ const { sourceType, sourceId, chatId, title, customTitle, top, outLinkAuthData } = parseApiInput({
req, req,
bodySchema: UpdateHistoryBodySchema bodySchema: UpdateHistoryBodySchema
}).body; }).body;
// App 历史属于用户个人数据;Skill Edit 历史仍要求资源写权限。
const per = sourceType === ChatSourceTypeEnum.skillEdit ? WritePermissionVal : ReadPermissionVal;
const match = await buildChatHistoryMatch({ const match = await buildChatHistoryMatch({
req, req,
sourceType, sourceType,
sourceId, sourceId,
chatId, chatId,
outLinkAuthData, outLinkAuthData,
per: WritePermissionVal per
}); });
if (!match) return Promise.reject(ChatErrEnum.unAuthChat); if (!match) return Promise.reject(ChatErrEnum.unAuthChat);
......
...@@ -11,8 +11,13 @@ import { Call } from '@test/utils/request'; ...@@ -11,8 +11,13 @@ import { Call } from '@test/utils/request';
import { describe, expect, it, beforeEach } from 'vitest'; import { describe, expect, it, beforeEach } from 'vitest';
import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema'; import { MongoResourcePermission } from '@fastgpt/service/support/permission/schema';
import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant'; import { AppReadChatLogPerVal } from '@fastgpt/global/support/permission/app/constant';
import { PerResourceTypeEnum } from '@fastgpt/global/support/permission/constant'; import {
PerResourceTypeEnum,
ReadPermissionVal
} from '@fastgpt/global/support/permission/constant';
import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant'; import { PublishChannelEnum } from '@fastgpt/global/support/outLink/constant';
import { MongoAgentSkills } from '@fastgpt/service/core/ai/skill/model/schema';
import { AgentSkillSourceEnum } from '@fastgpt/global/core/ai/skill/constants';
describe('updateHistory api test', () => { describe('updateHistory api test', () => {
let testUser: Awaited<ReturnType<typeof getUser>>; let testUser: Awaited<ReturnType<typeof getUser>>;
...@@ -123,6 +128,134 @@ describe('updateHistory api test', () => { ...@@ -123,6 +128,134 @@ describe('updateHistory api test', () => {
expect(updatedChat?.top).toBe(true); expect(updatedChat?.top).toBe(true);
}); });
it('should allow a read-only app member to pin their own history', async () => {
const readonlyUser = await getUser(`readonly-update-history-${getNanoid(6)}`, testUser.teamId);
const readonlyUserChatId = getNanoid();
await Promise.all([
MongoResourcePermission.create({
resourceType: PerResourceTypeEnum.app,
teamId: testUser.teamId,
resourceId: appId,
tmbId: readonlyUser.tmbId,
permission: ReadPermissionVal
}),
MongoChat.create({
teamId: testUser.teamId,
tmbId: readonlyUser.tmbId,
sourceType: ChatSourceTypeEnum.app,
appId,
chatId: readonlyUserChatId,
source: ChatSourceEnum.online,
title: 'Readonly user chat'
})
]);
const res = await Call<UpdateHistoryBodyType, unknown>(handler, {
auth: readonlyUser,
body: {
appId,
chatId: readonlyUserChatId,
top: true
}
});
expect(res.code).toBe(200);
expect(res.error).toBeUndefined();
const updatedChat = await MongoChat.findOne({ appId, chatId: readonlyUserChatId }).lean();
expect(updatedChat?.top).toBe(true);
});
it('should reject a read-only app member updating another member history', async () => {
const readonlyUser = await getUser(`readonly-update-history-${getNanoid(6)}`, testUser.teamId);
const otherUser = await getUser(`other-update-history-${getNanoid(6)}`, testUser.teamId);
const otherUserChatId = getNanoid();
await Promise.all([
MongoResourcePermission.create({
resourceType: PerResourceTypeEnum.app,
teamId: testUser.teamId,
resourceId: appId,
tmbId: readonlyUser.tmbId,
permission: ReadPermissionVal
}),
MongoChat.create({
teamId: testUser.teamId,
tmbId: otherUser.tmbId,
sourceType: ChatSourceTypeEnum.app,
appId,
chatId: otherUserChatId,
source: ChatSourceEnum.online,
title: 'Other user chat',
top: false
})
]);
const res = await Call<UpdateHistoryBodyType, unknown>(handler, {
auth: readonlyUser,
body: {
appId,
chatId: otherUserChatId,
top: true
}
});
expect(res.code).not.toBe(200);
const unchangedChat = await MongoChat.findOne({ appId, chatId: otherUserChatId }).lean();
expect(unchangedChat?.top).toBe(false);
});
it('should reject a read-only skill collaborator updating skill edit history', async () => {
const readonlyUser = await getUser(`readonly-skill-history-${getNanoid(6)}`, testUser.teamId);
const skill = await MongoAgentSkills.create({
name: 'Readonly Update Skill History',
source: AgentSkillSourceEnum.personal,
teamId: testUser.teamId,
tmbId: testUser.tmbId
});
const skillId = String(skill._id);
const skillChatId = getNanoid();
await Promise.all([
MongoResourcePermission.create({
resourceType: PerResourceTypeEnum.agentSkill,
teamId: testUser.teamId,
resourceId: skillId,
tmbId: readonlyUser.tmbId,
permission: ReadPermissionVal
}),
MongoChat.create({
teamId: testUser.teamId,
tmbId: readonlyUser.tmbId,
sourceType: ChatSourceTypeEnum.skillEdit,
appId: skillId,
chatId: skillChatId,
source: ChatSourceEnum.test,
top: false
})
]);
const res = await Call<UpdateHistoryBodyType, unknown>(handler, {
auth: readonlyUser,
body: {
skillId,
chatId: skillChatId,
top: true
}
});
expect(res.code).not.toBe(200);
const unchangedChat = await MongoChat.findOne({
sourceType: ChatSourceTypeEnum.skillEdit,
appId: skillId,
chatId: skillChatId
}).lean();
expect(unchangedChat?.top).toBe(false);
});
it('should update top status for share history without appId', async () => { it('should update top status for share history without appId', async () => {
const shareId = `share-update-history-${getNanoid()}`; const shareId = `share-update-history-${getNanoid()}`;
const outLinkUid = `share-user-${getNanoid()}`; const outLinkUid = `share-user-${getNanoid()}`;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment