From e6832d7da9e45d0ffc975d9314b243b735200bdd Mon Sep 17 00:00:00 2001 From: Nazar Kovtun Date: Fri, 25 Oct 2024 19:26:41 +0300 Subject: [PATCH 1/2] HCK-8554: extended errors processing to handle Entra grant c onsent issues --- reverse_engineering/api.js | 23 +++- .../databaseService/helpers/errorService.js | 102 ++++++++++++++++++ 2 files changed, 121 insertions(+), 4 deletions(-) create mode 100644 reverse_engineering/databaseService/helpers/errorService.js diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js index b43e0a5..1f452b9 100644 --- a/reverse_engineering/api.js +++ b/reverse_engineering/api.js @@ -17,6 +17,7 @@ const { adaptJsonSchema } = require('./helpers/adaptJsonSchema'); const crypto = require('crypto'); const randomstring = require('randomstring'); const base64url = require('base64url'); +const { prepareError } = require('./databaseService/helpers/errorService'); module.exports = { async connect(connectionInfo, logger, callback, app) { @@ -47,8 +48,17 @@ module.exports = { } callback(null); } catch (error) { - logger.log('error', { message: error.message, stack: error.stack, error }, 'Test connection'); - callback({ message: error.message, stack: error.stack }); + const errorWithUpdatedInfo = prepareError({ error }); + logger.log( + 'error', + { + message: errorWithUpdatedInfo.message, + stack: errorWithUpdatedInfo.stack, + error: errorWithUpdatedInfo, + }, + 'Test connection', + ); + callback({ message: errorWithUpdatedInfo.message, stack: errorWithUpdatedInfo.stack }); } }, @@ -91,12 +101,17 @@ module.exports = { logger.log('info', { collation: collationData[0] }, 'Database collation'); callback(null, objects); } catch (error) { + const errorWithUpdatedInfo = prepareError({ error }); logger.log( 'error', - { message: error.message, stack: error.stack, error }, + { + message: errorWithUpdatedInfo.message, + stack: errorWithUpdatedInfo.stack, + error: errorWithUpdatedInfo, + }, 'Retrieving databases and tables information', ); - callback({ message: error.message, stack: error.stack }); + callback({ message: errorWithUpdatedInfo.message, stack: errorWithUpdatedInfo.stack }); } }, diff --git a/reverse_engineering/databaseService/helpers/errorService.js b/reverse_engineering/databaseService/helpers/errorService.js new file mode 100644 index 0000000..56438d8 --- /dev/null +++ b/reverse_engineering/databaseService/helpers/errorService.js @@ -0,0 +1,102 @@ +/** + * + * @param {{message: string}} param + * @returns {boolean} + */ +const isDisabledPublicClientFlowsError = ({ message }) => { + const DISABLED_PUBLIC_CLIENT_FLOWS_ERROR_ID = 'AADSTS7000218'; + + return message.includes(DISABLED_PUBLIC_CLIENT_FLOWS_ERROR_ID); +}; + +/** + * + * @param {{message: string}} param + * @returns {boolean} + */ +const isConsentRequiredError = ({ message }) => { + const CONSENT_REQUIRED_ERROR_ID = 'AADSTS65001'; + + return message.includes(CONSENT_REQUIRED_ERROR_ID); +}; + +/** + * + * @param {{error: object, newMessage: string, newStackTrace: string}} param + * @returns {object} + */ +const updateErrorMessageAndStack = ({ error, newMessage, newStackTrace }) => ({ + code: error.code, + name: error.name, + message: newMessage, + stack: newStackTrace, +}); + +/** + * + * @param {{clientId: string}} param + * @returns {string} + */ +const getConsentRequiredErrorMessage = ({ clientId }) => { + const consentLink = `https://login.microsoftonline.com/organizations/adminconsent?client_id=${clientId}`; + + return `Your Azure administrator needs to grant tenant-wide consent to the Hackolade application using the link below: ${consentLink}`; +}; + +/** + * + * @param {{match: string}} param + * @returns {string} + */ +const getClientIdFromErrorMessage = ({ message }) => { + const clientIdRegularExpression = new RegExp( + /'[0-9a-z]{8}\-[0-9a-z]{4}\-[0-9a-z]{4}\-[0-9a-z]{4}\-[0-9a-z]{12}'/gim, + ); + const clientIdMatches = message.match(clientIdRegularExpression); + + if (clientIdMatches.length === 0) { + return 'Unknown'; + } + + const [clientId] = clientIdMatches; + const clientIdWithoutQuotes = clientId.slice(1, clientId.length - 1); + + return clientIdWithoutQuotes; +}; + +/** + * + * @param {{error: object}} param + * @returns {object} + */ +const prepareError = ({ error }) => { + const { code, name, originalError, message, stack } = error; + const originalErrors = originalError?.errors; + if (!originalErrors || originalErrors?.length === 0) { + return error; + } + + const initialErrorDataIndex = originalErrors.length - 1; + const initialError = originalErrors[initialErrorDataIndex]; + + const isInitialErrorConsentRequiredError = isConsentRequiredError(initialError); + if (isInitialErrorConsentRequiredError) { + const clientId = getClientIdFromErrorMessage({ message: initialError.message }); + const newErrorMessage = getConsentRequiredErrorMessage({ clientId }); + + return updateErrorMessageAndStack({ error, newMessage: newErrorMessage, newStackTrace: initialError.stack }); + } + + const isInitialErrorDisabledPublicClientFlowsError = isDisabledPublicClientFlowsError(initialError); + if (isInitialErrorDisabledPublicClientFlowsError) { + const newErrorMessage = 'You need to allow Public client flows for the Entra ID application'; + + return updateErrorMessageAndStack({ error, newMessage: newErrorMessage, newStackTrace: initialError.stack }); + } + + return error; +}; + +module.exports = { + prepareError, +}; From d0c2567dd225c62585d7891642b782ba5d3b66bd Mon Sep 17 00:00:00 2001 From: Nazar Kovtun Date: Fri, 25 Oct 2024 19:33:08 +0300 Subject: [PATCH 2/2] HCk-8554: removed redundant variables and escape characters --- .../databaseService/helpers/errorService.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/reverse_engineering/databaseService/helpers/errorService.js b/reverse_engineering/databaseService/helpers/errorService.js index 56438d8..cb18d5e 100644 --- a/reverse_engineering/databaseService/helpers/errorService.js +++ b/reverse_engineering/databaseService/helpers/errorService.js @@ -49,9 +49,7 @@ const getConsentRequiredErrorMessage = ({ clientId }) => { * @returns {string} */ const getClientIdFromErrorMessage = ({ message }) => { - const clientIdRegularExpression = new RegExp( - /'[0-9a-z]{8}\-[0-9a-z]{4}\-[0-9a-z]{4}\-[0-9a-z]{4}\-[0-9a-z]{12}'/gim, - ); + const clientIdRegularExpression = new RegExp(/'[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}'/gim); const clientIdMatches = message.match(clientIdRegularExpression); if (clientIdMatches.length === 0) { @@ -70,8 +68,7 @@ const getClientIdFromErrorMessage = ({ message }) => { * @returns {object} */ const prepareError = ({ error }) => { - const { code, name, originalError, message, stack } = error; - const originalErrors = originalError?.errors; + const originalErrors = error?.originalError?.errors; if (!originalErrors || originalErrors?.length === 0) { return error; }