From 5187eb44492bb97542b1269082a4b96f1d13734a Mon Sep 17 00:00:00 2001 From: NFish Date: Wed, 30 Oct 2024 17:52:31 +0800 Subject: [PATCH 1/7] fix: disable SWR revalidate --- web/app/components/swr-initor.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web/app/components/swr-initor.tsx b/web/app/components/swr-initor.tsx index ff9a7b832f2bed..e05f78122c366e 100644 --- a/web/app/components/swr-initor.tsx +++ b/web/app/components/swr-initor.tsx @@ -85,6 +85,8 @@ const SwrInitor = ({ {children} From c431e3555081be85714fb29ca4fff59256b0eaa6 Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 1 Nov 2024 12:38:15 +0800 Subject: [PATCH 2/7] chore: redesign the logic of refreshing access_token --- web/app/components/swr-initor.tsx | 30 +------ web/app/signin/normalForm.tsx | 5 +- web/hooks/use-refresh-token.ts | 99 ----------------------- web/service/base.ts | 128 ++++++++++++++++++------------ web/service/refresh-token.ts | 64 +++++++++++++++ 5 files changed, 142 insertions(+), 184 deletions(-) delete mode 100644 web/hooks/use-refresh-token.ts create mode 100644 web/service/refresh-token.ts diff --git a/web/app/components/swr-initor.tsx b/web/app/components/swr-initor.tsx index e05f78122c366e..e7dfda101efdce 100644 --- a/web/app/components/swr-initor.tsx +++ b/web/app/components/swr-initor.tsx @@ -4,7 +4,6 @@ import { SWRConfig } from 'swr' import { useCallback, useEffect, useState } from 'react' import type { ReactNode } from 'react' import { usePathname, useRouter, useSearchParams } from 'next/navigation' -import useRefreshToken from '@/hooks/use-refresh-token' import { fetchSetupStatus } from '@/service/common' type SwrInitorProps = { @@ -16,11 +15,6 @@ const SwrInitor = ({ const router = useRouter() const searchParams = useSearchParams() const pathname = usePathname() - const { getNewAccessToken } = useRefreshToken() - const consoleToken = searchParams.get('access_token') - const refreshToken = searchParams.get('refresh_token') - const consoleTokenFromLocalStorage = localStorage?.getItem('console_token') - const refreshTokenFromLocalStorage = localStorage?.getItem('refresh_token') const [init, setInit] = useState(false) const isSetupFinished = useCallback(async () => { @@ -41,25 +35,6 @@ const SwrInitor = ({ } }, []) - const setRefreshToken = useCallback(async () => { - try { - if (!(consoleToken || refreshToken || consoleTokenFromLocalStorage || refreshTokenFromLocalStorage)) - return Promise.reject(new Error('No token found')) - - if (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage) - await getNewAccessToken() - - if (consoleToken && refreshToken) { - localStorage.setItem('console_token', consoleToken) - localStorage.setItem('refresh_token', refreshToken) - await getNewAccessToken() - } - } - catch (error) { - return Promise.reject(error) - } - }, [consoleToken, refreshToken, consoleTokenFromLocalStorage, refreshTokenFromLocalStorage, getNewAccessToken]) - useEffect(() => { (async () => { try { @@ -68,7 +43,6 @@ const SwrInitor = ({ router.replace('/install') return } - await setRefreshToken() if (searchParams.has('access_token') || searchParams.has('refresh_token')) router.replace(pathname) @@ -78,15 +52,13 @@ const SwrInitor = ({ router.replace('/signin') } })() - }, [isSetupFinished, setRefreshToken, router, pathname, searchParams]) + }, [isSetupFinished, router, pathname, searchParams]) return init ? ( {children} diff --git a/web/app/signin/normalForm.tsx b/web/app/signin/normalForm.tsx index c0f2d89b37910c..f4f46c68ba6543 100644 --- a/web/app/signin/normalForm.tsx +++ b/web/app/signin/normalForm.tsx @@ -12,11 +12,9 @@ import cn from '@/utils/classnames' import { getSystemFeatures, invitationCheck } from '@/service/common' import { defaultSystemFeatures } from '@/types/feature' import Toast from '@/app/components/base/toast' -import useRefreshToken from '@/hooks/use-refresh-token' import { IS_CE_EDITION } from '@/config' const NormalForm = () => { - const { getNewAccessToken } = useRefreshToken() const { t } = useTranslation() const router = useRouter() const searchParams = useSearchParams() @@ -38,7 +36,6 @@ const NormalForm = () => { if (consoleToken && refreshToken) { localStorage.setItem('console_token', consoleToken) localStorage.setItem('refresh_token', refreshToken) - getNewAccessToken() router.replace('/apps') return } @@ -71,7 +68,7 @@ const NormalForm = () => { setSystemFeatures(defaultSystemFeatures) } finally { setIsLoading(false) } - }, [consoleToken, refreshToken, message, router, invite_token, isInviteLink, getNewAccessToken]) + }, [consoleToken, refreshToken, message, router, invite_token, isInviteLink]) useEffect(() => { init() }, [init]) diff --git a/web/hooks/use-refresh-token.ts b/web/hooks/use-refresh-token.ts deleted file mode 100644 index 53dc4faf0049f8..00000000000000 --- a/web/hooks/use-refresh-token.ts +++ /dev/null @@ -1,99 +0,0 @@ -'use client' -import { useCallback, useEffect, useRef } from 'react' -import { jwtDecode } from 'jwt-decode' -import dayjs from 'dayjs' -import utc from 'dayjs/plugin/utc' -import { useRouter } from 'next/navigation' -import type { CommonResponse } from '@/models/common' -import { fetchNewToken } from '@/service/common' -import { fetchWithRetry } from '@/utils' - -dayjs.extend(utc) - -const useRefreshToken = () => { - const router = useRouter() - const timer = useRef() - const advanceTime = useRef(5 * 60 * 1000) - - const getExpireTime = useCallback((token: string) => { - if (!token) - return 0 - const decoded = jwtDecode(token) - return (decoded.exp || 0) * 1000 - }, []) - - const getCurrentTimeStamp = useCallback(() => { - return dayjs.utc().valueOf() - }, []) - - const handleError = useCallback(() => { - localStorage?.removeItem('is_refreshing') - localStorage?.removeItem('console_token') - localStorage?.removeItem('refresh_token') - router.replace('/signin') - }, []) - - const getNewAccessToken = useCallback(async () => { - const currentAccessToken = localStorage?.getItem('console_token') - const currentRefreshToken = localStorage?.getItem('refresh_token') - if (!currentAccessToken || !currentRefreshToken) { - handleError() - return new Error('No access token or refresh token found') - } - if (localStorage?.getItem('is_refreshing') === '1') { - clearTimeout(timer.current) - timer.current = setTimeout(() => { - getNewAccessToken() - }, 1000) - return null - } - const currentTokenExpireTime = getExpireTime(currentAccessToken) - if (getCurrentTimeStamp() + advanceTime.current > currentTokenExpireTime) { - localStorage?.setItem('is_refreshing', '1') - const [e, res] = await fetchWithRetry(fetchNewToken({ - body: { refresh_token: currentRefreshToken }, - }) as Promise) - if (e) { - handleError() - return e - } - const { access_token, refresh_token } = res.data - localStorage?.setItem('is_refreshing', '0') - localStorage?.setItem('console_token', access_token) - localStorage?.setItem('refresh_token', refresh_token) - const newTokenExpireTime = getExpireTime(access_token) - clearTimeout(timer.current) - timer.current = setTimeout(() => { - getNewAccessToken() - }, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp()) - } - else { - const newTokenExpireTime = getExpireTime(currentAccessToken) - clearTimeout(timer.current) - timer.current = setTimeout(() => { - getNewAccessToken() - }, newTokenExpireTime - advanceTime.current - getCurrentTimeStamp()) - } - return null - }, [getExpireTime, getCurrentTimeStamp, handleError]) - - const handleVisibilityChange = useCallback(() => { - if (document.visibilityState === 'visible') - getNewAccessToken() - }, []) - - useEffect(() => { - window.addEventListener('visibilitychange', handleVisibilityChange) - return () => { - window.removeEventListener('visibilitychange', handleVisibilityChange) - clearTimeout(timer.current) - localStorage?.removeItem('is_refreshing') - } - }, []) - - return { - getNewAccessToken, - } -} - -export default useRefreshToken diff --git a/web/service/base.ts b/web/service/base.ts index fbdd5c1fd35848..e173325d008ad5 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -1,3 +1,4 @@ +import { refreshAccessTokenOrRelogin } from './refresh-token' import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config' import Toast from '@/app/components/base/toast' import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type' @@ -356,39 +357,6 @@ const baseFetch = ( if (!/^(2|3)\d{2}$/.test(String(res.status))) { const bodyJson = res.json() switch (res.status) { - case 401: { - if (isPublicAPI) { - return bodyJson.then((data: ResponseError) => { - if (data.code === 'web_sso_auth_required') - requiredWebSSOLogin() - - if (data.code === 'unauthorized') { - removeAccessToken() - globalThis.location.reload() - } - - return Promise.reject(data) - }) - } - const loginUrl = `${globalThis.location.origin}/signin` - bodyJson.then((data: ResponseError) => { - if (data.code === 'init_validate_failed' && IS_CE_EDITION && !silent) - Toast.notify({ type: 'error', message: data.message, duration: 4000 }) - else if (data.code === 'not_init_validated' && IS_CE_EDITION) - globalThis.location.href = `${globalThis.location.origin}/init` - else if (data.code === 'not_setup' && IS_CE_EDITION) - globalThis.location.href = `${globalThis.location.origin}/install` - else if (location.pathname !== '/signin' || !IS_CE_EDITION) - globalThis.location.href = loginUrl - else if (!silent) - Toast.notify({ type: 'error', message: data.message }) - }).catch(() => { - // Handle any other errors - globalThis.location.href = loginUrl - }) - - break - } case 403: bodyJson.then((data: ResponseError) => { if (!silent) @@ -484,7 +452,9 @@ export const upload = (options: any, isPublicAPI?: boolean, url?: string, search export const ssePost = ( url: string, fetchOptions: FetchOptionType, - { + otherOptions: IOtherOptions, +) => { + const { isPublicAPI = false, onData, onCompleted, @@ -507,8 +477,7 @@ export const ssePost = ( onTextReplace, onError, getAbortController, - }: IOtherOptions, -) => { + } = otherOptions const abortController = new AbortController() const options = Object.assign({}, baseOptions, { @@ -532,21 +501,29 @@ export const ssePost = ( globalThis.fetch(urlWithPrefix, options as RequestInit) .then((res) => { if (!/^(2|3)\d{2}$/.test(String(res.status))) { - res.json().then((data: any) => { - if (isPublicAPI) { - if (data.code === 'web_sso_auth_required') - requiredWebSSOLogin() - - if (data.code === 'unauthorized') { - removeAccessToken() - globalThis.location.reload() - } - if (res.status === 401) - return - } - Toast.notify({ type: 'error', message: data.message || 'Server Error' }) - }) - onError?.('Server Error') + if (res.status === 401) { + refreshAccessTokenOrRelogin().then(() => { + ssePost(url, fetchOptions, otherOptions) + }).catch(() => { + res.json().then((data: any) => { + if (isPublicAPI) { + if (data.code === 'web_sso_auth_required') + requiredWebSSOLogin() + + if (data.code === 'unauthorized') { + removeAccessToken() + globalThis.location.reload() + } + } + }) + }) + } + else { + res.json().then((data) => { + Toast.notify({ type: 'error', message: data.message || 'Server Error' }) + }) + onError?.('Server Error') + } return } return handleStream(res, (str: string, isFirstMessage: boolean, moreInfo: IOnDataMoreInfo) => { @@ -568,7 +545,54 @@ export const ssePost = ( // base request export const request = (url: string, options = {}, otherOptions?: IOtherOptions) => { - return baseFetch(url, options, otherOptions || {}) + return new Promise((resolve, reject) => { + const otherOptionsForBaseFetch = otherOptions || {} + baseFetch(url, options, otherOptionsForBaseFetch).then(resolve).catch((errResp) => { + if (errResp?.status === 401) { + return refreshAccessTokenOrRelogin().then(() => { + baseFetch(url, options, otherOptionsForBaseFetch).then(resolve).catch(reject) + }).catch(() => { + const { + isPublicAPI = false, + silent, + } = otherOptionsForBaseFetch + const bodyJson = errResp.json() + if (isPublicAPI) { + return bodyJson.then((data: ResponseError) => { + if (data.code === 'web_sso_auth_required') + requiredWebSSOLogin() + + if (data.code === 'unauthorized') { + removeAccessToken() + globalThis.location.reload() + } + + return Promise.reject(data) + }) + } + const loginUrl = `${globalThis.location.origin}/signin` + bodyJson.then((data: ResponseError) => { + if (data.code === 'init_validate_failed' && IS_CE_EDITION && !silent) + Toast.notify({ type: 'error', message: data.message, duration: 4000 }) + else if (data.code === 'not_init_validated' && IS_CE_EDITION) + globalThis.location.href = `${globalThis.location.origin}/init` + else if (data.code === 'not_setup' && IS_CE_EDITION) + globalThis.location.href = `${globalThis.location.origin}/install` + else if (location.pathname !== '/signin' || !IS_CE_EDITION) + globalThis.location.href = loginUrl + else if (!silent) + Toast.notify({ type: 'error', message: data.message }) + }).catch(() => { + // Handle any other errors + globalThis.location.href = loginUrl + }) + }) + } + else { + reject(errResp) + } + }) + }) } // request methods diff --git a/web/service/refresh-token.ts b/web/service/refresh-token.ts new file mode 100644 index 00000000000000..2a5d958e3dfc31 --- /dev/null +++ b/web/service/refresh-token.ts @@ -0,0 +1,64 @@ +import { apiPrefix } from '@/config' +import { fetchWithRetry } from '@/utils' + +let isRefreshing = false +function waitUntilTokenRefreshed() { + return new Promise((resolve, reject) => { + function _check() { + const isRefreshingSign = localStorage.getItem('is_refreshing') + if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) { + setTimeout(() => { + _check() + }, 1000) + } + else { + resolve() + } + } + _check() + }) +} + +// only one request can send +async function getNewAccessToken(): Promise { + const isRefreshingSign = localStorage.getItem('is_refreshing') + if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) { + await waitUntilTokenRefreshed() + } + else { + globalThis.localStorage.setItem('is_refreshing', '1') + isRefreshing = true + const refresh_token = globalThis.localStorage.getItem('refresh_token') + // do not use baseFetch for refresh token, if return 401, request will in a loop + const [error, ret] = await fetchWithRetry(globalThis.fetch(`${apiPrefix}/refresh-token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json;utf-8', + }, + body: JSON.stringify({ refresh_token }), + })) + if (error) { + return Promise.reject(error) + } + else { + if (ret.status === 401) + return Promise.reject(ret) + + const { data } = await ret.json() + globalThis.localStorage.setItem('console_token', data.access_token) + globalThis.localStorage.setItem('refresh_token', data.refresh_token) + isRefreshing = false + globalThis.localStorage.removeItem('is_refreshing') + } + } +} + +export async function refreshAccessTokenOrRelogin() { + try { + await getNewAccessToken() + } + catch (error) { + console.error(error) + return Promise.reject(error) + } +} From ce31786ca72f835df37d2022841a189c4fce1b16 Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 1 Nov 2024 14:57:09 +0800 Subject: [PATCH 3/7] fix: bug fix --- web/app/components/swr-initor.tsx | 5 ++- web/service/refresh-token.ts | 58 ++++++++++++++++++------------- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/web/app/components/swr-initor.tsx b/web/app/components/swr-initor.tsx index e7dfda101efdce..91a991a44ca923 100644 --- a/web/app/components/swr-initor.tsx +++ b/web/app/components/swr-initor.tsx @@ -43,8 +43,11 @@ const SwrInitor = ({ router.replace('/install') return } - if (searchParams.has('access_token') || searchParams.has('refresh_token')) + if (searchParams.has('access_token') || searchParams.has('refresh_token')) { + localStorage.setItem('console_token', searchParams.get('access_token')) + localStorage.setItem('refresh_token', searchParams.get('refresh_token')) router.replace(pathname) + } setInit(true) } diff --git a/web/service/refresh-token.ts b/web/service/refresh-token.ts index 2a5d958e3dfc31..ef3dfea53bac1e 100644 --- a/web/service/refresh-token.ts +++ b/web/service/refresh-token.ts @@ -21,36 +21,44 @@ function waitUntilTokenRefreshed() { // only one request can send async function getNewAccessToken(): Promise { - const isRefreshingSign = localStorage.getItem('is_refreshing') - if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) { - await waitUntilTokenRefreshed() - } - else { - globalThis.localStorage.setItem('is_refreshing', '1') - isRefreshing = true - const refresh_token = globalThis.localStorage.getItem('refresh_token') - // do not use baseFetch for refresh token, if return 401, request will in a loop - const [error, ret] = await fetchWithRetry(globalThis.fetch(`${apiPrefix}/refresh-token`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json;utf-8', - }, - body: JSON.stringify({ refresh_token }), - })) - if (error) { - return Promise.reject(error) + try { + const isRefreshingSign = localStorage.getItem('is_refreshing') + if ((isRefreshingSign && isRefreshingSign === '1') || isRefreshing) { + await waitUntilTokenRefreshed() } else { - if (ret.status === 401) - return Promise.reject(ret) + globalThis.localStorage.setItem('is_refreshing', '1') + isRefreshing = true + const refresh_token = globalThis.localStorage.getItem('refresh_token') + // do not use baseFetch for refresh token, if return 401, request will in a loop + const [error, ret] = await fetchWithRetry(globalThis.fetch(`${apiPrefix}/refresh-token`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json;utf-8', + }, + body: JSON.stringify({ refresh_token }), + })) + if (error) { + return Promise.reject(error) + } + else { + if (ret.status === 401) + return Promise.reject(ret) - const { data } = await ret.json() - globalThis.localStorage.setItem('console_token', data.access_token) - globalThis.localStorage.setItem('refresh_token', data.refresh_token) - isRefreshing = false - globalThis.localStorage.removeItem('is_refreshing') + const { data } = await ret.json() + globalThis.localStorage.setItem('console_token', data.access_token) + globalThis.localStorage.setItem('refresh_token', data.refresh_token) + } } } + catch (error) { + console.error(error) + return Promise.reject(error) + } + finally { + isRefreshing = false + globalThis.localStorage.removeItem('is_refreshing') + } } export async function refreshAccessTokenOrRelogin() { From d640e8037f819fddaf5ab8ce4a93fcf47f6433b6 Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 1 Nov 2024 15:08:20 +0800 Subject: [PATCH 4/7] fix: decode access_token & refresh_token before save to localStorage --- web/app/components/swr-initor.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/app/components/swr-initor.tsx b/web/app/components/swr-initor.tsx index 91a991a44ca923..5ee06fc2e81a27 100644 --- a/web/app/components/swr-initor.tsx +++ b/web/app/components/swr-initor.tsx @@ -44,8 +44,10 @@ const SwrInitor = ({ return } if (searchParams.has('access_token') || searchParams.has('refresh_token')) { - localStorage.setItem('console_token', searchParams.get('access_token')) - localStorage.setItem('refresh_token', searchParams.get('refresh_token')) + const consoleToken = decodeURIComponent(searchParams.get('access_token') || '') + const refreshToken = decodeURIComponent(searchParams.get('refresh_token') || '') + consoleToken && localStorage.setItem('console_token', consoleToken) + refreshToken && localStorage.setItem('refresh_token', refreshToken) router.replace(pathname) } From 8ffc7631b2614fc924c61651e06fda151302469d Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 1 Nov 2024 15:38:58 +0800 Subject: [PATCH 5/7] fix: apply request timeout settings to refresh token. --- web/service/base.ts | 6 ++++-- web/service/refresh-token.ts | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/web/service/base.ts b/web/service/base.ts index e173325d008ad5..fcf8d8bd7d1a45 100644 --- a/web/service/base.ts +++ b/web/service/base.ts @@ -357,6 +357,8 @@ const baseFetch = ( if (!/^(2|3)\d{2}$/.test(String(res.status))) { const bodyJson = res.json() switch (res.status) { + case 401: + return Promise.reject(resClone) case 403: bodyJson.then((data: ResponseError) => { if (!silent) @@ -502,7 +504,7 @@ export const ssePost = ( .then((res) => { if (!/^(2|3)\d{2}$/.test(String(res.status))) { if (res.status === 401) { - refreshAccessTokenOrRelogin().then(() => { + refreshAccessTokenOrRelogin(TIME_OUT).then(() => { ssePost(url, fetchOptions, otherOptions) }).catch(() => { res.json().then((data: any) => { @@ -549,7 +551,7 @@ export const request = (url: string, options = {}, otherOptions?: IOtherOptio const otherOptionsForBaseFetch = otherOptions || {} baseFetch(url, options, otherOptionsForBaseFetch).then(resolve).catch((errResp) => { if (errResp?.status === 401) { - return refreshAccessTokenOrRelogin().then(() => { + return refreshAccessTokenOrRelogin(TIME_OUT).then(() => { baseFetch(url, options, otherOptionsForBaseFetch).then(resolve).catch(reject) }).catch(() => { const { diff --git a/web/service/refresh-token.ts b/web/service/refresh-token.ts index ef3dfea53bac1e..8bd22150414cea 100644 --- a/web/service/refresh-token.ts +++ b/web/service/refresh-token.ts @@ -30,7 +30,12 @@ async function getNewAccessToken(): Promise { globalThis.localStorage.setItem('is_refreshing', '1') isRefreshing = true const refresh_token = globalThis.localStorage.getItem('refresh_token') - // do not use baseFetch for refresh token, if return 401, request will in a loop + + // Do not use baseFetch to refresh tokens. + // If a 401 response occurs and baseFetch itself attempts to refresh the token, + // it can lead to an infinite loop if the refresh attempt also returns 401. + // To avoid this, handle token refresh separately in a dedicated function + // that does not call baseFetch and uses a single retry mechanism. const [error, ret] = await fetchWithRetry(globalThis.fetch(`${apiPrefix}/refresh-token`, { method: 'POST', headers: { @@ -61,12 +66,10 @@ async function getNewAccessToken(): Promise { } } -export async function refreshAccessTokenOrRelogin() { - try { - await getNewAccessToken() - } - catch (error) { - console.error(error) - return Promise.reject(error) - } +export async function refreshAccessTokenOrRelogin(timeout: number) { + return Promise.race([new Promise((resolve, reject) => setTimeout(() => { + isRefreshing = false + globalThis.localStorage.removeItem('is_refreshing') + reject(new Error('request timeout')) + }, timeout)), getNewAccessToken()]) } From 8e4c6e78c73d1d8cecdfd4d5745fb09c6d4b0576 Mon Sep 17 00:00:00 2001 From: NFish Date: Fri, 1 Nov 2024 15:49:33 +0800 Subject: [PATCH 6/7] fix: redirect to the signin page if no access_token found --- web/app/components/swr-initor.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/web/app/components/swr-initor.tsx b/web/app/components/swr-initor.tsx index 5ee06fc2e81a27..80f1b12ed8800b 100644 --- a/web/app/components/swr-initor.tsx +++ b/web/app/components/swr-initor.tsx @@ -14,6 +14,10 @@ const SwrInitor = ({ }: SwrInitorProps) => { const router = useRouter() const searchParams = useSearchParams() + const consoleToken = decodeURIComponent(searchParams.get('access_token') || '') + const refreshToken = decodeURIComponent(searchParams.get('refresh_token') || '') + const consoleTokenFromLocalStorage = localStorage?.getItem('console_token') + const refreshTokenFromLocalStorage = localStorage?.getItem('refresh_token') const pathname = usePathname() const [init, setInit] = useState(false) @@ -43,9 +47,11 @@ const SwrInitor = ({ router.replace('/install') return } + if (!((consoleToken && refreshToken) || (consoleTokenFromLocalStorage && refreshTokenFromLocalStorage))) { + router.replace('/signin') + return + } if (searchParams.has('access_token') || searchParams.has('refresh_token')) { - const consoleToken = decodeURIComponent(searchParams.get('access_token') || '') - const refreshToken = decodeURIComponent(searchParams.get('refresh_token') || '') consoleToken && localStorage.setItem('console_token', consoleToken) refreshToken && localStorage.setItem('refresh_token', refreshToken) router.replace(pathname) From 878ebd83ed1497b26020cfc730482cff42d5d724 Mon Sep 17 00:00:00 2001 From: NFish Date: Tue, 5 Nov 2024 10:53:48 +0800 Subject: [PATCH 7/7] fix: clear all related data from localStorage on user logout --- web/app/account/avatar.tsx | 5 +++-- web/app/components/header/account-dropdown/index.tsx | 5 +++-- web/app/components/swr-initor.tsx | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/web/app/account/avatar.tsx b/web/app/account/avatar.tsx index 2b9aeba5dab353..544e43ab27f99f 100644 --- a/web/app/account/avatar.tsx +++ b/web/app/account/avatar.tsx @@ -23,8 +23,9 @@ export default function AppSelector() { params: {}, }) - if (localStorage?.getItem('console_token')) - localStorage.removeItem('console_token') + localStorage.removeItem('setup_status') + localStorage.removeItem('console_token') + localStorage.removeItem('refresh_token') router.push('/signin') } diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index 712906ebae3815..14f079c0f29a95 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -47,8 +47,9 @@ export default function AppSelector({ isMobile }: IAppSelector) { params: {}, }) - if (localStorage?.getItem('console_token')) - localStorage.removeItem('console_token') + localStorage.removeItem('setup_status') + localStorage.removeItem('console_token') + localStorage.removeItem('refresh_token') router.push('/signin') } diff --git a/web/app/components/swr-initor.tsx b/web/app/components/swr-initor.tsx index 80f1b12ed8800b..2a119df9963d58 100644 --- a/web/app/components/swr-initor.tsx +++ b/web/app/components/swr-initor.tsx @@ -63,7 +63,7 @@ const SwrInitor = ({ router.replace('/signin') } })() - }, [isSetupFinished, router, pathname, searchParams]) + }, [isSetupFinished, router, pathname, searchParams, consoleToken, refreshToken, consoleTokenFromLocalStorage, refreshTokenFromLocalStorage]) return init ? (