mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 23:41:12 +08:00
## Summary Fixes #14985 — clicking the **Thinking** button in a shared/embedded chat returns 401 and bounces the user to the login page, even though the same share page can chat with the agent just fine. ## Root cause In shared chat, `useGetSharedChatSearchParams` binds `conversationId` to the URL's `shared_id` query param — which is the **beta APIToken**, not the real agent id. That `conversationId` propagates through the component tree: ```tsx <WorkFlowTimeline canvasId={conversationId}> → useFetchMessageTrace(canvasId) → GET /api/v1/agents/<sharedId>/logs/<messageId> ``` But `/agents/<agent_id>/logs/<message_id>` is decorated with `@login_required` (`api/apps/restful_apis/agent_api.py:842-846`). The share page only holds the beta token — there is no session JWT — so the request 401s and quart-auth redirects to the login page. The reporter's server log matches exactly: ``` load_user from jwt got exception No b'.' found in value load_user: No APIToken found for token=ULG10SWG3E... Unauthorized request (quart_auth) GET /api/v1/agents/394013f8d42211f0bad6123fa55e8ed9/logs/96fd72e2-... 1.1 401 ``` The `394013f8...` segment in the URL is the `shared_id` (beta token), not an actual agent id. `_load_user` already accepts the regular `APIToken.token` field, but not `APIToken.beta`, by design — beta is a much weaker share-link credential than a personal API key. The sibling endpoints `/agentbots/<id>/completions` and `/agentbots/<id>/inputs` already use the right auth pattern for this scope (beta-token via `_get_sdk_authorization_token` → `APIToken.query(beta=token)`). Trace just didn't have a parallel. ## Fix ### Backend (`api/apps/restful_apis/bot_api.py`) Added a beta-token sibling endpoint: ``` GET /api/v1/agentbots/<shared_id>/logs/<message_id> ``` - Same auth shape as the existing `agentbots` endpoints. - The `<shared_id>` path segment is a client-supplied label only. The real `agent_id` used to build the Redis key (`<agent_id>-<message_id>-logs`) is taken from `APIToken.dialog_id` on the looked-up token, so the endpoint never trusts client-supplied identifiers for the data lookup. - Returns the same `{data: ...}` shape as the existing `/agents/<id>/logs/<message_id>` endpoint, so the frontend doesn't need to reshape the response. ### Frontend - `web/src/utils/api.ts`: added `sharedTrace(sharedId, messageId)` URL builder. - `web/src/services/agent-service.ts`: added `fetchSharedTrace({ shared_id, message_id })`. - `web/src/hooks/use-agent-request.ts`: `useFetchMessageTrace` takes an optional `isShare` argument. When set, it calls `fetchSharedTrace`; `isShare` is also folded into the `queryKey` so the two modes never share cached results. - `web/src/pages/agent/log-sheet/workflow-timeline.tsx`: forwards the already-existing `isShare` prop into the hook. All other existing call sites of `useFetchMessageTrace` (webhook timeline, pipeline log, dataflow result) pass no `isShare` argument → undefined → falsy → unchanged behavior. ## Test plan - [ ] In the regular Agent UI (logged-in user): open the trace / log sheet for any message and click into "Thinking" — the timeline should still load via `/agents/<id>/logs/<msg>`, same as before. - [ ] From the Agent page, click **Chat in new tab** to open `/chat/share?shared_id=<token>&from=agent`. Send a message, wait for a response, then click **Thinking** on the assistant turn. The trace panel should load instead of redirecting to the login page. - [ ] Same flow but with the agent embedded in an iframe ("Embed into webpage") — confirm there is no login redirect. - [ ] In DevTools → Network, confirm the share-chat trace request goes to `/api/v1/agentbots/<sharedId>/logs/<msgId>` and returns 200 with the same JSON shape as the logged-in path. - [ ] Confirm the chat completions, inputs, and upload flows in the share page still work — they were not touched. - [ ] Send a bogus / expired beta token to the new endpoint and confirm it returns the standard "Authentication error: API key is invalid!" response (no traceback, no 500). - [ ] Run `uv run pytest` to make sure no existing tests regress. ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) - [ ] New Feature (non-breaking change which adds functionality) - [ ] Documentation Update - [ ] Refactoring - [ ] Performance Improvement - [ ] Other (please describe): --------- Co-authored-by: Zhichang Yu <yuzhichang@gmail.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
# limitations under the License.
|
||||
#
|
||||
import copy
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
|
||||
@@ -250,6 +251,54 @@ async def begin_inputs(agent_id, tenant_id=None):
|
||||
"prologue": canvas.get_prologue(), "mode": canvas.get_mode()})
|
||||
|
||||
|
||||
@manager.route("/agentbots/<shared_id>/logs/<message_id>", methods=["GET"]) # noqa: F821
|
||||
async def agent_bot_logs(shared_id, message_id):
|
||||
# Beta-token sibling of /agents/<agent_id>/logs/<message_id>.
|
||||
# Used by the shared/embedded chat page's "Thinking" button (fixes #14985).
|
||||
# The <shared_id> path segment is just the value the client passed in the
|
||||
# URL (it equals the beta token in the share flow); authentication comes
|
||||
# from the Authorization header and the real agent_id is read from the
|
||||
# looked-up APIToken so we never trust client-supplied identifiers.
|
||||
from rag.utils.redis_conn import REDIS_CONN
|
||||
|
||||
token = _get_sdk_authorization_token()
|
||||
if not token:
|
||||
logger.warning(
|
||||
"agent_bot_logs: missing Authorization header (shared_id=%s message_id=%s)",
|
||||
shared_id, message_id,
|
||||
)
|
||||
return get_error_data_result(message='Authorization is not valid!')
|
||||
# Non-reversible fingerprint of the share token: lets operators correlate
|
||||
# auth-failure log lines for the same token without leaking a guessable
|
||||
# substring of the secret itself.
|
||||
token_fp = hashlib.sha256(token.encode("utf-8")).hexdigest()[:16]
|
||||
objs = await thread_pool_exec(APIToken.query, beta=token)
|
||||
if not objs:
|
||||
logger.warning(
|
||||
"agent_bot_logs: invalid beta token (fingerprint=%s shared_id=%s)",
|
||||
token_fp, shared_id,
|
||||
)
|
||||
return get_error_data_result(message='Authentication error: API key is invalid!"')
|
||||
|
||||
agent_id = objs[0].dialog_id
|
||||
if not agent_id:
|
||||
logger.warning(
|
||||
"agent_bot_logs: APIToken has no dialog_id (tenant_id=%s fingerprint=%s)",
|
||||
objs[0].tenant_id, token_fp,
|
||||
)
|
||||
return get_error_data_result(message='API token is not bound to an agent.')
|
||||
|
||||
try:
|
||||
binary = await thread_pool_exec(REDIS_CONN.get, f"{agent_id}-{message_id}-logs")
|
||||
if not binary:
|
||||
return get_json_result(data={})
|
||||
payload = binary.decode("utf-8") if isinstance(binary, bytes) else binary
|
||||
return get_json_result(data=json.loads(payload))
|
||||
except Exception as exc:
|
||||
logging.exception(exc)
|
||||
return server_error_response(exc)
|
||||
|
||||
|
||||
@manager.route("/searchbots/ask", methods=["POST"]) # noqa: F821
|
||||
@login_required(auth_types=AUTH_BETA)
|
||||
@add_tenant_id_to_kwargs
|
||||
|
||||
Reference in New Issue
Block a user