From 4917fd568f33bcdbdeb90a873225d00314dbae07 Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Wed, 1 Apr 2026 07:49:38 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Bearer=20=ED=86=A0=ED=81=B0=20=EC=9D=B8?= =?UTF-8?q?=EC=A6=9D=EC=9C=BC=EB=A1=9C=20=EC=A0=84=ED=99=98=20=E2=80=94=20?= =?UTF-8?q?=EC=BF=A0=ED=82=A4=20=ED=94=84=EB=A1=9D=EC=8B=9C=20=EC=9D=B4?= =?UTF-8?q?=EC=8A=88=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Caddy 프록시를 거치면서 httpOnly 쿠키가 제대로 전달 안 되는 문제. 로그인 시 받은 토큰을 메모리에 저장하고 Authorization: Bearer 헤더로 전송하는 방식으로 변경. 쿠키 의존성 제거. Co-Authored-By: Claude Opus 4.6 (1M context) --- hub-web/src/lib/api.ts | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/hub-web/src/lib/api.ts b/hub-web/src/lib/api.ts index fae709d..d06841d 100644 --- a/hub-web/src/lib/api.ts +++ b/hub-web/src/lib/api.ts @@ -1,27 +1,43 @@ const BASE = ''; +// Store token in memory for Bearer auth (more reliable than cookies through proxies) +let _token: string | null = null; + +export function setToken(token: string | null) { + _token = token; +} + +function authHeaders(): Record { + const h: Record = { 'Content-Type': 'application/json' }; + if (_token) h['Authorization'] = `Bearer ${_token}`; + return h; +} + export async function login(password: string): Promise<{ role: string; token: string }> { const res = await fetch(`${BASE}/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, - credentials: 'include', body: JSON.stringify({ password }), }); if (!res.ok) { const err = await res.json().catch(() => null); throw new Error(err?.error?.message || 'Login failed'); } - return res.json(); + const data = await res.json(); + _token = data.token; + return data; } export async function getMe(): Promise<{ role: string } | null> { - const res = await fetch(`${BASE}/auth/me`, { credentials: 'include' }); - if (!res.ok) return null; + if (!_token) return null; + const res = await fetch(`${BASE}/auth/me`, { headers: authHeaders() }); + if (!res.ok) { _token = null; return null; } return res.json(); } export async function logout(): Promise { - await fetch(`${BASE}/auth/logout`, { method: 'POST', credentials: 'include' }); + await fetch(`${BASE}/auth/logout`, { method: 'POST', headers: authHeaders() }); + _token = null; } export interface Model { @@ -33,7 +49,7 @@ export interface Model { } export async function getModels(): Promise { - const res = await fetch(`${BASE}/v1/models`, { credentials: 'include' }); + const res = await fetch(`${BASE}/v1/models`, { headers: authHeaders() }); if (!res.ok) return []; const data = await res.json(); return data.data || []; @@ -73,8 +89,7 @@ export async function* streamChat( ): AsyncGenerator { const res = await fetch(`${BASE}/v1/chat/completions`, { method: 'POST', - headers: { 'Content-Type': 'application/json' }, - credentials: 'include', + headers: authHeaders(), body: JSON.stringify({ model, messages, stream: true }), });