Add service login session sync

This commit is contained in:
DCCONSTRUCTIONS 2026-06-23 19:28:47 +03:00
parent 1b58246897
commit 5b23410549
3 changed files with 36 additions and 3 deletions

View File

@ -74,6 +74,7 @@ const bimViewerService = {
url: "https://bim.nodedc.tech", url: "https://bim.nodedc.tech",
launchUrl: "https://bim.nodedc.tech", launchUrl: "https://bim.nodedc.tech",
logoutUrl: "https://bim.nodedc.tech/logout", logoutUrl: "https://bim.nodedc.tech/logout",
loginSyncUrl: "https://bim.nodedc.tech/auth/login-sync",
accentColor: "#8FE3C8", accentColor: "#8FE3C8",
fallbackGradient: "linear-gradient(132deg, rgba(143, 227, 200, 0.78), rgba(24, 70, 64, 0.9) 46%, #080B0F 84%)", fallbackGradient: "linear-gradient(132deg, rgba(143, 227, 200, 0.78), rgba(24, 70, 64, 0.9) 46%, #080B0F 84%)",
status: "active", status: "active",
@ -2500,6 +2501,7 @@ function normalizeService(service) {
? "https://bim.nodedc.tech" ? "https://bim.nodedc.tech"
: service.launchUrl, : service.launchUrl,
logoutUrl: service.logoutUrl || "https://bim.nodedc.tech/logout", logoutUrl: service.logoutUrl || "https://bim.nodedc.tech/logout",
loginSyncUrl: service.loginSyncUrl || "https://bim.nodedc.tech/auth/login-sync",
authentikApplicationSlug: service.authentikApplicationSlug || "bim-viewer", authentikApplicationSlug: service.authentikApplicationSlug || "bim-viewer",
authentikGroupName: service.authentikGroupName || "nodedc:bim:access", authentikGroupName: service.authentikGroupName || "nodedc:bim:access",
}; };
@ -3663,6 +3665,7 @@ function sanitizeServicePatch(payload, service) {
"url", "url",
"launchUrl", "launchUrl",
"logoutUrl", "logoutUrl",
"loginSyncUrl",
"iconUrl", "iconUrl",
"coverImageUrl", "coverImageUrl",
"coverMediaKind", "coverMediaKind",

View File

@ -214,7 +214,7 @@ app.get("/auth/callback", asyncRoute(async (req, res) => {
expiresAt: tokenSet.expires_in ? Date.now() + Number(tokenSet.expires_in) * 1000 : null, expiresAt: tokenSet.expires_in ? Date.now() + Number(tokenSet.expires_in) * 1000 : null,
}); });
setNoStore(res); setNoStore(res);
res.type("html").send(renderLoginCompletePage(pendingLogin.returnTo)); res.type("html").send(renderLoginCompletePage(pendingLogin.returnTo, getFrontchannelLoginSyncUrls()));
})); }));
app.get("/auth/logged-out", (req, res) => { app.get("/auth/logged-out", (req, res) => {
@ -3924,6 +3924,20 @@ function getFrontchannelLogoutUrls() {
return [...new Set(urls.map(normalizeLogoutUrl).filter(Boolean))]; return [...new Set(urls.map(normalizeLogoutUrl).filter(Boolean))];
} }
function getFrontchannelLoginSyncUrls() {
const launcherData = readLauncherData();
const services = Array.isArray(launcherData?.services) ? launcherData.services : [];
const urls = [];
for (const service of services) {
if (typeof service.loginSyncUrl === "string" && service.loginSyncUrl.trim()) {
urls.push(service.loginSyncUrl.trim());
}
}
return [...new Set(urls.map(normalizeLogoutUrl).filter(Boolean))];
}
async function notifyTaskSessionLogout(session) { async function notifyTaskSessionLogout(session) {
if (!session?.user || !config.internalAccessToken || !config.taskInternalLogoutUrl) { if (!session?.user || !config.internalAccessToken || !config.taskInternalLogoutUrl) {
return; return;
@ -4016,8 +4030,9 @@ function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
</html>`; </html>`;
} }
function renderLoginCompletePage(returnTo) { function renderLoginCompletePage(returnTo, frontchannelLoginSyncUrls = []) {
const redirectUrlJson = JSON.stringify(sanitizeReturnTo(returnTo)); const redirectUrlJson = JSON.stringify(sanitizeReturnTo(returnTo));
const loginSyncUrlsJson = JSON.stringify(frontchannelLoginSyncUrls);
return `<!doctype html> return `<!doctype html>
<html lang="ru"> <html lang="ru">
@ -4045,7 +4060,14 @@ function renderLoginCompletePage(returnTo) {
try { try {
localStorage.setItem("nodedc:platform-session-event", JSON.stringify(eventPayload)); localStorage.setItem("nodedc:platform-session-event", JSON.stringify(eventPayload));
} catch {} } catch {}
window.setTimeout(() => window.location.replace(${redirectUrlJson}), 30); const loginSyncUrls = ${loginSyncUrlsJson};
for (const loginSyncUrl of loginSyncUrls) {
fetch(loginSyncUrl, { mode: "no-cors", credentials: "include", keepalive: true }).catch(() => undefined);
const image = new Image();
image.referrerPolicy = "no-referrer";
image.src = loginSyncUrl;
}
window.setTimeout(() => window.location.replace(${redirectUrlJson}), 180);
</script> </script>
</body> </body>
</html>`; </html>`;
@ -4134,6 +4156,13 @@ function getSessionSyncAllowedOrigins() {
void 0; void 0;
} }
} }
for (const loginSyncUrl of getFrontchannelLoginSyncUrls()) {
try {
origins.add(new URL(loginSyncUrl).origin);
} catch {
void 0;
}
}
return [...origins]; return [...origins];
} }

View File

@ -14,6 +14,7 @@ export interface Service {
url: string; url: string;
launchUrl?: string | null; launchUrl?: string | null;
logoutUrl?: string | null; logoutUrl?: string | null;
loginSyncUrl?: string | null;
iconUrl?: string | null; iconUrl?: string | null;
coverImageUrl?: string | null; coverImageUrl?: string | null;
coverMediaKind?: MediaKind | null; coverMediaKind?: MediaKind | null;