From 589f2fc4c76f0465c32d1b71d4ced92f974124cf Mon Sep 17 00:00:00 2001 From: OG T Date: Tue, 31 Mar 2026 21:50:34 +0800 Subject: [PATCH] =?UTF-8?q?fix(web):=20openclaw-state-machine=20=E8=A3=9C?= =?UTF-8?q?=E4=B8=8A=20CSRF=20Token=20(P0=20=E6=A0=B9=E6=9C=AC=E5=8E=9F?= =?UTF-8?q?=E5=9B=A0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根本原因: 首頁用的是 openclaw-state-machine.tsx 而非 LiveApprovalPanel 該元件的 handleApprove 完全沒有 CSRF token 和 credentials: include 導致後端回傳 "CSRF token cookie missing" → 按鈕沒有任何反應 修復: - import useCSRF hook - handleApprove 加上 X-CSRF-Token header - fetch 加上 credentials: 'include' - useCallback deps 加上 csrfToken Co-Authored-By: Claude Sonnet 4.6 --- apps/web/src/components/ai/openclaw-state-machine.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/ai/openclaw-state-machine.tsx b/apps/web/src/components/ai/openclaw-state-machine.tsx index 7b918918..29122333 100644 --- a/apps/web/src/components/ai/openclaw-state-machine.tsx +++ b/apps/web/src/components/ai/openclaw-state-machine.tsx @@ -19,6 +19,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' import { useTranslations } from 'next-intl' import { cn } from '@/lib/utils' +import { useCSRF } from '@/hooks/useCSRF' import { OpenClawPanel, type OpenClawStatus } from './openclaw-panel' import type { ThinkingStream as _ThinkingStream, DEFAULT_THINKING_MESSAGES as _DEFAULT_THINKING_MESSAGES } from './thinking-stream' import { ApprovalCard, type ApprovalRequest } from '@/components/approval/approval-card' @@ -94,6 +95,7 @@ export function OpenClawStateMachine({ className, }: OpenClawStateMachineProps) { const t = useTranslations() + const { csrfToken } = useCSRF() // State const [machineState, setMachineState] = useState('idle') @@ -199,9 +201,13 @@ export function OpenClawStateMachine({ signerCounter.current++ try { + const headers: Record = { 'Content-Type': 'application/json' } + if (csrfToken) headers['X-CSRF-Token'] = csrfToken + const response = await fetch(`${apiBaseUrl}/api/v1/approvals/${approvalId}/sign`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, + headers, + credentials: 'include', body: JSON.stringify({ signer_id: signer.id, signer_name: signer.name, @@ -223,7 +229,7 @@ export function OpenClawStateMachine({ setError(err instanceof Error ? err.message : 'Sign failed') return -1 } - }, [fetchPendingApprovals]) + }, [fetchPendingApprovals, csrfToken]) // ========================================================================== // API: Reject Request