const https = require('https'); const config = require('../config'); /** * Express middleware — извлекает контекст из сессии. * При 401 от IAM — чистит сессию и редиректит на логин (SSO). */ async function resolveContext(req, res, next) { const u = req.session && req.session.user; if (!u) return res.redirect('/v2/login'); const token = req.session && req.session.token; if (token) { try { await checkImpersonation(token, req, u); } catch (e) { const msg = (e.message || ''); // 401 — токен протух, чистим сессию → SSO даст свежий if (msg.startsWith('401')) { req.session.destroy(() => res.redirect('/v2/login')); return; } // Другие ошибки IAM — игнорируем, используем старые данные } } applyContext(req, u); next(); } /** * Запрашивает IAM и обновляет сессию если имперсонация изменилась. * Бросает ошибку при 401 — вызывающий обработает. */ 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 || ''; req.clientId = isImpersonated ? (u.activeClientId || u.impersonatedCompanyId || u.clientId || '') : (u.activeClientId || u.clientId || ''); req.companyName = u.companyName || req.clientId; 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; req.isAdmin = req.canAdmin; req.isImpersonated = isImpersonated; req.impersonatedBy = isImpersonated ? u.originalUserEmail : null; req.allClientIds = allCids; req.profiles = u.profiles || []; } module.exports = { resolveContext };