ФУНКЦИИ - NODEDC LAUNCHER: sync logout across tabs
This commit is contained in:
@@ -145,6 +145,16 @@ app.get("/auth/logged-out", (req, res) => {
|
||||
res.redirect(buildLoginRedirectUrl(returnTo, { forceLogin: true }));
|
||||
});
|
||||
|
||||
app.get("/auth/session-sync", (req, res) => {
|
||||
const allowedOrigins = getSessionSyncAllowedOrigins();
|
||||
setNoStore(res);
|
||||
res.setHeader(
|
||||
"Content-Security-Policy",
|
||||
`default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; frame-ancestors ${allowedOrigins.join(" ")}`
|
||||
);
|
||||
res.type("html").send(renderSessionSyncBridgePage(allowedOrigins));
|
||||
});
|
||||
|
||||
app.get("/auth/logout", asyncRoute(async (req, res) => {
|
||||
const session = getCurrentSession(req);
|
||||
const returnTo = sanitizeReturnTo(req.query.returnTo);
|
||||
@@ -954,6 +964,20 @@ function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
|
||||
<p>Закрываем сессии подключённых приложений и платформенный вход.</p>
|
||||
</main>
|
||||
<script>
|
||||
const eventPayload = {
|
||||
type: "nodedc:session:logout",
|
||||
id: globalThis.crypto?.randomUUID ? globalThis.crypto.randomUUID() : String(Date.now()) + "-" + Math.random().toString(36).slice(2),
|
||||
source: "launcher-global-logout",
|
||||
createdAt: Date.now()
|
||||
};
|
||||
try {
|
||||
const channel = new BroadcastChannel("nodedc-platform-session");
|
||||
channel.postMessage(eventPayload);
|
||||
channel.close();
|
||||
} catch {}
|
||||
try {
|
||||
localStorage.setItem("nodedc:platform-session-event", JSON.stringify(eventPayload));
|
||||
} catch {}
|
||||
const logoutUrls = ${logoutUrlsJson};
|
||||
const finalRedirectUrl = ${redirectUrlJson};
|
||||
for (const logoutUrl of logoutUrls) {
|
||||
@@ -968,6 +992,91 @@ function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function renderSessionSyncBridgePage(allowedOrigins) {
|
||||
const allowedOriginsJson = JSON.stringify(allowedOrigins);
|
||||
|
||||
return `<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>NODE.DC session sync</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
const allowedOrigins = new Set(${allowedOriginsJson});
|
||||
const parentOrigin = (() => {
|
||||
try {
|
||||
return new URL(document.referrer).origin;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
})();
|
||||
const channelName = "nodedc-platform-session";
|
||||
const storageKey = "nodedc:platform-session-event";
|
||||
let lastEventId = null;
|
||||
|
||||
function isAllowedOrigin(origin) {
|
||||
return allowedOrigins.has(origin);
|
||||
}
|
||||
|
||||
function isLogoutEvent(payload) {
|
||||
return payload && payload.type === "nodedc:session:logout" && typeof payload.id === "string";
|
||||
}
|
||||
|
||||
function forwardToParent(payload) {
|
||||
if (!isLogoutEvent(payload) || payload.id === lastEventId || !isAllowedOrigin(parentOrigin)) return;
|
||||
lastEventId = payload.id;
|
||||
window.parent.postMessage(payload, parentOrigin);
|
||||
}
|
||||
|
||||
function publish(payload) {
|
||||
if (!isLogoutEvent(payload)) return;
|
||||
try {
|
||||
const channel = new BroadcastChannel(channelName);
|
||||
channel.postMessage(payload);
|
||||
channel.close();
|
||||
} catch {}
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify(payload));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (!isAllowedOrigin(event.origin) || !isLogoutEvent(event.data)) return;
|
||||
publish(event.data);
|
||||
});
|
||||
|
||||
try {
|
||||
const channel = new BroadcastChannel(channelName);
|
||||
channel.addEventListener("message", (event) => forwardToParent(event.data));
|
||||
} catch {}
|
||||
|
||||
window.addEventListener("storage", (event) => {
|
||||
if (event.key !== storageKey || !event.newValue) return;
|
||||
try {
|
||||
forwardToParent(JSON.parse(event.newValue));
|
||||
} catch {}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
function getSessionSyncAllowedOrigins() {
|
||||
const origins = new Set([new URL(config.appBaseUrl).origin]);
|
||||
|
||||
for (const logoutUrl of getFrontchannelLogoutUrls()) {
|
||||
try {
|
||||
origins.add(new URL(logoutUrl).origin);
|
||||
} catch {
|
||||
void 0;
|
||||
}
|
||||
}
|
||||
|
||||
return [...origins];
|
||||
}
|
||||
|
||||
function readLauncherData() {
|
||||
const dataPath = join(projectRoot, "public", "storage", "launcher-data.json");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user