fix(web): PendingApprovalsCard 批准/拒絕補 CSRF Token — 修復 403
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled

根因: fetch 沒帶 X-CSRF-Token header + credentials:include
     API 回 403 "CSRF token cookie missing"

修復: 加 useCSRF hook,sign/reject 請求帶 ...getHeaders() + credentials:include
     與 incident-card.tsx / openclaw-state-machine.tsx 同一模式

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-04-09 19:00:02 +08:00
parent a2a98452ad
commit 9a8f410f23

View File

@@ -10,6 +10,7 @@ import { useState, useEffect } from 'react'
import { useTranslations } from 'next-intl'
import { useRouter } from 'next/navigation'
import { useLocale } from 'next-intl'
import { useCSRF } from '@/hooks/useCSRF'
const API_BASE = process.env.NEXT_PUBLIC_API_URL ?? ''
@@ -33,6 +34,7 @@ export function PendingApprovalsCard() {
const t = useTranslations('dashboard')
const router = useRouter()
const locale = useLocale()
const { getHeaders } = useCSRF()
const [approvals, setApprovals] = useState<Approval[]>([])
const [actionError, setActionError] = useState<string | null>(null)
const [actioningId, setActioningId] = useState<string | null>(null)
@@ -83,7 +85,7 @@ export function PendingApprovalsCard() {
onClick={() => {
setActionError(null)
setActioningId(ap.id)
fetch(`${API_BASE}/api/v1/approvals/${ap.id}/sign`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ signer: 'web-ui' }) })
fetch(`${API_BASE}/api/v1/approvals/${ap.id}/sign`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', ...getHeaders() }, body: JSON.stringify({ signer: 'web-ui' }) })
.then(r => { if (!r.ok) throw new Error(`${r.status}`); return r })
.then(() => setApprovals(prev => prev.filter(x => x.id !== ap.id)))
.catch(e => setActionError(`approve failed: ${e.message}`))
@@ -96,7 +98,7 @@ export function PendingApprovalsCard() {
onClick={() => {
setActionError(null)
setActioningId(ap.id)
fetch(`${API_BASE}/api/v1/approvals/${ap.id}/reject`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reason: 'rejected-from-web' }) })
fetch(`${API_BASE}/api/v1/approvals/${ap.id}/reject`, { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', ...getHeaders() }, body: JSON.stringify({ reason: 'rejected-from-web' }) })
.then(r => { if (!r.ok) throw new Error(`${r.status}`); return r })
.then(() => setApprovals(prev => prev.filter(x => x.id !== ap.id)))
.catch(e => setActionError(`reject failed: ${e.message}`))