fix(R61): 增加 session hint cookie 消除 bootstrap refresh 401

这个提交包含在:
2026-04-12 19:29:10 +08:00
父节点 dabb03e55b
当前提交 c3f1152824
共修改 2 个文件,包含 41 行新增和 2 行删除
+24 -2
查看文件
@@ -58,6 +58,17 @@ function isWindowAvailable() {
return typeof window !== "undefined";
}
function hasSessionHint() {
return typeof document !== "undefined" && document.cookie.includes("has_session=1");
}
function clearSessionHint() {
if (typeof document === "undefined") {
return;
}
document.cookie = "has_session=; path=/; max-age=0";
}
function readMustChangePasswordFromStorage() {
return isWindowAvailable() && window.localStorage.getItem(MUST_CHANGE_PASSWORD_STORAGE_KEY) === "1";
}
@@ -168,7 +179,7 @@ export function AuthProvider({ children }: PropsWithChildren) {
}
}, []);
const clearSession = useCallback(() => {
const markUnauthenticatedReady = useCallback(() => {
sessionGenerationRef.current += 1;
bootstrapPromiseRef.current = null;
refreshRequestRef.current = null;
@@ -189,6 +200,12 @@ export function AuthProvider({ children }: PropsWithChildren) {
}));
}, [clearProactiveRefreshTimer]);
const clearSession = useCallback(() => {
clearSessionHint();
markUnauthenticatedReady();
void clearPwaRuntimeCaches();
}, [markUnauthenticatedReady]);
const commitAuthenticatedUser = useCallback(
(accessToken: string, me: MeResponse, mustChangePassword: boolean) => {
const permissions = resolvePermissionsForRoles(me.roleCodes ?? []);
@@ -301,6 +318,11 @@ export function AuthProvider({ children }: PropsWithChildren) {
return;
}
if (!hasSessionHint() && (!isWindowAvailable() || window.navigator.onLine !== false)) {
markUnauthenticatedReady();
return;
}
if (!bootstrapPromiseRef.current) {
bootstrapPromiseRef.current = (async () => {
try {
@@ -323,7 +345,7 @@ export function AuthProvider({ children }: PropsWithChildren) {
}
await bootstrapPromiseRef.current;
}, [activateOfflineSnapshot, clearSession, runRefresh, scheduleProactiveRefresh]);
}, [activateOfflineSnapshot, clearSession, markUnauthenticatedReady, runRefresh, scheduleProactiveRefresh]);
const login = useCallback(
async (identifier: string, password: string) => {
+17
查看文件
@@ -118,6 +118,16 @@ function refreshCookieOptions(config) {
};
}
function sessionHintCookieOptions(config) {
return {
httpOnly: false,
sameSite: "strict",
secure: config.cookieSecure,
path: "/",
maxAge: config.refreshTtlSeconds,
};
}
function oidcCookieOptions(config) {
return {
httpOnly: true,
@@ -420,6 +430,7 @@ async function authRoutes(fastify) {
refreshToken,
refreshCookieOptions(fastify.config),
);
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
const accessToken = fastify.issueAccessToken(employee);
return sendData(reply, request, {
@@ -540,6 +551,7 @@ async function authRoutes(fastify) {
}
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
reply.clearCookie("has_session", sessionHintCookieOptions(fastify.config));
throw new AppError({
code: "AUTH_005",
message: "Refresh Token 无效/已撤销",
@@ -607,6 +619,7 @@ async function authRoutes(fastify) {
newRefreshToken,
refreshCookieOptions(fastify.config),
);
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
const accessToken = fastify.issueAccessToken(employee);
return sendData(reply, request, {
@@ -857,6 +870,7 @@ async function authRoutes(fastify) {
refreshToken,
refreshCookieOptions(fastify.config),
);
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
reply.clearCookie(oidcCookieName, oidcCookieOptions(fastify.config));
const origin = resolvePrimaryAppOrigin(fastify.config, request);
@@ -894,6 +908,7 @@ async function authRoutes(fastify) {
}
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
reply.clearCookie("has_session", sessionHintCookieOptions(fastify.config));
return sendData(reply, request, {});
});
@@ -1019,6 +1034,7 @@ async function authRoutes(fastify) {
refreshToken,
refreshCookieOptions(fastify.config),
);
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
const accessToken = fastify.issueAccessToken(updatedEmployee);
return sendData(reply, request, {
@@ -1245,6 +1261,7 @@ async function authRoutes(fastify) {
}
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
reply.clearCookie("has_session", sessionHintCookieOptions(fastify.config));
return sendData(reply, request, {});
});
}