From 16700543561e83c65b8a475797d4d0586ba9826a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Sun, 8 Mar 2026 19:29:04 +0800 Subject: [PATCH] Clear all auth cookies when API token refresh fails before redirecting to login Co-Authored-By: Codex --- src/api/client.js | 2 ++ tests/api/client.test.js | 56 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 tests/api/client.test.js diff --git a/src/api/client.js b/src/api/client.js index df5d38f..7a90a58 100644 --- a/src/api/client.js +++ b/src/api/client.js @@ -88,6 +88,8 @@ apiClient.interceptors.response.use( // Refresh failed: clear auth and redirect to login deleteCookie("luciaToken"); + deleteCookie("luciaRefreshToken"); + deleteCookie("isLuciaLoggedIn"); window.location.href = "/login"; return Promise.reject(refreshError); } diff --git a/tests/api/client.test.js b/tests/api/client.test.js new file mode 100644 index 0000000..1918c4c --- /dev/null +++ b/tests/api/client.test.js @@ -0,0 +1,56 @@ +// The Lucia project. +// Copyright 2026-2026 DSP, inc. All rights reserved. +// Authors: +// imacat.yang@dsp.im (imacat), 2026/03/08 + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const { mockAxiosCreate, mockRequestUse, mockResponseUse, mockDeleteCookie } = + vi.hoisted(() => ({ + mockAxiosCreate: vi.fn(), + mockRequestUse: vi.fn(), + mockResponseUse: vi.fn(), + mockDeleteCookie: vi.fn(), + })); + +vi.mock("axios", () => ({ + default: { + create: mockAxiosCreate, + }, +})); + +vi.mock("@/utils/cookieUtil.js", () => ({ + getCookie: vi.fn(() => null), + deleteCookie: mockDeleteCookie, +})); + +vi.mock("@/api/auth.js", () => ({ + refreshTokenAndGetNew: vi.fn().mockRejectedValue(new Error("401")), +})); + +describe("apiClient response interceptor", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockAxiosCreate.mockReturnValue({ + interceptors: { + request: { use: mockRequestUse }, + response: { use: mockResponseUse }, + }, + }); + }); + + it("clears all auth cookies when token refresh fails", async () => { + await import("@/api/client.js"); + const rejectedHandler = mockResponseUse.mock.calls[0][1]; + await expect( + rejectedHandler({ + response: { status: 401 }, + config: { url: "/api/my-account", headers: {} }, + }), + ).rejects.toThrow("401"); + + expect(mockDeleteCookie).toHaveBeenCalledWith("luciaToken"); + expect(mockDeleteCookie).toHaveBeenCalledWith("luciaRefreshToken"); + expect(mockDeleteCookie).toHaveBeenCalledWith("isLuciaLoggedIn"); + }); +});