mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-06-29 15:31:05 +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
|
||||
|
||||
@@ -26,6 +26,7 @@ import agentService, {
|
||||
fetchAgentLogsByCanvasId,
|
||||
fetchAgentLogsById,
|
||||
fetchPipeLineList,
|
||||
fetchSharedTrace,
|
||||
fetchTrace,
|
||||
fetchWebhookTrace,
|
||||
updateAgent,
|
||||
@@ -529,8 +530,12 @@ export const useUploadAgentFileWithProgress = (identifier?: string | null) => {
|
||||
return { data, loading, uploadAgentFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchMessageTrace = (canvasId?: string) => {
|
||||
export const useFetchMessageTrace = (canvasId?: string, isShare?: boolean) => {
|
||||
const { id } = useParams();
|
||||
// In shared mode there's no :id route param and `canvasId` actually carries
|
||||
// the share (beta) APIToken — route through fetchSharedTrace so the request
|
||||
// hits the beta-token-aware endpoint instead of /agents/<id>/logs which
|
||||
// requires a session login (fixes #14985).
|
||||
const queryId = id || canvasId;
|
||||
const [messageId, setMessageId] = useState('');
|
||||
const [isStopFetchTrace, setISStopFetchTrace] = useState(false);
|
||||
@@ -540,7 +545,7 @@ export const useFetchMessageTrace = (canvasId?: string) => {
|
||||
isFetching: loading,
|
||||
refetch,
|
||||
} = useQuery<ITraceData[]>({
|
||||
queryKey: [AgentApiAction.Trace, queryId, messageId],
|
||||
queryKey: [AgentApiAction.Trace, queryId, messageId, !!isShare],
|
||||
refetchOnReconnect: false,
|
||||
refetchOnMount: false,
|
||||
refetchOnWindowFocus: false,
|
||||
@@ -548,7 +553,12 @@ export const useFetchMessageTrace = (canvasId?: string) => {
|
||||
enabled: !!queryId && !!messageId,
|
||||
refetchInterval: !isStopFetchTrace ? 3000 : false,
|
||||
queryFn: async () => {
|
||||
const { data } = await fetchTrace({
|
||||
const { data } = isShare
|
||||
? await fetchSharedTrace({
|
||||
shared_id: queryId as string,
|
||||
message_id: messageId,
|
||||
})
|
||||
: await fetchTrace({
|
||||
canvas_id: queryId as string,
|
||||
message_id: messageId,
|
||||
});
|
||||
|
||||
@@ -103,7 +103,7 @@ export const WorkFlowTimeline = ({
|
||||
data: traceData,
|
||||
setMessageId,
|
||||
setISStopFetchTrace,
|
||||
} = useFetchMessageTrace(canvasId);
|
||||
} = useFetchMessageTrace(canvasId, isShare);
|
||||
|
||||
useEffect(() => {
|
||||
setMessageId(currentMessageId);
|
||||
|
||||
@@ -154,6 +154,15 @@ export const fetchTrace = (data: { canvas_id: string; message_id: string }) => {
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
// Used by the shared/embedded chat page where the only credential available
|
||||
// is the share (beta) APIToken (fixes #14985).
|
||||
export const fetchSharedTrace = (data: {
|
||||
shared_id: string;
|
||||
message_id: string;
|
||||
}) => {
|
||||
return request.get(api.sharedTrace(data.shared_id, data.message_id));
|
||||
};
|
||||
export const fetchAgentLogsByCanvasId = (
|
||||
canvasId: string,
|
||||
params: IAgentLogsRequest,
|
||||
|
||||
@@ -256,6 +256,8 @@ export default {
|
||||
`${restAPIv1}/agents/${agentId}/components/${componentId}/debug`,
|
||||
trace: (agentId: string, messageId: string) =>
|
||||
`${restAPIv1}/agents/${agentId}/logs/${messageId}`,
|
||||
sharedTrace: (sharedId: string, messageId: string) =>
|
||||
`${restAPIv1}/agentbots/${sharedId}/logs/${messageId}`,
|
||||
cancelCanvas: (taskId: string) => `${restAPIv1}/tasks/${taskId}/cancel`,
|
||||
// agent
|
||||
inputForm: (agentId: string, componentId: string) =>
|
||||
|
||||
Reference in New Issue
Block a user