diff --git a/package.json b/package.json index e17b58b..149e113 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ipwhitelist", - "version": "0.5.156", + "version": "0.5.157", "description": "IP WhiteList microservice for cloud provider", "main": "server.js", "scripts": { diff --git a/v2/src/router/index.js b/v2/src/router/index.js index f942e97..8201ea2 100644 --- a/v2/src/router/index.js +++ b/v2/src/router/index.js @@ -1,30 +1,5 @@ -// ═══════════════════════════════════════════════════════════════════════════════ -// V2 — resolveContext middleware -// -// ЭТО: Express middleware — извлекает контекст из сессии. -// НЕ: БД, бизнес-логика. -// -// ЗАЧЕМ: -// 1. Единственное место где сессия превращается в req.* поля. -// 2. user/ и admin/ не парсят сессию сами — получают готовые req.email, req.clientId. -// 3. Поддержка имперсонации (админ действует от имени компании). -// -// ПОРЯДОК: -// 1. Нет сессии → 302 /v2/login -// 2. Обычный юзер → req.email = session.user.email, req.clientId = activeClientId -// 3. Админ → req.isAdmin = true (если adminMode в сессии) -// 4. Имперсонация → req.email = originalUserEmail, req.impersonatedBy = админ -// -// ВЫХОД (поля на req): -// req.email — почта (при имперсонации — originalUserEmail) -// req.clientId — W-номер текущей компании -// req.companyName — название компании -// req.isAdmin — флаг админа -// req.isImpersonated — флаг имперсонации -// req.impersonatedBy — почта админа (null если нет имперсонации) -// req.allClientIds — все W-номера юзера -// req.profiles — профили из IAM -// ═══════════════════════════════════════════════════════════════════════════════ +const https = require('https'); +const config = require('../config'); function resolveContext(req, res, next) { const u = req.session && req.session.user; @@ -32,18 +7,92 @@ function resolveContext(req, res, next) { // Нет сессии — нет доступа if (!u) return res.redirect('/v2/login'); - // Имперсонация: админ действует от имени компании - // u.originalUserEmail — реальный админ - // u.impersonatedCompanyId — W-номер компании + // ── Проверка имперсонации через IAM ────────────────────────────── + // Если есть токен — запрашиваем свежие данные, чтобы обнаружить имперсонацию + // которая могла начаться после логина (ЛК сессия отдельно от whitelist). + const token = req.session && req.session.token; + if (token) { + checkImpersonation(token, req, u).catch(() => { /* fallback — тихо */ }); + } + + applyContext(req, u); + next(); +} + +/** + * Запрашивает IAM и обновляет сессию если имперсонация изменилась. + */ +async function checkImpersonation(token, req, u) { + const url = config.iamApiBase + '/auth/user'; + const uObj = new URL(url); + const data = await new Promise((resolve, reject) => { + https.get({ + hostname: uObj.hostname, port: 443, path: uObj.pathname, + headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' }, + }, res => { + let d = ''; + res.on('data', c => d += c); + res.on('end', () => { + if (res.statusCode !== 200) return reject(new Error(res.statusCode + ': ' + d.slice(0, 200))); + try { resolve(JSON.parse(d)); } catch (e) { reject(e); } + }); + }).on('error', reject); + }); + + const imp = data.impersonation || {}; + const newIsImp = !!(imp.is_impersonated); + const oldIsImp = !!(u.originalUserEmail && u.impersonatedCompanyId); + + // Статус не изменился — ничего не делаем + if (newIsImp === oldIsImp) return; + + if (newIsImp) { + // Имперсонация началась — обновляем сессию данными таргета + const profiles = data.profiles || []; + const activeProfile = profiles.find(p => p.is_active_profile) || profiles[0] || {}; + const ui = data.userInfo || {}; + + req.session.user = { + ...u, + email: ui.email || '', + clientId: ui.clientID || activeProfile.client_id || '', + allClientIds: profiles.map(p => p.client_id).filter(Boolean), + activeClientId: ui.clientID || activeProfile.client_id || '', + activeProfileId: activeProfile.id || null, + companyId: ui.companyId || '', + companyName: ui.company || activeProfile.company_name || '', + isAdmin: !!ui.isAdmin, + isImpersonated: true, + impersonationType: imp.type || null, + impersonatedCompanyId: imp.impersonatedCompanyId || '', + originalUserEmail: imp.originalUserEmail || u.originalUserEmail || '', + originalUserFullName: imp.originalUserFullName || u.originalUserFullName || '', + originalUserCompany: imp.originalUserCompany || u.originalUserCompany || '', + fio: ui.fio || null, + profiles, + }; + } else { + // Имперсонация завершилась — убираем флаги, восстанавливаем оригинальные данные + req.session.user = { + ...u, + isImpersonated: false, + impersonationType: null, + impersonatedCompanyId: null, + originalUserEmail: null, + originalUserFullName: null, + originalUserCompany: null, + }; + } +} + +function applyContext(req, u) { const isImpersonated = !!(u.originalUserEmail && u.impersonatedCompanyId); req.email = u.email || ''; - // activeClientId может переопределять impersonatedCompanyId при switchTo req.clientId = isImpersonated ? (u.activeClientId || u.impersonatedCompanyId || u.clientId || '') : (u.activeClientId || u.clientId || ''); req.companyName = u.companyName || req.clientId; - // Админ: пользователь с WZ01112 — всегда админ const allCids = u.allClientIds || [u.clientId || '']; req.canAdmin = allCids.includes('WZ01112') || u.clientId === 'WZ01112' || !!u.isAdmin || u.email === (process.env.ADMIN_EMAIL || ''); req.adminMode = req.canAdmin; @@ -52,8 +101,6 @@ function resolveContext(req, res, next) { req.impersonatedBy = isImpersonated ? u.originalUserEmail : null; req.allClientIds = allCids; req.profiles = u.profiles || []; - - next(); } module.exports = { resolveContext };