fix(web): redirect to auth page instead of explore on session expiry

Fixes #5617
This commit is contained in:
Steven 2026-02-10 21:15:44 +08:00
parent 27063d4850
commit 47e2ace690
3 changed files with 16 additions and 16 deletions

View File

@ -67,7 +67,7 @@ const refreshTransport = createConnectTransport({
const refreshAuthClient = createClient(AuthService, refreshTransport);
export async function refreshAccessToken(): Promise<void> {
async function doRefreshAccessToken(): 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) {

View File

@ -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

View File

@ -1,5 +1,4 @@
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);
}
}