ФУНКЦИИ - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: устойчивый global logout и скрытие служебных экранов
This commit is contained in:
parent
583f1547e1
commit
09400f7db8
|
|
@ -207,24 +207,6 @@
|
|||
"coverMediaKind": "image",
|
||||
"coverMediaSource": "file",
|
||||
"coverMediaFileName": "1777711943125-691830c2-NODEDC_DT_MMAP.png"
|
||||
},
|
||||
{
|
||||
"id": "service_dm",
|
||||
"slug": "digital-modules",
|
||||
"title": "Digital Modules",
|
||||
"subtitle": "Будущие модули",
|
||||
"description": "Скрытый каталог модулей для root-admin preview.",
|
||||
"fullDescription": "Площадка для будущих цифровых модулей NODE.DC.",
|
||||
"url": "https://dm.handhdc.ru/sso/launch",
|
||||
"launchUrl": "https://dm.handhdc.ru/sso/launch",
|
||||
"accentColor": "#FF9AC2",
|
||||
"fallbackGradient": "linear-gradient(135deg, rgba(255, 154, 194, 0.78), rgba(76, 41, 64, 0.9) 44%, #090B0F 86%)",
|
||||
"status": "hidden",
|
||||
"order": 60,
|
||||
"authentikApplicationSlug": "digital-modules",
|
||||
"authentikGroupName": "service-digital-modules",
|
||||
"createdAt": "2026-04-10T10:00:00Z",
|
||||
"updatedAt": "2026-05-01T17:59:10.713Z"
|
||||
}
|
||||
],
|
||||
"grants": [
|
||||
|
|
@ -1140,6 +1122,18 @@
|
|||
"clientId": null,
|
||||
"result": "success",
|
||||
"details": "Groups: nodedc:launcher:user, nodedc:taskmanager:user, service-digital-twin"
|
||||
},
|
||||
{
|
||||
"id": "audit_digital_modules",
|
||||
"at": "2026-05-04T19:15:22.791Z",
|
||||
"actorUserId": "user_root",
|
||||
"actorName": "DC SUDO",
|
||||
"action": "Удалён сервис",
|
||||
"objectType": "service",
|
||||
"objectName": "Digital Modules",
|
||||
"clientId": null,
|
||||
"result": "warning",
|
||||
"details": null
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -185,6 +185,7 @@ app.get("/auth/logout", asyncRoute(async (req, res) => {
|
|||
const session = getCurrentSession(req);
|
||||
const returnTo = sanitizeReturnTo(req.query.returnTo);
|
||||
const globalLogout = req.query.global === "1" || req.query.global === "true";
|
||||
const taskSessionLogoutPromise = globalLogout ? notifyTaskSessionLogout(session) : Promise.resolve();
|
||||
|
||||
if (session) {
|
||||
sessions.delete(session.id);
|
||||
|
|
@ -200,6 +201,7 @@ app.get("/auth/logout", asyncRoute(async (req, res) => {
|
|||
|
||||
const discovery = await getOidcDiscovery();
|
||||
const logoutUrl = buildOidcLogoutUrl(discovery, returnTo, session?.tokenSet.idToken);
|
||||
await taskSessionLogoutPromise;
|
||||
|
||||
setNoStore(res);
|
||||
res.type("html").send(renderGlobalLogoutPage(getFrontchannelLogoutUrls(), logoutUrl.toString()));
|
||||
|
|
@ -600,6 +602,9 @@ function readConfig() {
|
|||
taskLogoutUrl:
|
||||
process.env.TASK_LOGOUT_URL ??
|
||||
`${(process.env.TASK_BASE_URL ?? `http://${process.env.TASK_DOMAIN ?? "task.local.nodedc"}`).replace(/\/$/, "")}/logout`,
|
||||
taskInternalLogoutUrl:
|
||||
process.env.TASK_INTERNAL_LOGOUT_URL ??
|
||||
`${(process.env.TASK_BASE_URL ?? `http://${process.env.TASK_DOMAIN ?? "task.local.nodedc"}`).replace(/\/$/, "")}/api/internal/nodedc/logout/`,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -939,6 +944,44 @@ function getFrontchannelLogoutUrls() {
|
|||
return [...new Set(urls.map(normalizeLogoutUrl).filter(Boolean))];
|
||||
}
|
||||
|
||||
async function notifyTaskSessionLogout(session) {
|
||||
if (!session?.user || !config.internalAccessToken || !config.taskInternalLogoutUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const runtimeContext = getRuntimeSessionContext(session);
|
||||
const user = runtimeContext.user ?? session.user;
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 1500);
|
||||
|
||||
try {
|
||||
const response = await fetch(config.taskInternalLogoutUrl, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Accept": "application/json",
|
||||
"Authorization": `Bearer ${config.internalAccessToken}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
source: "launcher-global-logout",
|
||||
subject: session.user.sub,
|
||||
email: user.email || session.user.email || null,
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn(`Task internal logout returned ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error?.name !== "AbortError") {
|
||||
console.warn(error instanceof Error ? error.message : "Task internal logout failed");
|
||||
}
|
||||
} finally {
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLogoutUrl(value) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
|
|
@ -958,20 +1001,12 @@ function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
|
|||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Выход из NODE.DC</title>
|
||||
<title>NODE.DC</title>
|
||||
<style>
|
||||
html,body{height:100%;margin:0;background:#050606;color:#f4f4f4;font-family:Inter,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
||||
body{display:grid;place-items:center}
|
||||
main{max-width:28rem;padding:2rem;text-align:center}
|
||||
h1{margin:0 0 .75rem;font-size:1.5rem}
|
||||
p{margin:0;color:#a6a6a6;line-height:1.5}
|
||||
html,body{height:100%;margin:0;background:#0b0f0e;color:#0b0f0e}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>Выходим из NODE.DC</h1>
|
||||
<p>Закрываем сессии подключённых приложений и платформенный вход.</p>
|
||||
</main>
|
||||
<body aria-busy="true">
|
||||
<script>
|
||||
const eventPayload = {
|
||||
type: "nodedc:session:logout",
|
||||
|
|
@ -995,7 +1030,7 @@ function renderGlobalLogoutPage(frontchannelLogoutUrls, finalRedirectUrl) {
|
|||
image.referrerPolicy = "no-referrer";
|
||||
image.src = logoutUrl;
|
||||
}
|
||||
window.setTimeout(() => window.location.replace(finalRedirectUrl), 1200);
|
||||
window.setTimeout(() => window.location.replace(finalRedirectUrl), 50);
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ export function LauncherApp() {
|
|||
}
|
||||
|
||||
if (!authSession) {
|
||||
return <AuthStateScreen title="Проверяем сессию NODE.DC" description="Платформа подготавливает рабочую область и список приложений." />;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!authSession.authenticated) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue