ИСПРАВЛЕНИЕ - NODEDC LAUNCHER: restore reliable IdP logout handoff

This commit is contained in:
DCCONSTRUCTIONS 2026-05-05 09:01:07 +03:00
parent 96e6a97d38
commit 6e8b05c679
1 changed files with 31 additions and 25 deletions

View File

@ -167,6 +167,20 @@ app.get("/auth/session-sync", (req, res) => {
res.type("html").send(renderSessionSyncBridgePage(allowedOrigins)); res.type("html").send(renderSessionSyncBridgePage(allowedOrigins));
}); });
app.get("/logout", (req, res) => {
const session = getCurrentSession(req);
if (session) {
sessions.delete(session.id);
}
res.clearCookie(sessionCookieName, clearCookieOptions());
setNoStore(res);
res.type("html").send(
"<!doctype html><html><head><meta charset='utf-8'></head><body>NODE.DC Launcher session closed.</body></html>"
);
});
app.get("/auth/logout", asyncRoute(async (req, res) => { app.get("/auth/logout", asyncRoute(async (req, res) => {
const session = getCurrentSession(req); const session = getCurrentSession(req);
const returnTo = sanitizeReturnTo(req.query.returnTo); const returnTo = sanitizeReturnTo(req.query.returnTo);
@ -188,21 +202,19 @@ app.get("/auth/logout", asyncRoute(async (req, res) => {
const endSessionEndpoint = discovery.end_session_endpoint; const endSessionEndpoint = discovery.end_session_endpoint;
const loginRedirectUrl = buildLoginRedirectUrl(returnTo, { forceLogin: true }); const loginRedirectUrl = buildLoginRedirectUrl(returnTo, { forceLogin: true });
if (!endSessionEndpoint) { if (!endSessionEndpoint || !session?.tokenSet.idToken) {
setNoStore(res); setNoStore(res);
res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), null, loginRedirectUrl)); res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), loginRedirectUrl));
return; return;
} }
const logoutUrl = new URL(endSessionEndpoint); const logoutUrl = new URL(endSessionEndpoint);
logoutUrl.searchParams.set("client_id", config.clientId); logoutUrl.searchParams.set("client_id", config.clientId);
logoutUrl.searchParams.set("post_logout_redirect_uri", buildLoggedOutRedirectUrl(returnTo));
if (session?.tokenSet.idToken) { logoutUrl.searchParams.set("id_token_hint", session.tokenSet.idToken);
logoutUrl.searchParams.set("id_token_hint", session.tokenSet.idToken);
}
setNoStore(res); setNoStore(res);
res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), logoutUrl.toString(), loginRedirectUrl)); res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), logoutUrl.toString()));
})); }));
app.get("/api/me", (req, res) => { app.get("/api/me", (req, res) => {
@ -949,9 +961,8 @@ function normalizeLogoutUrl(value) {
} }
} }
function renderGlobalLogoutPage(frontchannelLogoutUrls, identityProviderLogoutUrl, finalRedirectUrl) { function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
const logoutUrlsJson = JSON.stringify(frontchannelLogoutUrls); const logoutUrlsJson = JSON.stringify(frontchannelLogoutUrls);
const identityProviderLogoutUrlJson = JSON.stringify(identityProviderLogoutUrl);
const redirectUrlJson = JSON.stringify(finalRedirectUrl); const redirectUrlJson = JSON.stringify(finalRedirectUrl);
return `<!doctype html> return `<!doctype html>
@ -989,7 +1000,6 @@ function renderGlobalLogoutPage(frontchannelLogoutUrls, identityProviderLogoutUr
localStorage.setItem("nodedc:platform-session-event", JSON.stringify(eventPayload)); localStorage.setItem("nodedc:platform-session-event", JSON.stringify(eventPayload));
} catch {} } catch {}
const logoutUrls = ${logoutUrlsJson}; const logoutUrls = ${logoutUrlsJson};
const identityProviderLogoutUrl = ${identityProviderLogoutUrlJson};
const finalRedirectUrl = ${redirectUrlJson}; const finalRedirectUrl = ${redirectUrlJson};
for (const logoutUrl of logoutUrls) { for (const logoutUrl of logoutUrls) {
fetch(logoutUrl, { mode: "no-cors", credentials: "include", keepalive: true }).catch(() => undefined); fetch(logoutUrl, { mode: "no-cors", credentials: "include", keepalive: true }).catch(() => undefined);
@ -997,21 +1007,6 @@ function renderGlobalLogoutPage(frontchannelLogoutUrls, identityProviderLogoutUr
image.referrerPolicy = "no-referrer"; image.referrerPolicy = "no-referrer";
image.src = logoutUrl; image.src = logoutUrl;
} }
if (identityProviderLogoutUrl) {
fetch(identityProviderLogoutUrl, { mode: "no-cors", credentials: "include", keepalive: true }).catch(() => undefined);
const iframe = document.createElement("iframe");
iframe.title = "NODE.DC identity logout";
iframe.tabIndex = -1;
iframe.setAttribute("aria-hidden", "true");
iframe.style.position = "fixed";
iframe.style.width = "0";
iframe.style.height = "0";
iframe.style.opacity = "0";
iframe.style.pointerEvents = "none";
iframe.style.border = "0";
iframe.src = identityProviderLogoutUrl;
document.body.appendChild(iframe);
}
window.setTimeout(() => window.location.replace(finalRedirectUrl), 1200); window.setTimeout(() => window.location.replace(finalRedirectUrl), 1200);
</script> </script>
</body> </body>
@ -1459,6 +1454,17 @@ function buildLoginRedirectUrl(returnTo, { forceLogin = false } = {}) {
return loginUrl.toString(); return loginUrl.toString();
} }
function buildLoggedOutRedirectUrl(returnTo = "/") {
const loggedOutUrl = new URL("/auth/logged-out", config.appBaseUrl);
const cleanReturnTo = sanitizeReturnTo(returnTo);
if (cleanReturnTo !== "/") {
loggedOutUrl.searchParams.set("returnTo", cleanReturnTo);
}
return loggedOutUrl.toString();
}
function randomBase64Url(size) { function randomBase64Url(size) {
return randomBytes(size).toString("base64url"); return randomBytes(size).toString("base64url");
} }