feat: IAM API интеграция + обновление ТЗ + тесты
- src/auth.js: fetchIamUser(), switchProfile(), userFromPayload(iamData) - src/config.js: IAM_API_URL из env - src/routes/oidc.js: обогащение сессии через IAM (с fallback на JWT) - ui/routes/auth.js: обогащение при токен-логине - ui/routes/entries.js: switchTo через POST /switch-profile - src/api/routes/entries.js: resolveCompany через profiles[] - views/index.ejs: переключатель компаний с названиями из profiles - .env.example: IAM_API_URL - docs: обновлены ТЗ-реализация.md, ТЗ-плюс.md, добавлен iam-integration.md - tests: api-crud.sh (14 тестов CRUD + валидации) - .gitignore: исключены .env.test, DEPLOY-TESTING.md
This commit is contained in:
+109
-3
@@ -25,6 +25,7 @@ const jwt = require('jsonwebtoken');
|
||||
const crypto = require('crypto');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const { IAM_API_URL } = require('./config');
|
||||
|
||||
// ── Конфигурация из окружения ────────────────────────────────────────────────
|
||||
const ISSUER = process.env.JWT_ISSUER || 'mock-auth-api';
|
||||
@@ -186,6 +187,7 @@ async function exchangeCode(code) {
|
||||
|
||||
return {
|
||||
user: userFromPayload(payload),
|
||||
accessToken: data.access_token,
|
||||
idToken: data.id_token || null,
|
||||
refreshToken: data.refresh_token || null,
|
||||
};
|
||||
@@ -274,8 +276,112 @@ function verifyAnyToken(token, devMode) {
|
||||
throw lastErr || new Error('Token verification failed: no verifier available');
|
||||
}
|
||||
|
||||
function userFromPayload(payload) {
|
||||
// ── Компании ──────────────────────────────────────────────────────────
|
||||
// ── IAM API — получение профилей пользователя ─────────────────────────────────
|
||||
/**
|
||||
* Выполняет HTTP-запрос к IAM API и возвращает обогащённые данные пользователя.
|
||||
* @param {string} token — access_token от IAM
|
||||
* @returns {object} { email, clientId, allClientIds, activeProfileId, companyId, companyName, isAdmin, fio, profiles, raw }
|
||||
*/
|
||||
async function fetchIamUser(token) {
|
||||
const raw = await new Promise((resolve, reject) => {
|
||||
const url = new URL(IAM_API_URL + '/api/v1/auth/user');
|
||||
const lib = url.protocol === 'https:' ? https : http;
|
||||
const req = lib.request({
|
||||
hostname: url.hostname,
|
||||
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
||||
path: url.pathname,
|
||||
method: 'GET',
|
||||
headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' },
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', c => { data += c; });
|
||||
res.on('end', () => {
|
||||
if (res.statusCode !== 200) {
|
||||
return reject(new Error(`IAM API returned ${res.statusCode}: ${data.slice(0, 200)}`));
|
||||
}
|
||||
try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.setTimeout(10000, () => { req.destroy(); reject(new Error('IAM API timeout')); });
|
||||
req.end();
|
||||
});
|
||||
|
||||
const profiles = raw.profiles || [];
|
||||
const activeProfile = profiles.find(p => p.is_active_profile) || profiles[0] || {};
|
||||
const ui = raw.userInfo || {};
|
||||
|
||||
return {
|
||||
email: ui.email || '',
|
||||
clientId: ui.clientID || activeProfile.client_id || '',
|
||||
allClientIds: profiles.map(p => p.client_id).filter(Boolean),
|
||||
activeProfileId: activeProfile.id || null,
|
||||
companyId: ui.companyId || '',
|
||||
companyName: ui.company || activeProfile.company_name || '',
|
||||
isAdmin: !!ui.isAdmin,
|
||||
fio: ui.fio || null,
|
||||
profiles, // полный массив для UI
|
||||
raw, // сырой ответ (для отладки)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Переключает активный профиль пользователя через IAM API.
|
||||
* @param {string} token — access_token от IAM
|
||||
* @param {number} profileId — profiles[].id нового активного профиля
|
||||
* @returns {object} { success, active_profile }
|
||||
*/
|
||||
async function switchProfile(token, profileId) {
|
||||
const body = JSON.stringify({ profile_id: profileId });
|
||||
const raw = await new Promise((resolve, reject) => {
|
||||
const url = new URL(IAM_API_URL + '/api/v1/user/switch-profile');
|
||||
const lib = url.protocol === 'https:' ? https : http;
|
||||
const req = lib.request({
|
||||
hostname: url.hostname,
|
||||
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
||||
path: url.pathname,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + token,
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', c => { data += c; });
|
||||
res.on('end', () => {
|
||||
if (res.statusCode !== 200) {
|
||||
return reject(new Error(`IAM switch-profile failed: ${res.statusCode} ${data.slice(0, 200)}`));
|
||||
}
|
||||
try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
|
||||
});
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.setTimeout(10000, () => { req.destroy(); reject(new Error('IAM switch-profile timeout')); });
|
||||
req.write(body);
|
||||
req.end();
|
||||
});
|
||||
return raw;
|
||||
}
|
||||
|
||||
function userFromPayload(payload, iamData) {
|
||||
// ── Если есть данные из IAM API — используем их как основной источник ────
|
||||
if (iamData) {
|
||||
return {
|
||||
email: iamData.email,
|
||||
clientId: iamData.clientId,
|
||||
allClientIds: iamData.allClientIds,
|
||||
activeClientId: iamData.clientId, // активная по IAM
|
||||
activeProfileId: iamData.activeProfileId,
|
||||
companyId: iamData.companyId,
|
||||
companyName: iamData.companyName,
|
||||
isAdmin: iamData.isAdmin,
|
||||
fio: iamData.fio,
|
||||
profiles: iamData.profiles,
|
||||
};
|
||||
}
|
||||
|
||||
// ── Без IAM — fallback на JWT (mock-режим) ──────────────────────────────
|
||||
let allClientIds = [];
|
||||
|
||||
// 1. auth-api: ClientID = "WZ03709" или "WZ03709, WZ54321"
|
||||
@@ -402,4 +508,4 @@ function safeReturn(target) {
|
||||
return '/';
|
||||
}
|
||||
|
||||
module.exports = { initAuth, requireAdmin, safeReturn };
|
||||
module.exports = { initAuth, requireAdmin, safeReturn, fetchIamUser, switchProfile };
|
||||
|
||||
Reference in New Issue
Block a user