feat(frontend): 로그인 토큰을 Bearer 로 들고 다니게 — Django 백엔드 연결

Django(OpenCode 중계) 백엔드는 토큰을 쿠키가 아니라 응답 body 로 주고, Tauri 앱은
서버와 origin 이 달라 쿠키가 안 붙는다. 기존 tokenProvider seam 에 refresh 토큰과
localStorage 영속을 얹고, client/sse 의 refresh 를 body {refreshToken} 방식으로 바꿈.
로그인 때 저장하고 로그아웃 때 비움. 토큰 없으면 예전 쿠키 모드 그대로.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 21:24:16 +09:00
co-authored by Claude Fable 5.1
parent 858ee9e9da
commit 54f75aa089
9 changed files with 247 additions and 30 deletions
+26 -1
View File
@@ -4,7 +4,7 @@ import MockAdapter from "axios-mock-adapter"
import { apiClient, apiGet, apiPost, apiList } from "./client"
import { ApiError } from "./errors"
import { useSessionExpiryStore } from "@/features/auth/store/sessionExpiryStore"
import { setAccessToken } from "@/lib/auth/tokenProvider"
import { setAccessToken, setTokens, clearTokens, getAccessToken } from "@/lib/auth/tokenProvider"
const BASE = "http://localhost:8001/api/v1"
@@ -152,6 +152,31 @@ describe("401 인터셉터 — refresh 성공", () => {
})
})
describe("401 인터셉터 — Bearer 모드 refresh", () => {
afterEach(() => clearTokens())
it("refreshToken 있으면 body 로 보내고, 응답 토큰을 저장한 뒤 새 Bearer 로 재시도", async () => {
setTokens({ token: "old", refreshToken: "ref" })
const authHeaders: string[] = []
clientMock.onGet("/users/me").reply((config) => {
authHeaders.push(String(config.headers?.Authorization))
return authHeaders.length === 1
? [401, errorEnvelope({ statusCode: 401, message: "expired" })]
: [200, envelope({ id: "u1" })]
})
globalMock
.onPost(`${BASE}/auth/refresh`)
.reply(200, envelope({ token: "new", refreshToken: "ref", tokenExpirationTime: 1 }))
await apiGet<{ id: string }>("/users/me")
const refreshReq = globalMock.history.post.find((r) => r.url?.endsWith("/auth/refresh"))
expect(JSON.parse(refreshReq?.data as string)).toEqual({ refreshToken: "ref" })
expect(getAccessToken()).toBe("new")
expect(authHeaders).toEqual(["Bearer old", "Bearer new"])
})
})
describe("401 인터셉터 — refresh 실패", () => {
it("refresh가 401이면 sessionExpiryStore에 큐 push + open=true", async () => {
clientMock.onGet("/users/me").reply(401, errorEnvelope({ statusCode: 401, message: "expired" }))