diff --git a/public/storage/launcher-data.json b/public/storage/launcher-data.json index 07d5cc3..9a050fd 100644 --- a/public/storage/launcher-data.json +++ b/public/storage/launcher-data.json @@ -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 } ] } diff --git a/server/dev-server.mjs b/server/dev-server.mjs index ca86d35..714fffd 100644 --- a/server/dev-server.mjs +++ b/server/dev-server.mjs @@ -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) { - Выход из NODE.DC + NODE.DC - -
-

Выходим из NODE.DC

-

Закрываем сессии подключённых приложений и платформенный вход.

-
+ `; diff --git a/src/app/LauncherApp.tsx b/src/app/LauncherApp.tsx index 8a88a64..f502cae 100644 --- a/src/app/LauncherApp.tsx +++ b/src/app/LauncherApp.tsx @@ -516,7 +516,7 @@ export function LauncherApp() { } if (!authSession) { - return ; + return null; } if (!authSession.authenticated) {