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:
2026-06-04 13:53:44 +03:00
parent 878e46287b
commit a8a07fe019
15 changed files with 1404 additions and 164 deletions
+39 -18
View File
@@ -43,30 +43,51 @@ function createRouter({ auth, doubleCsrfProtection, generateCsrfToken, authLimit
}
try {
// Обмен code на токен (через Keycloak или API Gateway)
// Обмен code на токен (через IAM / Keycloak)
const tokenData = await auth.exchangeCode(code);
const accessToken = tokenData.access_token;
const accessToken = tokenData.accessToken;
// Верификация токена (проверка подписи через JWKS)
// userFromPayload уже встроена в exchangeCode или делается здесь
if (!accessToken) throw new Error('No access_token in response');
// Базовый user из JWT (fallback)
const payload = jwt.decode(accessToken);
if (!payload) throw new Error('Failed to decode token');
// Определяем компании пользователя
const rawClientId = payload.ClientID || payload.client_id || '';
const allClientIds = rawClientId.split(',').map(s => s.trim()).filter(Boolean);
const activeClientId = allClientIds[0] || rawClientId;
req.session.token = accessToken;
req.session.user = {
clientId: rawClientId,
allClientIds,
activeClientId,
email: payload.email || payload.login || '',
companyId: payload.company_id || payload.companyId || null,
companyName: payload.company_name || payload.companyName || activeClientId,
isAdmin: activeClientId === (process.env.ADMIN_CLIENT_ID || 'WZ01112'),
};
// Пытаемся обогатить через IAM API
try {
const { fetchIamUser } = require('../auth');
const iamData = await fetchIamUser(accessToken);
req.session.user = {
email: iamData.email,
clientId: iamData.clientId,
allClientIds: iamData.allClientIds,
activeClientId: iamData.clientId,
activeProfileId: iamData.activeProfileId,
companyId: iamData.companyId,
companyName: iamData.companyName,
isAdmin: iamData.isAdmin,
fio: iamData.fio,
profiles: iamData.profiles,
};
console.log('[oidc] IAM enrichment OK:', iamData.email, iamData.clientId, 'profiles:', iamData.allClientIds.length);
} catch (iamErr) {
// IAM недоступен — используем fallback из JWT
console.warn('[oidc] IAM enrichment failed, using JWT fallback:', iamErr.message);
const rawClientId = payload.ClientID || payload.client_id || '';
const allClientIds = rawClientId.split(',').map(s => s.trim()).filter(Boolean);
const activeClientId = allClientIds[0] || rawClientId || 'UNKNOWN';
req.session.user = {
clientId: activeClientId,
allClientIds,
activeClientId,
email: payload.email || payload.login || '',
companyId: payload.company_id || null,
companyName: payload.company_name || activeClientId,
isAdmin: activeClientId === (process.env.ADMIN_CLIENT_ID || 'WZ01112'),
};
}
const returnTo = req.session.oidcReturnTo || '/';
delete req.session.oidcReturnTo;