diff --git a/api/.env.example b/api/.env.example index 6690d04c3..53e24571d 100644 --- a/api/.env.example +++ b/api/.env.example @@ -750,3 +750,4 @@ BEDROCK_PROXY= # 初始额度 ACCOUNT_TOTAL_QUOTA=15 +ENTERPRISE_ENABLED=True diff --git a/api/controllers/web/completion.py b/api/controllers/web/completion.py index 79ac57676..19e618b9f 100644 --- a/api/controllers/web/completion.py +++ b/api/controllers/web/completion.py @@ -45,20 +45,33 @@ from controllers.web.error_extend import ( ) from extensions.ext_database import db from libs.passport import PassportService +from libs.token import extract_access_token from models.account_money_extend import AccountMoneyExtend +from services.account_service import AccountService from services.app_generate_service_extend import AppGenerateServiceExtend def is_end_login(end_user): user_info = None try: - auth_token = request.headers.get("Authorization-extend") + # 从 cookie 中读取 access_token + auth_token = extract_access_token(request) + if not auth_token: + return None + + # 验证 access_token decoded = PassportService().verify(auth_token) - user_info = AccountService.load_logged_in_account(account_id=decoded.get("user_id")) + user_id = decoded.get("user_id") + + # 加载 Console 用户信息 + user_info = AccountService.load_logged_in_account(account_id=user_id) + + # 绑定 end_user 与 Console 用户 if user_info is not None: if end_user.external_user_id is None: - end_user.external_user_id = decoded.get("user_id") - except: + end_user.external_user_id = user_id + db.session.commit() # 提交绑定关系 + except Exception: logging.exception("load_logged_in_account error") pass # no login @@ -152,6 +165,11 @@ class CompletionApi(WebApiResource): streaming = payload.response_mode == "streaming" args["auto_generate_name"] = False + # extend 获取 Console 用户 ID,直接作为 from_account_id 传递 + user_info = is_end_login(end_user) + if user_info: + args["account_id"] = user_info.id + try: AppGenerateServiceExtend.calculate_cumulative_usage( app_model=app_model, @@ -251,6 +269,11 @@ class ChatApi(WebApiResource): streaming = payload.response_mode == "streaming" args["auto_generate_name"] = False + # 获取 Console 用户 ID,直接作为 from_account_id 传递 + user_info = is_end_login(end_user) + if user_info: + args["account_id"] = user_info.id + try: AppGenerateServiceExtend.calculate_cumulative_usage( app_model=app_model, diff --git a/api/controllers/web/login.py b/api/controllers/web/login.py index 5847f4ae3..bf404dc9d 100644 --- a/api/controllers/web/login.py +++ b/api/controllers/web/login.py @@ -23,6 +23,7 @@ from libs.passport import PassportService from libs.password import valid_password from libs.token import ( clear_webapp_access_token_from_cookie, + extract_access_token, extract_webapp_access_token, ) from services.account_service import AccountService @@ -88,11 +89,23 @@ class LoginStatusApi(Resource): def get(self): app_code = request.args.get("app_code") user_id = request.args.get("user_id") + + # 检查 Console 用户的 access_token cookie + console_token = extract_access_token(request) + console_user_logged_in = False + if console_token: + try: + PassportService().verify(console_token) + console_user_logged_in = True + except Exception: + console_user_logged_in = False + token = extract_webapp_access_token(request) if not app_code: return { "logged_in": bool(token), "app_logged_in": False, + "console_logged_in": console_user_logged_in, } app_id = AppService.get_app_id_by_code(app_code) is_public = not dify_config.ENTERPRISE_ENABLED or not WebAppAuthService.is_app_require_permission_check( @@ -118,6 +131,7 @@ class LoginStatusApi(Resource): return { "logged_in": user_logged_in, "app_logged_in": app_logged_in, + "console_logged_in": console_user_logged_in, } diff --git a/api/controllers/web/workflow.py b/api/controllers/web/workflow.py index b96b0b41c..bb69423d7 100644 --- a/api/controllers/web/workflow.py +++ b/api/controllers/web/workflow.py @@ -86,6 +86,11 @@ class WorkflowRunApi(WebApiResource): ) args = parser.parse_args() + # extend: 获取 Console 用户 ID,直接作为 from_account_id 传递 + user_info = is_end_login(end_user) + if user_info: + args["account_id"] = user_info.id + try: AppGenerateServiceExtend.calculate_cumulative_usage( app_model=app_model, diff --git a/api/core/app/apps/advanced_chat/app_generator.py b/api/core/app/apps/advanced_chat/app_generator.py index 1f93c3624..9728df7ca 100644 --- a/api/core/app/apps/advanced_chat/app_generator.py +++ b/api/core/app/apps/advanced_chat/app_generator.py @@ -135,6 +135,10 @@ class AdvancedChatAppGenerator(MessageBasedAppGenerator): extras["app_token_id"] = api_token.id # ------------------- 二开部分End - 密钥额度限制 ------------------- + # extend: 如果 args 中有 account_id(Web App 登录用户),将其放入 extras + if args.get("account_id"): + extras["account_id"] = args.get("account_id") + # get conversation conversation = None conversation_id = args.get("conversation_id") diff --git a/api/core/app/apps/agent_chat/app_generator.py b/api/core/app/apps/agent_chat/app_generator.py index 523049793..fa37c39c0 100644 --- a/api/core/app/apps/agent_chat/app_generator.py +++ b/api/core/app/apps/agent_chat/app_generator.py @@ -98,6 +98,9 @@ class AgentChatAppGenerator(MessageBasedAppGenerator): inputs = args["inputs"] extras = {"auto_generate_conversation_name": args.get("auto_generate_name", True)} + # extend: 如果 args 中有 account_id(Web App 登录用户),将其放入 extras + if args.get("account_id"): + extras["account_id"] = args.get("account_id") # get conversation conversation = None diff --git a/api/core/app/apps/chat/app_generator.py b/api/core/app/apps/chat/app_generator.py index e075f4484..b017c413f 100644 --- a/api/core/app/apps/chat/app_generator.py +++ b/api/core/app/apps/chat/app_generator.py @@ -90,6 +90,9 @@ class ChatAppGenerator(MessageBasedAppGenerator): inputs = args["inputs"] extras = {"auto_generate_conversation_name": args.get("auto_generate_name", True)} + # extend: 如果 args 中有 account_id(Web App 登录用户),将其放入 extras + if args.get("account_id"): + extras["account_id"] = args.get("account_id") # get conversation conversation = None diff --git a/api/core/app/apps/completion/app_generator.py b/api/core/app/apps/completion/app_generator.py index 53053444a..f36b98bef 100644 --- a/api/core/app/apps/completion/app_generator.py +++ b/api/core/app/apps/completion/app_generator.py @@ -130,6 +130,11 @@ class CompletionAppGenerator(MessageBasedAppGenerator): app_id=app_model.id, user_id=user.id if isinstance(user, Account) else user.session_id ) + # extend: 如果 args 中有 account_id(Web App 登录用户),将其放入 extras + extras = {} + if args.get("account_id"): + extras["account_id"] = args.get("account_id") + # init application generate entity application_generate_entity = CompletionAppGenerateEntity( task_id=str(uuid.uuid4()), @@ -144,7 +149,7 @@ class CompletionAppGenerator(MessageBasedAppGenerator): user_id=user.id, stream=streaming, invoke_from=invoke_from, - extras={}, + extras=extras, trace_manager=trace_manager, ) diff --git a/api/core/app/apps/message_based_app_generator.py b/api/core/app/apps/message_based_app_generator.py index 57617d886..7b9b0e7e7 100644 --- a/api/core/app/apps/message_based_app_generator.py +++ b/api/core/app/apps/message_based_app_generator.py @@ -128,6 +128,9 @@ class MessageBasedAppGenerator(BaseAppGenerator): if application_generate_entity.invoke_from in {InvokeFrom.WEB_APP, InvokeFrom.SERVICE_API}: from_source = "api" end_user_id = application_generate_entity.user_id + # 如果 extras 中提供了 account_id(Web App 登录用户),优先使用 + if application_generate_entity.extras and application_generate_entity.extras.get("account_id"): + account_id = application_generate_entity.extras.get("account_id") else: from_source = "console" account_id = application_generate_entity.user_id diff --git a/api/core/app/apps/workflow/app_generator.py b/api/core/app/apps/workflow/app_generator.py index bd9f956d7..933a134d0 100644 --- a/api/core/app/apps/workflow/app_generator.py +++ b/api/core/app/apps/workflow/app_generator.py @@ -166,6 +166,10 @@ class WorkflowAppGenerator(BaseAppGenerator): extras["app_token_id"] = api_token.id # ------------------- 二开部分End - 密钥额度限制 + # extend: 如果 args 中有 account_id(Web App 登录用户),将其放入 extras + if args.get("account_id"): + extras["account_id"] = args.get("account_id") + # init application generate entity application_generate_entity = WorkflowAppGenerateEntity( task_id=str(uuid.uuid4()), diff --git a/api/models/model.py b/api/models/model.py index ba8239ad1..e09960acc 100644 --- a/api/models/model.py +++ b/api/models/model.py @@ -957,9 +957,15 @@ class Conversation(Base): ): return end_user.session_id elif end_user is not None: - user: Account = db.session.query(Account).filter(Account.id == end_user.external_user_id).first() - if user: - return user.name + # 验证 external_user_id 是否为有效的 UUID + try: + uuid.UUID(end_user.external_user_id) + user: Account = db.session.query(Account).filter(Account.id == end_user.external_user_id).first() + if user: + return user.name + except (ValueError, TypeError): + # 如果不是有效的 UUID,返回 session_id + return end_user.session_id elif self.from_account_id: user: Account = db.session.query(Account).filter(Account.id == self.from_account_id).first() if user: diff --git a/api/models/workflow.py b/api/models/workflow.py index 7a1fc3169..8db14c079 100644 --- a/api/models/workflow.py +++ b/api/models/workflow.py @@ -2,6 +2,7 @@ from __future__ import annotations import json import logging +import uuid from collections.abc import Generator, Mapping, Sequence from datetime import datetime from enum import StrEnum @@ -1174,14 +1175,20 @@ class WorkflowAppLog(TypeBase): from models.model import EndUser end_user = db.session.query(EndUser).filter(EndUser.id == self.created_by).first() if end_user is not None and end_user.external_user_id is not None and len(end_user.external_user_id) > 0: - user: Account = db.session.query(Account).filter(Account.id == end_user.external_user_id).first() - if user: - return { - "id": user.id, - "type": user.status, - "is_anonymous": "true", - "session_id": user.name, - } + # 验证 external_user_id 是否为有效的 UUID + try: + uuid.UUID(end_user.external_user_id) + user: Account = db.session.query(Account).filter(Account.id == end_user.external_user_id).first() + if user: + return { + "id": user.id, + "type": user.status, + "is_anonymous": "true", + "session_id": user.name, + } + except (ValueError, TypeError): + # 如果不是有效的 UUID,跳过查询 + pass return end_user elif len(self.created_by) > 0: user: Account = db.session.query(Account).filter(Account.id == self.created_by).first() diff --git a/web/app/(shareLayout)/components/authenticated-layout.tsx b/web/app/(shareLayout)/components/authenticated-layout.tsx index 113f3b568..ec92793f6 100644 --- a/web/app/(shareLayout)/components/authenticated-layout.tsx +++ b/web/app/(shareLayout)/components/authenticated-layout.tsx @@ -2,14 +2,14 @@ import { usePathname, useRouter, useSearchParams } from 'next/navigation' import * as React from 'react' -import { useCallback, useEffect } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import AppUnavailable from '@/app/components/base/app-unavailable' import Loading from '@/app/components/base/loading' import { useWebAppStore } from '@/context/web-app-context' import { useGetUserCanAccessApp } from '@/service/access-control' import { useGetWebAppInfo, useGetWebAppMeta, useGetWebAppParams } from '@/service/use-share' -import { webAppLogout } from '@/service/webapp-auth' +import { checkConsoleLoginStatus, webAppLogout } from '@/service/webapp-auth' const AuthenticatedLayout = ({ children }: { children: React.ReactNode }) => { const { t } = useTranslation() @@ -22,6 +22,7 @@ const AuthenticatedLayout = ({ children }: { children: React.ReactNode }) => { const { isFetching: isFetchingAppInfo, data: appInfo, error: appInfoError } = useGetWebAppInfo() const { isFetching: isFetchingAppMeta, data: appMeta, error: appMetaError } = useGetWebAppMeta() const { data: userCanAccessApp, error: useCanAccessAppError } = useGetUserCanAccessApp({ appId: appInfo?.app_id, isInstalledApp: false }) + const [isCheckingAuth, setIsCheckingAuth] = useState(true) useEffect(() => { if (appInfo) @@ -36,6 +37,22 @@ const AuthenticatedLayout = ({ children }: { children: React.ReactNode }) => { const router = useRouter() const pathname = usePathname() const searchParams = useSearchParams() + + // 检查 Console 用户登录状态 + useEffect(() => { + const checkConsoleAuth = async () => { + setIsCheckingAuth(true) + const isConsoleLoggedIn = await checkConsoleLoginStatus() + if (!isConsoleLoggedIn) { + // 未登录,保存当前 URL 到 localStorage,然后跳转到 Console 登录页面 + localStorage.setItem('redirect_url', pathname) + router.replace('/signin') + } + setIsCheckingAuth(false) + } + + checkConsoleAuth() + }, [pathname, router]) const getSigninUrl = useCallback(() => { const params = new URLSearchParams(searchParams) params.delete('message') @@ -85,7 +102,7 @@ const AuthenticatedLayout = ({ children }: { children: React.ReactNode }) => { ) } - if (isFetchingAppInfo || isFetchingAppParams || isFetchingAppMeta) { + if (isCheckingAuth || isFetchingAppInfo || isFetchingAppParams || isFetchingAppMeta) { return (