fix(web): 活躍事件 Y/n 按鈕補上 CSRF Token (P0 根本原因)
All checks were successful
E2E Health Check / e2e-health (push) Successful in 19s

問題: DualStateIncidentCard 的 Y/n 按鈕呼叫 apiClient.signApproval/rejectApproval
時,沒有帶 X-CSRF-Token header 也沒有 credentials: 'include'
後端返回 403 CSRF token cookie missing

修復:
- api-client.ts: signApproval/rejectApproval 加入 csrfToken 參數
  + X-CSRF-Token header + credentials: 'include'
- dual-state-incident-card.tsx: 加入 useCSRF() hook,
  將 csrfToken 傳入 API 呼叫,更新 useCallback deps

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-31 22:45:27 +08:00
parent 2ba61acf72
commit 95de7e0e15
2 changed files with 19 additions and 9 deletions

View File

@@ -114,10 +114,14 @@ export const apiClient = {
}>(res)
},
async signApproval(approvalId: string, signer: string = 'commander', comment?: string) {
async signApproval(approvalId: string, signer: string = 'commander', comment?: string, csrfToken?: string | null) {
// Phase 22 P0: 加入 CSRF token + credentials (2026-03-31 Claude Code)
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (csrfToken) headers['X-CSRF-Token'] = csrfToken
const res = await fetch(`${API_BASE_URL}/approvals/${approvalId}/sign`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers,
credentials: 'include',
body: JSON.stringify({
signer_id: signer,
signer_name: signer,
@@ -138,10 +142,14 @@ export const apiClient = {
}>(res)
},
async rejectApproval(approvalId: string, reason?: string) {
async rejectApproval(approvalId: string, reason?: string, csrfToken?: string | null) {
// Phase 22 P0: 加入 CSRF token + credentials (2026-03-31 Claude Code)
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (csrfToken) headers['X-CSRF-Token'] = csrfToken
const res = await fetch(`${API_BASE_URL}/approvals/${approvalId}/reject`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers,
credentials: 'include',
body: JSON.stringify({
rejector_id: 'commander',
rejector_name: 'Commander',