v2: IAM fallback — JWT claims если IAM недоступен

This commit is contained in:
2026-06-12 16:31:18 +04:00
parent 8f8093fa29
commit 21a2c65296
2 changed files with 58 additions and 34 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "ipwhitelist",
"version": "0.5.63",
"version": "0.5.64",
"description": "IP WhiteList microservice for cloud provider",
"main": "server.js",
"scripts": {
+40 -16
View File
@@ -1,10 +1,8 @@
// ═══════════════════════════════════════════════════════════════════════════════
// Модуль аутентификации V2
// ═══════════════════════════════════════════════════════════════════════════════
const crypto = require('crypto');
const https = require('https');
const http = require('http');
const jwt = require('jsonwebtoken');
function buildAuthUrl(config) {
const { baseUrl, clientId, redirectUri, state } = config;
@@ -29,13 +27,8 @@ async function exchangeCode(code, config) {
});
console.log('[v2:auth] exchangeCode →', tokenUrl);
console.log('[v2:auth] body:', body.toString());
const result = await postForm(tokenUrl, body);
console.log('[v2:auth] response keys:', Object.keys(result));
if (!result.access_token) {
console.log('[v2:auth] NO ACCESS_TOKEN, full response:', JSON.stringify(result).slice(0, 500));
}
return result;
}
@@ -72,14 +65,41 @@ async function fetchIamUser(token, iamUrl) {
};
}
function fallbackSessionUser(accessToken) {
const payload = jwt.decode(accessToken);
if (!payload) throw new Error('Failed to decode JWT');
const email = payload.email || payload.preferred_username || '';
const name = payload.name || payload.preferred_username || email;
return {
email: email,
clientId: payload.azp || payload.client_id || '',
allClientIds: [payload.azp || ''].filter(Boolean),
activeClientId: payload.azp || '',
activeProfileId: null,
companyId: '',
companyName: payload.azp || '',
isAdmin: false,
fio: name,
profiles: [],
isImpersonated: false,
impersonationType: null,
originalUserEmail: '',
originalUserFullName: '',
originalUserCompany: '',
};
}
async function login(code, oidcConfig, iamUrl) {
const tokenData = await exchangeCode(code, oidcConfig);
const accessToken = tokenData.access_token;
if (!accessToken) throw new Error('No access_token in Keycloak response: ' + JSON.stringify(tokenData).slice(0, 300));
if (!accessToken) throw new Error('No access_token in Keycloak response');
let sessionUser;
try {
const iamData = await fetchIamUser(accessToken, iamUrl);
const sessionUser = {
sessionUser = {
email: iamData.email,
clientId: iamData.clientId,
allClientIds: iamData.allClientIds,
@@ -96,6 +116,11 @@ async function login(code, oidcConfig, iamUrl) {
originalUserFullName: iamData.originalUserFullName,
originalUserCompany: iamData.originalUserCompany,
};
console.log('[v2:auth] IAM OK:', iamData.email, iamData.clientId);
} catch (iamErr) {
console.log('[v2:auth] IAM unavailable, using JWT fallback:', iamErr.message);
sessionUser = fallbackSessionUser(accessToken);
}
return { sessionUser, token: accessToken, idToken: tokenData.id_token || null };
}
@@ -109,9 +134,9 @@ function httpGet(url, token) {
headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' },
}, (res) => {
let data = '';
res.on('data', c => { data += c; if (data.length > 100_000) { req.destroy(); reject(new Error('Response too large')); } });
res.on('data', c => { data += c; if (data.length > 100_000) { req.destroy(); reject(new Error('Too large')); } });
res.on('end', () => {
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
if (res.statusCode !== 200) return reject(new Error('HTTP ' + res.statusCode + ': ' + data.slice(0, 200)));
resolve(data);
});
});
@@ -134,12 +159,11 @@ function postForm(urlStr, body) {
let data = '';
res.on('data', c => data += c);
res.on('end', () => {
console.log('[v2:auth] postForm status:', res.statusCode, 'body:', data.slice(0, 300));
if (res.statusCode !== 200) return reject(new Error(`POST ${res.statusCode}: ${data.slice(0, 200)}`));
if (res.statusCode !== 200) return reject(new Error('POST ' + res.statusCode + ': ' + data.slice(0, 200)));
try { resolve(JSON.parse(data)); } catch (e) { reject(e); }
});
});
req.on('error', (e) => { console.log('[v2:auth] postForm ERROR:', e.message); reject(e); });
req.on('error', reject);
req.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout')); });
req.write(postData);
req.end();