fix(R61): 增加 session hint cookie 消除 bootstrap refresh 401
这个提交包含在:
@@ -58,6 +58,17 @@ function isWindowAvailable() {
|
|||||||
return typeof window !== "undefined";
|
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() {
|
function readMustChangePasswordFromStorage() {
|
||||||
return isWindowAvailable() && window.localStorage.getItem(MUST_CHANGE_PASSWORD_STORAGE_KEY) === "1";
|
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;
|
sessionGenerationRef.current += 1;
|
||||||
bootstrapPromiseRef.current = null;
|
bootstrapPromiseRef.current = null;
|
||||||
refreshRequestRef.current = null;
|
refreshRequestRef.current = null;
|
||||||
@@ -189,6 +200,12 @@ export function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
}));
|
}));
|
||||||
}, [clearProactiveRefreshTimer]);
|
}, [clearProactiveRefreshTimer]);
|
||||||
|
|
||||||
|
const clearSession = useCallback(() => {
|
||||||
|
clearSessionHint();
|
||||||
|
markUnauthenticatedReady();
|
||||||
|
void clearPwaRuntimeCaches();
|
||||||
|
}, [markUnauthenticatedReady]);
|
||||||
|
|
||||||
const commitAuthenticatedUser = useCallback(
|
const commitAuthenticatedUser = useCallback(
|
||||||
(accessToken: string, me: MeResponse, mustChangePassword: boolean) => {
|
(accessToken: string, me: MeResponse, mustChangePassword: boolean) => {
|
||||||
const permissions = resolvePermissionsForRoles(me.roleCodes ?? []);
|
const permissions = resolvePermissionsForRoles(me.roleCodes ?? []);
|
||||||
@@ -301,6 +318,11 @@ export function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasSessionHint() && (!isWindowAvailable() || window.navigator.onLine !== false)) {
|
||||||
|
markUnauthenticatedReady();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!bootstrapPromiseRef.current) {
|
if (!bootstrapPromiseRef.current) {
|
||||||
bootstrapPromiseRef.current = (async () => {
|
bootstrapPromiseRef.current = (async () => {
|
||||||
try {
|
try {
|
||||||
@@ -323,7 +345,7 @@ export function AuthProvider({ children }: PropsWithChildren) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await bootstrapPromiseRef.current;
|
await bootstrapPromiseRef.current;
|
||||||
}, [activateOfflineSnapshot, clearSession, runRefresh, scheduleProactiveRefresh]);
|
}, [activateOfflineSnapshot, clearSession, markUnauthenticatedReady, runRefresh, scheduleProactiveRefresh]);
|
||||||
|
|
||||||
const login = useCallback(
|
const login = useCallback(
|
||||||
async (identifier: string, password: string) => {
|
async (identifier: string, password: string) => {
|
||||||
|
|||||||
@@ -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) {
|
function oidcCookieOptions(config) {
|
||||||
return {
|
return {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
@@ -420,6 +430,7 @@ async function authRoutes(fastify) {
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
refreshCookieOptions(fastify.config),
|
refreshCookieOptions(fastify.config),
|
||||||
);
|
);
|
||||||
|
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
|
||||||
|
|
||||||
const accessToken = fastify.issueAccessToken(employee);
|
const accessToken = fastify.issueAccessToken(employee);
|
||||||
return sendData(reply, request, {
|
return sendData(reply, request, {
|
||||||
@@ -540,6 +551,7 @@ async function authRoutes(fastify) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
|
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
|
||||||
|
reply.clearCookie("has_session", sessionHintCookieOptions(fastify.config));
|
||||||
throw new AppError({
|
throw new AppError({
|
||||||
code: "AUTH_005",
|
code: "AUTH_005",
|
||||||
message: "Refresh Token 无效/已撤销",
|
message: "Refresh Token 无效/已撤销",
|
||||||
@@ -607,6 +619,7 @@ async function authRoutes(fastify) {
|
|||||||
newRefreshToken,
|
newRefreshToken,
|
||||||
refreshCookieOptions(fastify.config),
|
refreshCookieOptions(fastify.config),
|
||||||
);
|
);
|
||||||
|
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
|
||||||
|
|
||||||
const accessToken = fastify.issueAccessToken(employee);
|
const accessToken = fastify.issueAccessToken(employee);
|
||||||
return sendData(reply, request, {
|
return sendData(reply, request, {
|
||||||
@@ -857,6 +870,7 @@ async function authRoutes(fastify) {
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
refreshCookieOptions(fastify.config),
|
refreshCookieOptions(fastify.config),
|
||||||
);
|
);
|
||||||
|
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
|
||||||
reply.clearCookie(oidcCookieName, oidcCookieOptions(fastify.config));
|
reply.clearCookie(oidcCookieName, oidcCookieOptions(fastify.config));
|
||||||
|
|
||||||
const origin = resolvePrimaryAppOrigin(fastify.config, request);
|
const origin = resolvePrimaryAppOrigin(fastify.config, request);
|
||||||
@@ -894,6 +908,7 @@ async function authRoutes(fastify) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
|
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
|
||||||
|
reply.clearCookie("has_session", sessionHintCookieOptions(fastify.config));
|
||||||
return sendData(reply, request, {});
|
return sendData(reply, request, {});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1019,6 +1034,7 @@ async function authRoutes(fastify) {
|
|||||||
refreshToken,
|
refreshToken,
|
||||||
refreshCookieOptions(fastify.config),
|
refreshCookieOptions(fastify.config),
|
||||||
);
|
);
|
||||||
|
reply.setCookie("has_session", "1", sessionHintCookieOptions(fastify.config));
|
||||||
|
|
||||||
const accessToken = fastify.issueAccessToken(updatedEmployee);
|
const accessToken = fastify.issueAccessToken(updatedEmployee);
|
||||||
return sendData(reply, request, {
|
return sendData(reply, request, {
|
||||||
@@ -1245,6 +1261,7 @@ async function authRoutes(fastify) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
|
reply.clearCookie(fastify.config.refreshCookieName, refreshCookieOptions(fastify.config));
|
||||||
|
reply.clearCookie("has_session", sessionHintCookieOptions(fastify.config));
|
||||||
return sendData(reply, request, {});
|
return sendData(reply, request, {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
在新工单中引用
屏蔽一个用户