diff --git a/apps/application/workflow/nodes/image_generate_node/image_generate_node.py b/apps/application/workflow/nodes/image_generate_node/image_generate_node.py index a18de5d2b2c..02859d6993a 100644 --- a/apps/application/workflow/nodes/image_generate_node/image_generate_node.py +++ b/apps/application/workflow/nodes/image_generate_node/image_generate_node.py @@ -221,12 +221,13 @@ def _upload_tool_file(self, file, workflow_params): def _upload_application_file(self, file, workflow_params): application_id = workflow_params.get("application_id") chat_id = workflow_params.get("chat_id") + chat_user_id = workflow_params.get("chat_user_id") return FileSerializer( data={ "file": file, - "meta": {"debug": False, "chat_id": chat_id, "application_id": application_id}, - "source_id": application_id, - "source_type": FileSourceType.APPLICATION.value, + "meta": {"chat_id": chat_id, "application_id": application_id, "user_id": chat_user_id}, + "source_id": chat_id, + "source_type": FileSourceType.CHAT.value, } ).upload() diff --git a/apps/application/workflow/nodes/image_to_video_node/image_to_video_node.py b/apps/application/workflow/nodes/image_to_video_node/image_to_video_node.py index 3252e201ff6..6dcc2c27dd4 100644 --- a/apps/application/workflow/nodes/image_to_video_node/image_to_video_node.py +++ b/apps/application/workflow/nodes/image_to_video_node/image_to_video_node.py @@ -223,18 +223,14 @@ def _upload_tool_file(self, file, workflow_params): def _upload_application_file(self, file, workflow_params): application_id = workflow_params.get("application_id") chat_id = workflow_params.get("chat_id") - debug = workflow_params.get("debug", False) - meta = { - "debug": debug, - "chat_id": chat_id, - "application_id": application_id, - } + chat_user_id = workflow_params.get("chat_user_id") + meta = {"chat_id": chat_id, "application_id": application_id, "user_id": chat_user_id} file_url = FileSerializer( data={ "file": file, "meta": meta, - "source_id": application_id, - "source_type": FileSourceType.APPLICATION.value, + "source_id": chat_id, + "source_type": FileSourceType.CHAT.value, } ).upload() return file_url diff --git a/apps/application/workflow/nodes/text_to_speech_node/text_to_speech_node.py b/apps/application/workflow/nodes/text_to_speech_node/text_to_speech_node.py index 36b844223e4..51d8d27cd92 100644 --- a/apps/application/workflow/nodes/text_to_speech_node/text_to_speech_node.py +++ b/apps/application/workflow/nodes/text_to_speech_node/text_to_speech_node.py @@ -198,12 +198,13 @@ def _upload_tool_file(self, file, workflow_params): def _upload_application_file(self, file, workflow_params): application_id = workflow_params.get("application_id") chat_id = workflow_params.get("chat_id") + chat_user_id = workflow_params.get("chat_user_id") return FileSerializer( data={ "file": file, - "meta": {"debug": False, "chat_id": chat_id, "application_id": application_id}, - "source_id": application_id, - "source_type": FileSourceType.APPLICATION.value, + "meta": {"debug": False, "chat_id": chat_id, "application_id": application_id, "user_id": chat_user_id}, + "source_id": chat_id, + "source_type": FileSourceType.CHAT.value, } ).upload() diff --git a/apps/application/workflow/nodes/text_to_video_node/text_to_video_node.py b/apps/application/workflow/nodes/text_to_video_node/text_to_video_node.py index 07c8b723b05..5c8352093fb 100644 --- a/apps/application/workflow/nodes/text_to_video_node/text_to_video_node.py +++ b/apps/application/workflow/nodes/text_to_video_node/text_to_video_node.py @@ -186,18 +186,14 @@ def _upload_tool_file(self, file, workflow_params): def _upload_application_file(self, file, workflow_params): application_id = workflow_params.get("application_id") chat_id = workflow_params.get("chat_id") - debug = workflow_params.get("debug", False) - meta = { - "debug": debug, - "chat_id": chat_id, - "application_id": application_id, - } + chat_user_id = workflow_params.get("chat_user_id") + meta = {"chat_id": chat_id, "application_id": application_id, "user_id": chat_user_id} file_url = FileSerializer( data={ "file": file, "meta": meta, - "source_id": application_id, - "source_type": FileSourceType.APPLICATION.value, + "source_id": chat_id, + "source_type": FileSourceType.CHAT.value, } ).upload() return file_url diff --git a/apps/chat/serializers/chat_authentication.py b/apps/chat/serializers/chat_authentication.py index 47eafcf157a..b72f846a0a1 100644 --- a/apps/chat/serializers/chat_authentication.py +++ b/apps/chat/serializers/chat_authentication.py @@ -31,14 +31,14 @@ class AnonymousAuthenticationSerializer(serializers.Serializer): def auth(self, request): self.is_valid(raise_exception=True) token = request.META.get("HTTP_AUTHORIZATION") - token_details = {} + token_details = None try: # 校验token if token is not None: - token_details = signing.loads(token[7:]) + token_details = parse_token(token[7:]) except Exception: pass - chat_user_id = token_details.get("id") or str(uuid.uuid7()) + chat_user_id = token_details.id if token_details else str(uuid.uuid7()) _type = AuthenticationType.CHAT_USER access_token = self.data.get("access_token") diff --git a/apps/chat/views/v3/chat.py b/apps/chat/views/v3/chat.py index 82068a0c3b5..2477a073eca 100644 --- a/apps/chat/views/v3/chat.py +++ b/apps/chat/views/v3/chat.py @@ -456,7 +456,7 @@ def post(self, request: Request): version, get_key = Cache_Version.CHAT_USER_TOKEN.value auth = request.META.get("HTTP_AUTHORIZATION") cache.delete(get_key(token=auth), version=version) - return result.success(True) + return clear_file_auth_cookie(result.success(True), request.query_params.get("accessToken")) return result.error(_("Failed to change password")) @@ -481,6 +481,20 @@ def get(self, request: Request): return result.success(ChatUserProfileSerializer().profile(request.user.profile)) +FILE_AUTH_COOKIE = "mk_file_auth" + + +def get_chat_user_cookie_path(access_token): + """对话用户登录时写入文件凭证 cookie 的路径;退出登录时按同一路径删除。""" + return f"/chat/{access_token + '/' if access_token else ''}" + + +def clear_file_auth_cookie(response: HttpResponse, access_token): + """删除对话用户登录写入的文件凭证 cookie;该 cookie 为 HttpOnly,前端无法自行清除。""" + response.delete_cookie(FILE_AUTH_COOKIE, path=get_chat_user_cookie_path(access_token), samesite="Lax") + return response + + class BaseAuthView(APIView): def create_token_and_cache(self, user, access_token, operate): application_id = None @@ -522,7 +536,7 @@ def create_token_and_cache(self, user, access_token, operate): def generate(self, request, token: str, response: HttpResponse, path: str = "/chat"): secure = request.is_secure() response.set_cookie( - "mk_file_auth", + FILE_AUTH_COOKIE, value=token, max_age=7 * 24 * 3600, path=path, @@ -550,7 +564,7 @@ def post(self, request: Request): user.source = "LOCAL" token = self.create_token_and_cache(user, access_token, Operate.LOCAL) response = result.success({"token": token}) - return self.generate(request, token, response, path=f"/chat/{access_token + '/' if access_token else ''}") + return self.generate(request, token, response, path=get_chat_user_cookie_path(access_token)) class Logout(APIView): @@ -569,4 +583,4 @@ def post(self, request: Request): version, get_key = Cache_Version.CHAT_USER_TOKEN.value auth = request.META.get("HTTP_AUTHORIZATION") cache.delete(get_key(token=auth[7:]), version=version) - return result.success(True) + return clear_file_auth_cookie(result.success(True), request.query_params.get("accessToken")) diff --git a/apps/system_manage/serializers/chat_user_serializer.py b/apps/system_manage/serializers/chat_user_serializer.py index c0ea53d290d..670454d6988 100644 --- a/apps/system_manage/serializers/chat_user_serializer.py +++ b/apps/system_manage/serializers/chat_user_serializer.py @@ -31,7 +31,10 @@ def create_token_and_cache(access_token, user): raise AppApiException(1005, _("Invalid access token")) application_id = application_access_token.application_id - # 检查用户是否有权限访问该应用 + # 如果不需要认证就直接 + if not application_access_token.authentication: + return + # 检查用户是否有权限访问该应用 is_authorized = ResourceChatUserAuthorize.objects.filter( resource_id=application_id, resource_type=ResourceType.APPLICATION.value, is_auth=True, user_id=user.id ).exists() diff --git a/ui/src/api/chat/auth.ts b/ui/src/api/chat/auth.ts index b84e2790669..76a129621ae 100644 --- a/ui/src/api/chat/auth.ts +++ b/ui/src/api/chat/auth.ts @@ -80,9 +80,9 @@ const getCaptcha = (username: string, accessToken: string) => { return get('/v3/captcha', { username, accessToken }) } -/** 退出当前对话用户登录状态。 */ -const postLogout = () => { - return post('/v3/auth/logout') +/** 退出当前对话用户登录状态;传入 accessToken 时后端按对应应用的路径清除文件凭证 cookie。 */ +const postLogout = (accessToken?: string) => { + return post('/v3/auth/logout', undefined, accessToken ? { accessToken } : undefined) } export default { diff --git a/ui/src/api/chat/chat-user.ts b/ui/src/api/chat/chat-user.ts index 03c85afc9fe..47b0df38677 100644 --- a/ui/src/api/chat/chat-user.ts +++ b/ui/src/api/chat/chat-user.ts @@ -9,7 +9,8 @@ const getChatUserProfile = () => { } /** 修改当前用户密码,提交 RSA 加密后的表单。 */ -const postResetPassword = (data: { encryptedData: string }) => post<{ encryptedData: string }, boolean>('/v3/chat_user/current/reset_password', data) +const postResetPassword = (data: { encryptedData: string }, accessToken?: string) => + post<{ encryptedData: string }, boolean>('/v3/chat_user/current/reset_password', data, accessToken ? { accessToken } : undefined) /** 获取当前用户 API Key;首次读取由服务端创建。 */ const getApiKey = () => get('/v3/api_key') diff --git a/ui/src/conversation-panel/left-sidebar/account-menu/UserPwdDialog.vue b/ui/src/conversation-panel/left-sidebar/account-menu/UserPwdDialog.vue index 83b9628eadd..9b1fb5c5448 100644 --- a/ui/src/conversation-panel/left-sidebar/account-menu/UserPwdDialog.vue +++ b/ui/src/conversation-panel/left-sidebar/account-menu/UserPwdDialog.vue @@ -58,7 +58,7 @@ async function submitPassword() { return } - return ChatUserApi.postResetPassword({ encryptedData }) + return ChatUserApi.postResetPassword({ encryptedData }, auth.accessToken) .then(() => { MsgSuccess('密码修改成功') diff --git a/ui/src/stores/chat/auth.ts b/ui/src/stores/chat/auth.ts index c4ad7c79e12..41d5ba7eea8 100644 --- a/ui/src/stores/chat/auth.ts +++ b/ui/src/stores/chat/auth.ts @@ -139,7 +139,7 @@ export const useChatAuthStore = defineStore('chat-auth', { /** 退出当前场景的登录状态。 */ logout() { - return ChatAuthApi.postLogout().then(() => this.clearToken()) + return ChatAuthApi.postLogout(this.accessToken).then(() => this.clearToken()) }, /** 保存指定场景的 token;是当前场景时同步到 Store。 */ @@ -158,7 +158,10 @@ export const useChatAuthStore = defineStore('chat-auth', { useChatUserStore().clearCurrentUser() }, - /** 清除当前场景的 token,如退出登录。 */ + /** + * 清除当前场景本地保存的 token,如退出登录。 + * 文件凭证 cookie(mk_file_auth)为 HttpOnly,前端无法清除,由退出登录、修改密码接口的响应删除。 + */ clearToken() { localStorage.removeItem(getTokenStorageKey(this.scope)) this.token = ''