ФУНКЦИИ - NODEDC LAUNCHER: fix global logout handoff

This commit is contained in:
DCCONSTRUCTIONS 2026-05-04 22:22:08 +03:00
parent 8e6d2cea39
commit ab1e0856d6
2 changed files with 71 additions and 6 deletions

View File

@ -148,27 +148,31 @@ app.get("/auth/logout", asyncRoute(async (req, res) => {
res.clearCookie(sessionCookieName, clearCookieOptions());
if (!globalLogout || !config.oidcConfigured) {
setNoStore(res);
res.redirect(returnTo);
return;
}
const discovery = await getOidcDiscovery();
const endSessionEndpoint = discovery.end_session_endpoint;
const loginRedirectUrl = buildLoginRedirectUrl(returnTo, { forceLogin: true });
if (!endSessionEndpoint) {
res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), new URL(returnTo, config.appBaseUrl).toString()));
setNoStore(res);
res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), loginRedirectUrl));
return;
}
const logoutUrl = new URL(endSessionEndpoint);
logoutUrl.searchParams.set("client_id", config.clientId);
logoutUrl.searchParams.set("post_logout_redirect_uri", new URL(returnTo, config.appBaseUrl).toString());
logoutUrl.searchParams.set("post_logout_redirect_uri", loginRedirectUrl);
if (session?.tokenSet.idToken) {
logoutUrl.searchParams.set("id_token_hint", session.tokenSet.idToken);
}
res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), logoutUrl.toString()));
setNoStore(res);
res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), loginRedirectUrl, logoutUrl.toString()));
}));
app.get("/api/me", (req, res) => {
@ -915,9 +919,10 @@ function normalizeLogoutUrl(value) {
}
}
function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl, identityLogoutUrl = null) {
const logoutUrlsJson = JSON.stringify(frontchannelLogoutUrls);
const redirectUrlJson = JSON.stringify(finalRedirectUrl);
const identityLogoutUrlJson = JSON.stringify(identityLogoutUrl);
return `<!doctype html>
<html lang="ru">
@ -941,13 +946,20 @@ function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
<script>
const logoutUrls = ${logoutUrlsJson};
const finalRedirectUrl = ${redirectUrlJson};
const identityLogoutUrl = ${identityLogoutUrlJson};
for (const logoutUrl of logoutUrls) {
fetch(logoutUrl, { mode: "no-cors", credentials: "include", keepalive: true }).catch(() => undefined);
const image = new Image();
image.referrerPolicy = "no-referrer";
image.src = logoutUrl;
}
window.setTimeout(() => window.location.replace(finalRedirectUrl), 700);
if (identityLogoutUrl) {
fetch(identityLogoutUrl, { mode: "no-cors", credentials: "include", keepalive: true }).catch(() => undefined);
const identityImage = new Image();
identityImage.referrerPolicy = "no-referrer";
identityImage.src = identityLogoutUrl;
}
window.setTimeout(() => window.location.replace(finalRedirectUrl), 900);
</script>
</body>
</html>`;
@ -1251,6 +1263,27 @@ function clearCookieOptions() {
return options;
}
function setNoStore(res) {
res.setHeader("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0");
res.setHeader("Pragma", "no-cache");
res.setHeader("Expires", "0");
}
function buildLoginRedirectUrl(returnTo, { forceLogin = false } = {}) {
const loginUrl = new URL("/auth/login", config.appBaseUrl);
const cleanReturnTo = sanitizeReturnTo(returnTo);
if (forceLogin) {
loginUrl.searchParams.set("prompt", "login");
}
if (cleanReturnTo !== "/") {
loginUrl.searchParams.set("returnTo", cleanReturnTo);
}
return loginUrl.toString();
}
function randomBase64Url(size) {
return randomBytes(size).toString("base64url");
}

View File

@ -190,6 +190,38 @@ export function LauncherApp() {
window.location.replace(buildLoginRedirectUrl(authSession.loginUrl));
}, [authSession]);
useEffect(() => {
let isMounted = true;
const validateRestoredSession = (event: PageTransitionEvent) => {
if (!event.persisted) return;
fetchAuthSession()
.then((session) => {
if (!isMounted) return;
if (!session.authenticated) {
window.location.replace(buildLoginRedirectUrl(session.loginUrl));
return;
}
setAuthSession(session);
})
.catch(() => {
if (isMounted) {
window.location.replace(buildLoginRedirectUrl("/auth/login"));
}
});
};
window.addEventListener("pageshow", validateRestoredSession);
return () => {
isMounted = false;
window.removeEventListener("pageshow", validateRestoredSession);
};
}, []);
useEffect(() => {
if (!authSession?.authenticated) return;
@ -493,7 +525,7 @@ export function LauncherApp() {
onToggleAdmin={() => setAdminOpen((current) => !current)}
onOpenShowcase={() => setAdminOpen(false)}
onOpenProfileSettings={() => setProfileSettingsOpen(true)}
onLogout={() => window.location.assign(authSession.logoutUrl)}
onLogout={() => window.location.replace(authSession.logoutUrl)}
/>
<main className="launcher-main">