v2: IAM fallback — JWT claims если IAM недоступен
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "ipwhitelist",
|
"name": "ipwhitelist",
|
||||||
"version": "0.5.63",
|
"version": "0.5.64",
|
||||||
"description": "IP WhiteList microservice for cloud provider",
|
"description": "IP WhiteList microservice for cloud provider",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+57
-33
@@ -1,10 +1,8 @@
|
|||||||
// ═══════════════════════════════════════════════════════════════════════════════
|
|
||||||
// Модуль аутентификации V2
|
// Модуль аутентификации V2
|
||||||
// ═══════════════════════════════════════════════════════════════════════════════
|
|
||||||
|
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
function buildAuthUrl(config) {
|
function buildAuthUrl(config) {
|
||||||
const { baseUrl, clientId, redirectUri, state } = 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] exchangeCode →', tokenUrl);
|
||||||
console.log('[v2:auth] body:', body.toString());
|
|
||||||
|
|
||||||
const result = await postForm(tokenUrl, body);
|
const result = await postForm(tokenUrl, body);
|
||||||
console.log('[v2:auth] response keys:', Object.keys(result));
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,30 +65,62 @@ 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) {
|
async function login(code, oidcConfig, iamUrl) {
|
||||||
const tokenData = await exchangeCode(code, oidcConfig);
|
const tokenData = await exchangeCode(code, oidcConfig);
|
||||||
const accessToken = tokenData.access_token;
|
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');
|
||||||
|
|
||||||
const iamData = await fetchIamUser(accessToken, iamUrl);
|
let sessionUser;
|
||||||
|
try {
|
||||||
const sessionUser = {
|
const iamData = await fetchIamUser(accessToken, iamUrl);
|
||||||
email: iamData.email,
|
sessionUser = {
|
||||||
clientId: iamData.clientId,
|
email: iamData.email,
|
||||||
allClientIds: iamData.allClientIds,
|
clientId: iamData.clientId,
|
||||||
activeClientId: iamData.clientId,
|
allClientIds: iamData.allClientIds,
|
||||||
activeProfileId: iamData.activeProfileId,
|
activeClientId: iamData.clientId,
|
||||||
companyId: iamData.companyId,
|
activeProfileId: iamData.activeProfileId,
|
||||||
companyName: iamData.companyName,
|
companyId: iamData.companyId,
|
||||||
isAdmin: iamData.isAdmin,
|
companyName: iamData.companyName,
|
||||||
fio: iamData.fio,
|
isAdmin: iamData.isAdmin,
|
||||||
profiles: iamData.profiles,
|
fio: iamData.fio,
|
||||||
isImpersonated: iamData.isImpersonated,
|
profiles: iamData.profiles,
|
||||||
impersonationType: iamData.impersonationType,
|
isImpersonated: iamData.isImpersonated,
|
||||||
originalUserEmail: iamData.originalUserEmail,
|
impersonationType: iamData.impersonationType,
|
||||||
originalUserFullName: iamData.originalUserFullName,
|
originalUserEmail: iamData.originalUserEmail,
|
||||||
originalUserCompany: iamData.originalUserCompany,
|
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 };
|
return { sessionUser, token: accessToken, idToken: tokenData.id_token || null };
|
||||||
}
|
}
|
||||||
@@ -109,9 +134,9 @@ function httpGet(url, token) {
|
|||||||
headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' },
|
headers: { Authorization: 'Bearer ' + token, Accept: 'application/json' },
|
||||||
}, (res) => {
|
}, (res) => {
|
||||||
let data = '';
|
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', () => {
|
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);
|
resolve(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -134,12 +159,11 @@ function postForm(urlStr, body) {
|
|||||||
let data = '';
|
let data = '';
|
||||||
res.on('data', c => data += c);
|
res.on('data', c => data += c);
|
||||||
res.on('end', () => {
|
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); }
|
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.setTimeout(10000, () => { req.destroy(); reject(new Error('Timeout')); });
|
||||||
req.write(postData);
|
req.write(postData);
|
||||||
req.end();
|
req.end();
|
||||||
|
|||||||
Reference in New Issue
Block a user