Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
C
canifa_note
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
canifa_note
Commits
47e2ace6
Commit
47e2ace6
authored
Feb 10, 2026
by
Steven
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
fix(web): redirect to auth page instead of explore on session expiry
Fixes #5617
parent
27063d48
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
16 additions
and
16 deletions
+16
-16
connect.ts
web/src/connect.ts
+10
-2
RootLayout.tsx
web/src/layouts/RootLayout.tsx
+2
-4
auth-redirect.ts
web/src/utils/auth-redirect.ts
+4
-10
No files found.
web/src/connect.ts
View file @
47e2ace6
...
...
@@ -67,7 +67,7 @@ const refreshTransport = createConnectTransport({
const
refreshAuthClient
=
createClient
(
AuthService
,
refreshTransport
);
export
async
function
r
efreshAccessToken
():
Promise
<
void
>
{
async
function
doR
efreshAccessToken
():
Promise
<
void
>
{
const
response
=
await
refreshAuthClient
.
refreshToken
({});
if
(
!
response
.
accessToken
)
{
...
...
@@ -78,6 +78,14 @@ export async function refreshAccessToken(): Promise<void> {
setAccessToken
(
response
.
accessToken
,
expiresAt
);
}
// All callers go through the manager to deduplicate concurrent refresh requests.
// This prevents race conditions between useTokenRefreshOnFocus (proactive refresh
// on tab focus) and the auth interceptor (reactive refresh on 401), which could
// otherwise send duplicate requests that conflict with server-side token rotation.
export
async
function
refreshAccessToken
():
Promise
<
void
>
{
return
tokenRefreshManager
.
refresh
(
doRefreshAccessToken
);
}
// ============================================================================
// Authentication Interceptor
// ============================================================================
...
...
@@ -104,7 +112,7 @@ const authInterceptor: Interceptor = (next) => async (req) => {
}
try
{
await
tokenRefreshManager
.
refresh
(
refreshAccessToken
);
await
refreshAccessToken
(
);
const
newToken
=
getAccessToken
();
if
(
!
newToken
)
{
...
...
web/src/layouts/RootLayout.tsx
View file @
47e2ace6
...
...
@@ -2,7 +2,6 @@ import { useEffect, useMemo } from "react";
import
{
Outlet
,
useLocation
,
useSearchParams
}
from
"react-router-dom"
;
import
usePrevious
from
"react-use/lib/usePrevious"
;
import
Navigation
from
"@/components/Navigation"
;
import
{
useInstance
}
from
"@/contexts/InstanceContext"
;
import
{
useMemoFilterContext
}
from
"@/contexts/MemoFilterContext"
;
import
useCurrentUser
from
"@/hooks/useCurrentUser"
;
import
useMediaQuery
from
"@/hooks/useMediaQuery"
;
...
...
@@ -14,16 +13,15 @@ const RootLayout = () => {
const
[
searchParams
]
=
useSearchParams
();
const
sm
=
useMediaQuery
(
"sm"
);
const
currentUser
=
useCurrentUser
();
const
{
memoRelatedSetting
}
=
useInstance
();
const
{
removeFilter
}
=
useMemoFilterContext
();
const
pathname
=
useMemo
(()
=>
location
.
pathname
,
[
location
.
pathname
]);
const
prevPathname
=
usePrevious
(
pathname
);
useEffect
(()
=>
{
if
(
!
currentUser
&&
memoRelatedSetting
.
disallowPublicVisibility
)
{
if
(
!
currentUser
)
{
redirectOnAuthFailure
();
}
},
[
currentUser
,
memoRelatedSetting
.
disallowPublicVisibility
]);
},
[
currentUser
]);
useEffect
(()
=>
{
// When the route changes and there is no filter in the search params, remove all filters
...
...
web/src/utils/auth-redirect.ts
View file @
47e2ace6
import
{
clearAccessToken
}
from
"@/auth-state"
;
import
{
getInstanceConfig
}
from
"@/instance-config"
;
import
{
ROUTES
}
from
"@/router/routes"
;
const
PUBLIC_ROUTES
=
[
...
...
@@ -27,15 +26,10 @@ export function redirectOnAuthFailure(): void {
return
;
}
const
disallowPublicVisibility
=
getInstanceConfig
().
memoRelatedSetting
.
disallowPublicVisibility
;
const
target
=
disallowPublicVisibility
?
ROUTES
.
AUTH
:
ROUTES
.
EXPLORE
;
// Only redirect if it's a private route or disallowPublicVisibility is enabled
if
(
disallowPublicVisibility
||
isPrivateRoute
(
currentPath
))
{
// Clear access token to ensure user is fully logged out
// This prevents the issue where user appears logged in but sees only public memos
// See: https://github.com/usememos/memos/issues/5565
// Always redirect to auth page on auth failure - the user's session expired
// and they should re-authenticate rather than being sent to explore.
if
(
isPrivateRoute
(
currentPath
))
{
clearAccessToken
();
window
.
location
.
replace
(
target
);
window
.
location
.
replace
(
ROUTES
.
AUTH
);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment