fix(client): 修复登录会话自动续期
This commit is contained in:
+7
-4
@@ -46,6 +46,11 @@ function mapError(error: unknown) {
|
||||
|
||||
let refreshPromise: Promise<string> | null = null
|
||||
let redirectingToLogin = false
|
||||
const publicAuthPaths = ['/auth/login', '/auth/register', '/auth/refresh', '/auth/impersonations/exchange']
|
||||
|
||||
function isPublicAuthRequest(url?: string) {
|
||||
return publicAuthPaths.some((path) => url?.endsWith(path))
|
||||
}
|
||||
|
||||
function redirectToLoginAfterSessionExpiry() {
|
||||
if (redirectingToLogin || typeof window === 'undefined') return
|
||||
@@ -98,7 +103,7 @@ export async function refreshAuthenticatedSession() {
|
||||
|
||||
transport.interceptors.request.use((config) => {
|
||||
const token = getAccessToken()
|
||||
if (token) config.headers.Authorization = `Bearer ${token}`
|
||||
if (token && !isPublicAuthRequest(config.url)) config.headers.Authorization = `Bearer ${token}`
|
||||
return config
|
||||
})
|
||||
|
||||
@@ -106,9 +111,7 @@ transport.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error: AxiosError) => {
|
||||
const request = error.config as RetryableRequest | undefined
|
||||
const publicAuthPaths = ['/auth/login', '/auth/register', '/auth/refresh', '/auth/impersonations/exchange']
|
||||
const isPublicAuthRequest = publicAuthPaths.some((path) => request?.url?.endsWith(path))
|
||||
if (error.response?.status !== 401 || !request || request._retriedAfterRefresh || isPublicAuthRequest) {
|
||||
if (error.response?.status !== 401 || !request || request._retriedAfterRefresh || isPublicAuthRequest(request.url)) {
|
||||
throw mapError(error)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import test from 'node:test'
|
||||
import {
|
||||
ACCESS_TOKEN_KEY,
|
||||
REFRESH_TOKEN_KEY,
|
||||
REMEMBERED_LOGIN_ID_KEY,
|
||||
clearSessionTokens,
|
||||
getRememberedLoginId,
|
||||
persistRememberedLoginId,
|
||||
persistTokenPair,
|
||||
} from './session.ts'
|
||||
|
||||
function storage() {
|
||||
const values = new Map()
|
||||
return {
|
||||
getItem: (key) => values.get(key) ?? null,
|
||||
setItem: (key, value) => values.set(key, String(value)),
|
||||
removeItem: (key) => values.delete(key),
|
||||
}
|
||||
}
|
||||
|
||||
function installStorage() {
|
||||
globalThis.localStorage = storage()
|
||||
globalThis.sessionStorage = storage()
|
||||
}
|
||||
|
||||
test('persists the normal rotating session independently of remembered account preference', () => {
|
||||
installStorage()
|
||||
persistTokenPair({
|
||||
tokenType: 'Bearer',
|
||||
accessToken: 'access-token',
|
||||
expiresIn: 1440,
|
||||
refreshToken: 'refresh-token',
|
||||
refreshExpiresIn: 604800,
|
||||
})
|
||||
|
||||
assert.equal(localStorage.getItem(ACCESS_TOKEN_KEY), 'access-token')
|
||||
assert.equal(localStorage.getItem(REFRESH_TOKEN_KEY), 'refresh-token')
|
||||
assert.equal(sessionStorage.getItem(ACCESS_TOKEN_KEY), null)
|
||||
assert.equal(sessionStorage.getItem(REFRESH_TOKEN_KEY), null)
|
||||
})
|
||||
|
||||
test('remembers only the login id and keeps it when session tokens are cleared', () => {
|
||||
installStorage()
|
||||
persistRememberedLoginId('merchant-user', true)
|
||||
localStorage.setItem(ACCESS_TOKEN_KEY, 'access-token')
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, 'refresh-token')
|
||||
|
||||
clearSessionTokens()
|
||||
|
||||
assert.equal(getRememberedLoginId(), 'merchant-user')
|
||||
assert.equal(localStorage.getItem(REMEMBERED_LOGIN_ID_KEY), 'merchant-user')
|
||||
assert.equal(localStorage.getItem(ACCESS_TOKEN_KEY), null)
|
||||
assert.equal(localStorage.getItem(REFRESH_TOKEN_KEY), null)
|
||||
|
||||
persistRememberedLoginId('merchant-user', false)
|
||||
assert.equal(getRememberedLoginId(), '')
|
||||
})
|
||||
+15
-6
@@ -2,6 +2,7 @@ export const ACCESS_TOKEN_KEY = 'cc-client-access-token'
|
||||
export const REFRESH_TOKEN_KEY = 'cc-client-refresh-token'
|
||||
export const OVERSEAS_ACCESS_TOKEN_KEY = 'cc-client-overseas-access-token'
|
||||
export const OVERSEAS_EXPIRES_AT_KEY = 'cc-client-overseas-expires-at'
|
||||
export const REMEMBERED_LOGIN_ID_KEY = 'cc-client-remembered-login-id'
|
||||
|
||||
export interface TokenPair {
|
||||
tokenType: string
|
||||
@@ -33,17 +34,25 @@ export function getOverseasAccessToken() {
|
||||
return storage.getItem(OVERSEAS_ACCESS_TOKEN_KEY) || ''
|
||||
}
|
||||
|
||||
export function persistTokenPair(tokens: TokenPair, remember?: boolean) {
|
||||
const storage = remember === undefined ? activeStorage() : remember ? localStorage : sessionStorage
|
||||
export function persistTokenPair(tokens: TokenPair) {
|
||||
clearSessionTokens()
|
||||
storage.setItem(ACCESS_TOKEN_KEY, tokens.accessToken)
|
||||
storage.setItem(REFRESH_TOKEN_KEY, tokens.refreshToken)
|
||||
localStorage.setItem(ACCESS_TOKEN_KEY, tokens.accessToken)
|
||||
localStorage.setItem(REFRESH_TOKEN_KEY, tokens.refreshToken)
|
||||
if (tokens.overseasAccessToken && Number(tokens.overseasExpiresIn) > 0) {
|
||||
storage.setItem(OVERSEAS_ACCESS_TOKEN_KEY, tokens.overseasAccessToken)
|
||||
storage.setItem(OVERSEAS_EXPIRES_AT_KEY, String(Date.now() + Number(tokens.overseasExpiresIn) * 1000))
|
||||
localStorage.setItem(OVERSEAS_ACCESS_TOKEN_KEY, tokens.overseasAccessToken)
|
||||
localStorage.setItem(OVERSEAS_EXPIRES_AT_KEY, String(Date.now() + Number(tokens.overseasExpiresIn) * 1000))
|
||||
}
|
||||
}
|
||||
|
||||
export function getRememberedLoginId() {
|
||||
return localStorage.getItem(REMEMBERED_LOGIN_ID_KEY) || ''
|
||||
}
|
||||
|
||||
export function persistRememberedLoginId(loginId: string, remember: boolean) {
|
||||
if (remember) localStorage.setItem(REMEMBERED_LOGIN_ID_KEY, loginId)
|
||||
else localStorage.removeItem(REMEMBERED_LOGIN_ID_KEY)
|
||||
}
|
||||
|
||||
export function persistTemporaryAccessToken(accessToken: string) {
|
||||
clearSessionTokens()
|
||||
sessionStorage.setItem(ACCESS_TOKEN_KEY, accessToken)
|
||||
|
||||
Reference in New Issue
Block a user