v0.5.157: resolveContext проверяет имперсонацию через IAM на каждый запрос

This commit is contained in:
2026-06-17 16:20:59 +04:00
parent b1d670a6bf
commit c823ae8128
2 changed files with 82 additions and 35 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "ipwhitelist", "name": "ipwhitelist",
"version": "0.5.156", "version": "0.5.157",
"description": "IP WhiteList microservice for cloud provider", "description": "IP WhiteList microservice for cloud provider",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
+81 -34
View File
@@ -1,30 +1,5 @@
// ═══════════════════════════════════════════════════════════════════════════════ const https = require('https');
// V2 — resolveContext middleware const config = require('../config');
//
// ЭТО: 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
// ═══════════════════════════════════════════════════════════════════════════════
function resolveContext(req, res, next) { function resolveContext(req, res, next) {
const u = req.session && req.session.user; const u = req.session && req.session.user;
@@ -32,18 +7,92 @@ function resolveContext(req, res, next) {
// Нет сессии — нет доступа // Нет сессии — нет доступа
if (!u) return res.redirect('/v2/login'); if (!u) return res.redirect('/v2/login');
// Имперсонация: админ действует от имени компании // ── Проверка имперсонации через IAM ──────────────────────────────
// u.originalUserEmail — реальный админ // Если есть токен — запрашиваем свежие данные, чтобы обнаружить имперсонацию
// u.impersonatedCompanyId — W-номер компании // которая могла начаться после логина (ЛК сессия отдельно от 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); const isImpersonated = !!(u.originalUserEmail && u.impersonatedCompanyId);
req.email = u.email || ''; req.email = u.email || '';
// activeClientId может переопределять impersonatedCompanyId при switchTo
req.clientId = isImpersonated req.clientId = isImpersonated
? (u.activeClientId || u.impersonatedCompanyId || u.clientId || '') ? (u.activeClientId || u.impersonatedCompanyId || u.clientId || '')
: (u.activeClientId || u.clientId || ''); : (u.activeClientId || u.clientId || '');
req.companyName = u.companyName || req.clientId; req.companyName = u.companyName || req.clientId;
// Админ: пользователь с WZ01112 — всегда админ
const allCids = u.allClientIds || [u.clientId || '']; const allCids = u.allClientIds || [u.clientId || ''];
req.canAdmin = allCids.includes('WZ01112') || u.clientId === 'WZ01112' || !!u.isAdmin || u.email === (process.env.ADMIN_EMAIL || ''); req.canAdmin = allCids.includes('WZ01112') || u.clientId === 'WZ01112' || !!u.isAdmin || u.email === (process.env.ADMIN_EMAIL || '');
req.adminMode = req.canAdmin; req.adminMode = req.canAdmin;
@@ -52,8 +101,6 @@ function resolveContext(req, res, next) {
req.impersonatedBy = isImpersonated ? u.originalUserEmail : null; req.impersonatedBy = isImpersonated ? u.originalUserEmail : null;
req.allClientIds = allCids; req.allClientIds = allCids;
req.profiles = u.profiles || []; req.profiles = u.profiles || [];
next();
} }
module.exports = { resolveContext }; module.exports = { resolveContext };