Skip to content

Commit ca540b4

Browse files
SonAIengineclaude
andcommitted
fix: AI CLI 토큰 전달 — cookie 대신 useAuth() + Rust 세션 fallback
- 사이드바: document.cookie 대신 user.access_token (CookieProvider) 직접 사용 - cli.html: 3단계 토큰 탐색 — URL param → cookie → Rust cli_get_token - cli_get_token 커맨드 추가 — open_cli_window 시 저장된 토큰 반환 - 토큰 있으면 "✅ 인증 완료", 없으면 "⚠️ 토큰 없음" 표시 - 원인: Tauri WKWebView에서 document.cookie 접근 불가 → 항상 null Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 21a21b7 commit ca540b4

4 files changed

Lines changed: 53 additions & 16 deletions

File tree

scripts/patch-sidebar-cli.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ if (usesSidebarLayout) {
8383
}
8484

8585
// 3. Add state + handler after quickLogout
86+
// NOTE: useAuth() already provides user.access_token — no need for document.cookie
8687
content = content.replace(
8788
/const \{ quickLogout \} = useQuickLogout\(\);/,
8889
`const { quickLogout } = useQuickLogout();
@@ -93,11 +94,9 @@ if (usesSidebarLayout) {
9394
const openCliWindow = async () => {
9495
try {
9596
const { invoke } = await import('@tauri-apps/api/core');
96-
const getCookie = (name: string) => {
97-
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
98-
return match ? match[2] : null;
99-
};
100-
const token = getCookie('access_token') || undefined;
97+
// Get token directly from CookieProvider context (user object)
98+
const token = user?.access_token || undefined;
99+
console.log('[AI CLI] Opening with token:', token ? token.substring(0, 20) + '...' : 'NONE');
101100
await invoke('open_cli_window', { xgenToken: token });
102101
} catch (e) {
103102
console.error('Failed to open CLI window:', e);

src-cli/cli.html

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,30 +202,54 @@
202202
let streamingText = '';
203203
let currentStreamEl = null;
204204

205-
// Get auth token: URL param → cookie → Tauri state
206-
function getToken() {
205+
// Auth token cache
206+
let cachedToken = null;
207+
208+
// Get auth token: URL param → cookie → Rust session
209+
function getTokenSync() {
210+
if (cachedToken) return cachedToken;
207211
// 1. URL query param (passed by open_cli_window)
208212
const params = new URLSearchParams(window.location.search);
209213
const paramToken = params.get('token');
210214
if (paramToken) {
211-
console.log('[CLI] Token from URL param:', paramToken.substring(0, 20) + '...');
215+
console.log('[CLI] Token from URL param');
216+
cachedToken = paramToken;
212217
return paramToken;
213218
}
214219
// 2. Cookie fallback (same origin in Tauri webview)
215220
const match = document.cookie.match(/(^| )access_token=([^;]+)/);
216221
const cookieToken = match ? decodeURIComponent(match[2]) : null;
217222
if (cookieToken) {
218-
console.log('[CLI] Token from cookie:', cookieToken.substring(0, 20) + '...');
223+
console.log('[CLI] Token from cookie');
224+
cachedToken = cookieToken;
219225
return cookieToken;
220226
}
227+
return null;
228+
}
229+
230+
// Async token getter: also tries Rust session as last resort
231+
async function getToken() {
232+
const syncToken = getTokenSync();
233+
if (syncToken) return syncToken;
234+
// 3. Rust session fallback (token stored when open_cli_window was called)
235+
try {
236+
const rustToken = await invoke('cli_get_token');
237+
if (rustToken) {
238+
console.log('[CLI] Token from Rust session');
239+
cachedToken = rustToken;
240+
return rustToken;
241+
}
242+
} catch (e) {
243+
console.warn('[CLI] Failed to get token from Rust:', e);
244+
}
221245
console.warn('[CLI] No auth token found!');
222246
return null;
223247
}
224248

225249
// Load providers
226250
async function loadProviders() {
227251
try {
228-
const token = getToken();
252+
const token = await getToken();
229253
const providers = await invoke('cli_list_providers', { xgenToken: token });
230254
const available = providers.filter(p => p.configured && p.available);
231255
providerSelect.innerHTML = available.map(p =>
@@ -291,7 +315,7 @@
291315
currentStreamEl = addMessage('assistant', '');
292316

293317
try {
294-
const token = getToken();
318+
const token = await getToken();
295319
await invoke('cli_send_message', {
296320
message: text,
297321
xgenToken: token,
@@ -379,11 +403,15 @@
379403
});
380404

381405
// Initialize
382-
const initToken = getToken();
383-
if (!initToken) {
384-
addMessage('system', '⚠️ 인증 토큰이 없습니다. 로그인 후 AI CLI를 다시 열어주세요.');
385-
}
386-
loadProviders();
406+
(async () => {
407+
const initToken = await getToken();
408+
if (!initToken) {
409+
addMessage('system', '⚠️ 인증 토큰이 없습니다. 로그인 후 AI CLI를 다시 열어주세요.');
410+
} else {
411+
addMessage('system', '✅ 인증 완료. 명령을 입력하세요.');
412+
}
413+
loadProviders();
414+
})();
387415
</script>
388416
</body>
389417
</html>

src-tauri/src/commands/cli.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,15 @@ pub async fn cli_list_providers(
187187
Ok(serde_json::to_value(providers).unwrap_or_default())
188188
}
189189

190+
/// Get stored auth token from CLI session (fallback for when URL param is missing)
191+
#[tauri::command]
192+
pub async fn cli_get_token(
193+
state: tauri::State<'_, Arc<AppState>>,
194+
) -> Result<Option<String>> {
195+
let session = state.cli_session.read().await;
196+
Ok(session.xgen_token.clone())
197+
}
198+
190199
/// Get CLI session info
191200
#[tauri::command]
192201
pub async fn cli_get_session_info(

src-tauri/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,7 @@ pub fn run() {
259259
commands::cli_send_message,
260260
commands::cli_get_history,
261261
commands::cli_clear_session,
262+
commands::cli_get_token,
262263
commands::cli_get_session_info,
263264
commands::cli_list_providers,
264265
])

0 commit comments

Comments
 (0)