v2: IAM fallback — JWT claims если IAM недоступен
This commit is contained in:
+57
-33
@@ -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,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) {
|
||||
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');
|
||||
|
||||
const iamData = await fetchIamUser(accessToken, iamUrl);
|
||||
|
||||
const sessionUser = {
|
||||
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,
|
||||
isImpersonated: iamData.isImpersonated,
|
||||
impersonationType: iamData.impersonationType,
|
||||
originalUserEmail: iamData.originalUserEmail,
|
||||
originalUserFullName: iamData.originalUserFullName,
|
||||
originalUserCompany: iamData.originalUserCompany,
|
||||
};
|
||||
let sessionUser;
|
||||
try {
|
||||
const iamData = await fetchIamUser(accessToken, iamUrl);
|
||||
sessionUser = {
|
||||
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,
|
||||
isImpersonated: iamData.isImpersonated,
|
||||
impersonationType: iamData.impersonationType,
|
||||
originalUserEmail: iamData.originalUserEmail,
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user