Skip to content

Commit 01d90bb

Browse files
SonAIengineclaude
andcommitted
fix: 프론트엔드 API 인증 — Tauri WKWebView cookie 불가 대응
- apiClient.js getToken(): cookie → localStorage → Rust 세션 3단계 fallback - initTauriToken(): 앱 시작 시 Rust 세션에서 토큰 미리 로드 - CookieProvider: 로그인 성공 시 localStorage + Rust(cli_set_token)에 토큰 동기화 - cli_set_token 커맨드 추가 — 프론트에서 Rust 세션에 토큰 저장 (CLI 창 안 열림) - patch-api-auth.js: apiClient.js + CookieProvider 자동 패치 스크립트 - 원인: Tauri WKWebView에서 document.cookie 읽기 불가 → API 401 인증 실패 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 14de268 commit 01d90bb

4 files changed

Lines changed: 151 additions & 1 deletion

File tree

scripts/patch-api-auth.js

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#!/usr/bin/env node
2+
/**
3+
* apiClient.js의 getToken()을 패치하여
4+
* Tauri WKWebView에서 document.cookie 접근 불가 시
5+
* Rust 세션(cli_get_token)에서 토큰을 가져오도록 한다.
6+
*
7+
* 또한 로그인 성공 시 Rust 세션에도 토큰을 저장하도록
8+
* CookieProvider를 패치한다.
9+
*/
10+
const fs = require('fs');
11+
const path = require('path');
12+
13+
const frontendDir = process.argv[2];
14+
if (!frontendDir) {
15+
console.error('Usage: node patch-api-auth.js <frontend-dir>');
16+
process.exit(1);
17+
}
18+
19+
// ============================================================
20+
// Part 1: apiClient.js — getToken() 패치
21+
// ============================================================
22+
23+
const apiClientPath = path.join(frontendDir, 'src/app/_common/api/helper/apiClient.js');
24+
if (!fs.existsSync(apiClientPath)) {
25+
console.log('[WARN] apiClient.js not found');
26+
process.exit(0);
27+
}
28+
29+
let content = fs.readFileSync(apiClientPath, 'utf8');
30+
31+
if (content.includes('tauriTokenCache')) {
32+
console.log('[INFO] apiClient.js already patched');
33+
} else {
34+
console.log('[PATCH] apiClient.js — getToken() Tauri fallback 추가');
35+
36+
// getToken 함수를 확장
37+
content = content.replace(
38+
/const getToken = \(\) => \{\s*\n\s*return getAuthCookie\('access_token'\);\s*\n\};/,
39+
`// Tauri 토큰 캐시 (Rust 세션에서 가져온 토큰)
40+
let tauriTokenCache = null;
41+
42+
/**
43+
* 인증 토큰 가져오기
44+
* 1. 쿠키 (document.cookie)
45+
* 2. Tauri 토큰 캐시 (Rust 세션에서 미리 가져온 값)
46+
* 3. localStorage fallback
47+
*/
48+
const getToken = () => {
49+
// 1. 쿠키에서 시도
50+
const cookieToken = getAuthCookie('access_token');
51+
if (cookieToken) return cookieToken;
52+
53+
// 2. Tauri 캐시 (initTauriToken으로 미리 로드)
54+
if (tauriTokenCache) return tauriTokenCache;
55+
56+
// 3. localStorage fallback (Tauri WKWebView에서 cookie 불가 시)
57+
try {
58+
const stored = localStorage.getItem('xgen_access_token');
59+
if (stored) return stored;
60+
} catch {}
61+
62+
return null;
63+
};
64+
65+
/**
66+
* Tauri 환경에서 Rust 세션의 토큰을 미리 로드
67+
*/
68+
const initTauriToken = async () => {
69+
if (typeof window === 'undefined') return;
70+
if (!window.__TAURI_INTERNALS__) return;
71+
try {
72+
const { invoke } = await import('@tauri-apps/api/core');
73+
const token = await invoke('cli_get_token');
74+
if (token) {
75+
tauriTokenCache = token;
76+
// localStorage에도 저장 (다른 코드에서도 접근 가능)
77+
try { localStorage.setItem('xgen_access_token', token); } catch {}
78+
}
79+
} catch {}
80+
};
81+
82+
// 앱 시작 시 Tauri 토큰 로드
83+
initTauriToken();`
84+
);
85+
86+
// setCookieAuth 호출 후 localStorage에도 저장하도록 패치
87+
// 로그인 성공 시 토큰이 쿠키에 저장되면 localStorage에도 동기화
88+
if (!content.includes('xgen_access_token')) {
89+
// 이미 위에서 추가했으므로 패스
90+
}
91+
92+
fs.writeFileSync(apiClientPath, content);
93+
console.log('[OK] apiClient.js patched');
94+
}
95+
96+
// ============================================================
97+
// Part 2: CookieProvider — 로그인 시 Rust에도 토큰 전달
98+
// ============================================================
99+
100+
const cookieProviderPath = path.join(frontendDir, 'src/app/_common/components/CookieProvider.tsx');
101+
if (!fs.existsSync(cookieProviderPath)) {
102+
console.log('[WARN] CookieProvider.tsx not found');
103+
process.exit(0);
104+
}
105+
106+
let cpContent = fs.readFileSync(cookieProviderPath, 'utf8');
107+
108+
if (cpContent.includes('syncTokenToTauri')) {
109+
console.log('[INFO] CookieProvider already patched');
110+
} else {
111+
console.log('[PATCH] CookieProvider — 로그인 시 Tauri/localStorage에 토큰 동기화');
112+
113+
// setUser 호출 근처에 Tauri 토큰 동기화 추가
114+
// setCookieAuth('access_token', userData.access_token) 뒤에 추가
115+
cpContent = cpContent.replace(
116+
/setCookieAuth\('access_token', userData\.access_token\);/,
117+
`setCookieAuth('access_token', userData.access_token);
118+
119+
// Tauri + localStorage 토큰 동기화
120+
const syncTokenToTauri = async (token: string) => {
121+
try { localStorage.setItem('xgen_access_token', token); } catch {}
122+
if (typeof window !== 'undefined' && (window as any).__TAURI_INTERNALS__) {
123+
try {
124+
const { invoke } = await import('@tauri-apps/api/core');
125+
await invoke('cli_set_token', { token });
126+
} catch {}
127+
}
128+
};
129+
syncTokenToTauri(userData.access_token);`
130+
);
131+
132+
fs.writeFileSync(cookieProviderPath, cpContent);
133+
console.log('[OK] CookieProvider patched');
134+
}

scripts/patch-frontend.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,11 @@ if [ -f "$CLI_HTML" ]; then
201201
echo "[OK] cli.html 준비 완료"
202202
fi
203203

204-
# 6. 사이드바에 AI CLI 버튼 추가 (클릭 시 별도 윈도우 열기)
204+
# 6. API 인증 패치 (Tauri WKWebView에서 cookie 대신 localStorage/Rust 세션 사용)
205+
echo "[PATCH] API 인증 토큰 패치"
206+
node "$SCRIPT_DIR/patch-api-auth.js" "$FRONTEND_DIR"
207+
208+
# 7. 사이드바에 AI CLI 버튼 추가 (클릭 시 별도 윈도우 열기)
205209
echo "[PATCH] 사이드바 AI CLI 버튼 패치"
206210
node "$SCRIPT_DIR/patch-sidebar-cli.js" "$FRONTEND_DIR"
207211

src-tauri/src/commands/cli.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,17 @@ pub async fn cli_list_providers(
189189
Ok(serde_json::to_value(providers).unwrap_or_default())
190190
}
191191

192+
/// Store auth token in CLI session (called by frontend after login)
193+
#[tauri::command]
194+
pub async fn cli_set_token(
195+
state: tauri::State<'_, Arc<AppState>>,
196+
token: String,
197+
) -> Result<()> {
198+
let mut session = state.cli_session.write().await;
199+
session.xgen_token = Some(token);
200+
Ok(())
201+
}
202+
192203
/// Get stored auth token from CLI session (fallback for when URL param is missing)
193204
#[tauri::command]
194205
pub async fn cli_get_token(

src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ pub fn run() {
273273
commands::cli_send_message,
274274
commands::cli_get_history,
275275
commands::cli_clear_session,
276+
commands::cli_set_token,
276277
commands::cli_get_token,
277278
commands::cli_get_session_info,
278279
commands::cli_list_providers,

0 commit comments

Comments
 (0)