From 3b1fef63c7aee376dd67c6b3235095f311d673fb Mon Sep 17 00:00:00 2001 From: Sankeerth Date: Fri, 15 Sep 2023 17:58:08 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20introduce=20de-activation=20of=20authSt?= =?UTF-8?q?atus=20for=20access=5Fdenied=20or=20inva=E2=80=A6=20(#2598)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: introduce de-activation of authStatus for access_denied or invalid_grant errors Signed-off-by: Sai Sankeerth * fix: test-cases-1 Signed-off-by: Sai Sankeerth * chore: remove all references of disable_dest and remove the const disable_dest Signed-off-by: Sai Sankeerth * chore: refactor to use util methods for getting auth error category and access-token Signed-off-by: Sai Sankeerth * fix: pardot router component test failure and remove unnecessary code Signed-off-by: Sai Sankeerth * fix: remove unused code --------- Signed-off-by: Sai Sankeerth Co-authored-by: Sai Sankeerth --- src/adapters/networkhandler/authConstants.js | 2 +- src/v0/destinations/bqstream/util.js | 11 ++-- .../campaign_manager/networkHandler.js | 19 +------ .../campaign_manager/transform.js | 12 +--- .../networkHandler.js | 22 ++------ .../transform.js | 25 +-------- .../networkHandler.js | 25 ++------- .../utils.js | 37 ++----------- .../networkHandler.js | 22 ++------ .../transform.js | 37 +++---------- src/v0/destinations/pardot/transform.js | 24 +------- .../networkHandler.js | 24 ++------ .../snapchat_custom_audience/transform.js | 26 +-------- src/v0/util/index.js | 55 ++++++++++++++++++- ...e_adwords_enhanced_conversions_output.json | 2 +- ...ds_enhanced_conversions_router_output.json | 2 +- ...ogle_adwords_remarketing_lists_output.json | 2 +- test/__tests__/data/pardot_router_output.json | 2 +- ...snapchat_custom_audience_proxy_output.json | 1 + .../destinations/pardot/router/data.ts | 2 +- 20 files changed, 112 insertions(+), 240 deletions(-) diff --git a/src/adapters/networkhandler/authConstants.js b/src/adapters/networkhandler/authConstants.js index c9a6a57835..f88361107a 100644 --- a/src/adapters/networkhandler/authConstants.js +++ b/src/adapters/networkhandler/authConstants.js @@ -2,6 +2,6 @@ * This class is used for handling Auth related errors */ module.exports = { - DISABLE_DEST: 'DISABLE_DESTINATION', REFRESH_TOKEN: 'REFRESH_TOKEN', + AUTH_STATUS_INACTIVE: 'AUTH_STATUS_INACTIVE', }; diff --git a/src/v0/destinations/bqstream/util.js b/src/v0/destinations/bqstream/util.js index cc3aba78b2..2448d72d76 100644 --- a/src/v0/destinations/bqstream/util.js +++ b/src/v0/destinations/bqstream/util.js @@ -4,7 +4,10 @@ const { getDynamicErrorType, processAxiosResponse, } = require('../../../adapters/utils/networkUtils'); -const { DISABLE_DEST, REFRESH_TOKEN } = require('../../../adapters/networkhandler/authConstants'); +const { + REFRESH_TOKEN, + AUTH_STATUS_INACTIVE, +} = require('../../../adapters/networkhandler/authConstants'); const { isHttpStatusSuccess } = require('../../util'); const { proxyRequest } = require('../../../adapters/network'); const { UnhandledStatusCodeError, NetworkError, AbortedError } = require('../../util/errorTypes'); @@ -24,7 +27,7 @@ const trimBqStreamResponse = (response) => ({ * Obtains the Destination OAuth Error Category based on the error code obtained from destination * * - If an error code is such that the user will not be allowed inside the destination, - * such error codes fall under DISABLE_DESTINATION + * such error codes fall under AUTH_STATUS_INACTIVE * - If an error code is such that upon refresh we can get a new token which can be used to send event, * such error codes fall under REFRESH_TOKEN category * - If an error code doesn't fall under both categories, we can return an empty string @@ -34,7 +37,7 @@ const trimBqStreamResponse = (response) => ({ const getDestAuthCategory = (errorCategory) => { switch (errorCategory) { case 'PERMISSION_DENIED': - return DISABLE_DEST; + return AUTH_STATUS_INACTIVE; case 'UNAUTHENTICATED': return REFRESH_TOKEN; default: @@ -88,7 +91,7 @@ const getStatusAndCategory = (dresponse, status) => { * Retryable -> 5[0-9][02-9], 401(UNAUTHENTICATED) * "Special Cases": * status=200, resp.insertErrors.length > 0 === Failure - * 403 => AccessDenied -> DISABLE_DEST, other 403 => Just abort + * 403 => AccessDenied -> AUTH_STATUS_INACTIVE, other 403 => Just abort * */ const processResponse = ({ dresponse, status } = {}) => { diff --git a/src/v0/destinations/campaign_manager/networkHandler.js b/src/v0/destinations/campaign_manager/networkHandler.js index d324c6300b..a80fff6fe4 100644 --- a/src/v0/destinations/campaign_manager/networkHandler.js +++ b/src/v0/destinations/campaign_manager/networkHandler.js @@ -1,6 +1,5 @@ const { prepareProxyRequest, proxyRequest } = require('../../../adapters/network'); -const { isHttpStatusSuccess } = require('../../util/index'); -const { REFRESH_TOKEN } = require('../../../adapters/networkhandler/authConstants'); +const { isHttpStatusSuccess, getAuthErrCategoryFromStCode } = require('../../util/index'); const { processAxiosResponse, @@ -9,20 +8,6 @@ const { const { AbortedError, RetryableError, NetworkError } = require('../../util/errorTypes'); const tags = require('../../util/tags'); -/** - * This function helps to detarmine type of error occured. According to the response - * we set authErrorCategory to take decision if we need to refresh the access_token - * or need to disable the destination. - * @param {*} code - * @returns - */ -const getAuthErrCategory = (code) => { - if (code === 401) { - return REFRESH_TOKEN; - } - return ''; -}; - function checkIfFailuresAreRetryable(response) { try { if (Array.isArray(response.status) && Array.isArray(response.status[0].errors)) { @@ -73,7 +58,7 @@ const responseHandler = (destinationResponse) => { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), }, destinationResponse, - getAuthErrCategory(status), + getAuthErrCategoryFromStCode(status), ); }; diff --git a/src/v0/destinations/campaign_manager/transform.js b/src/v0/destinations/campaign_manager/transform.js index 93ec3e8ea3..99e5fe9c57 100644 --- a/src/v0/destinations/campaign_manager/transform.js +++ b/src/v0/destinations/campaign_manager/transform.js @@ -7,6 +7,7 @@ const { removeUndefinedAndNullValues, isDefinedAndNotNull, simpleProcessRouterDest, + getAccessToken, } = require('../../util'); const { @@ -17,16 +18,9 @@ const { EncryptionSource, } = require('./config'); -const { InstrumentationError, OAuthSecretError } = require('../../util/errorTypes'); +const { InstrumentationError } = require('../../util/errorTypes'); const { JSON_MIME_TYPE } = require('../../util/constant'); -const getAccessToken = ({ secret }) => { - if (!secret) { - throw new OAuthSecretError('[CAMPAIGN MANAGER (DCM)]:: OAuth - access token not found'); - } - return secret.access_token; -}; - function isEmptyObject(obj) { return Object.keys(obj).length === 0 && obj.constructor === Object; } @@ -36,7 +30,7 @@ function buildResponse(requestJson, metadata, endpointUrl, requestType, encrypti const response = defaultRequestConfig(); response.endpoint = endpointUrl; response.headers = { - Authorization: `Bearer ${getAccessToken(metadata)}`, + Authorization: `Bearer ${getAccessToken(metadata, 'access_token')}`, 'Content-Type': JSON_MIME_TYPE, }; response.method = defaultPostRequestConfig.requestMethod; diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js index 5349b74178..e79c568238 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/networkHandler.js @@ -1,8 +1,10 @@ const { get, set } = require('lodash'); const sha256 = require('sha256'); const { prepareProxyRequest, handleHttpRequest } = require('../../../adapters/network'); -const { isHttpStatusSuccess } = require('../../util/index'); -const { REFRESH_TOKEN } = require('../../../adapters/networkhandler/authConstants'); +const { + isHttpStatusSuccess, + getAuthErrCategoryFromErrDetailsAndStCode, +} = require('../../util/index'); const { CONVERSION_ACTION_ID_CACHE_TTL } = require('./config'); const Cache = require('../../util/cache'); @@ -15,18 +17,6 @@ const { const { BASE_ENDPOINT } = require('./config'); const { NetworkError, NetworkInstrumentationError } = require('../../util/errorTypes'); const tags = require('../../util/tags'); -/** - * This function helps to detarmine type of error occured. According to the response - * we set authErrorCategory to take decision if we need to refresh the access_token - * or need to disable the destination. - * @param {*} code - * @param {*} response - * @returns - */ -const getAuthErrCategory = (code, response) => { - if (code === 401 && !get(response, 'error.details')) return REFRESH_TOKEN; - return ''; -}; /** * This function is used for collecting the conversionActionId using the conversion name @@ -68,7 +58,7 @@ const getConversionActionId = async (method, headers, params) => { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(gaecConversionActionIdResponse.status), }, gaecConversionActionIdResponse.response, - getAuthErrCategory( + getAuthErrCategoryFromErrDetailsAndStCode( get(gaecConversionActionIdResponse, 'status'), get(gaecConversionActionIdResponse, 'response[0].error.message'), ), @@ -134,7 +124,7 @@ const responseHandler = (destinationResponse) => { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), }, response, - getAuthErrCategory(status, response), + getAuthErrCategoryFromErrDetailsAndStCode(status, response), ); }; // eslint-disable-next-line func-names diff --git a/src/v0/destinations/google_adwords_enhanced_conversions/transform.js b/src/v0/destinations/google_adwords_enhanced_conversions/transform.js index 96ef089001..24993a3006 100644 --- a/src/v0/destinations/google_adwords_enhanced_conversions/transform.js +++ b/src/v0/destinations/google_adwords_enhanced_conversions/transform.js @@ -8,12 +8,12 @@ const { getValueFromMessage, removeHyphens, simpleProcessRouterDest, + getAccessToken, } = require('../../util'); const { InstrumentationError, ConfigurationError, - OAuthSecretError, } = require('../../util/errorTypes'); const { trackMapping, BASE_ENDPOINT } = require('./config'); @@ -36,34 +36,13 @@ const updateMappingJson = (mapping) => { return newMapping; }; -/** - * Get access token to be bound to the event req headers - * - * Note: - * This method needs to be implemented particular to the destination - * As the schema that we'd get in `metadata.secret` can be different - * for different destinations - * - * @param {Object} metadata - * @returns - */ -const getAccessToken = (metadata) => { - // OAuth for this destination - const { secret } = metadata; - // we would need to verify if secret is present and also if the access token field is present in secret - if (!secret || !secret.access_token) { - throw new OAuthSecretError('Empty/Invalid access token'); - } - return secret.access_token; -}; - const responseBuilder = async (metadata, message, { Config }, payload) => { const response = defaultRequestConfig(); const { event } = message; const filteredCustomerId = removeHyphens(Config.customerId); response.endpoint = `${BASE_ENDPOINT}/${filteredCustomerId}:uploadConversionAdjustments`; response.body.JSON = payload; - const accessToken = getAccessToken(metadata); + const accessToken = getAccessToken(metadata, 'access_token'); response.headers = { Authorization: `Bearer ${accessToken}`, 'Content-Type': JSON_MIME_TYPE, diff --git a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js index 963721f024..71fdccff20 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js +++ b/src/v0/destinations/google_adwords_offline_conversions/networkHandler.js @@ -2,11 +2,11 @@ const set = require('set-value'); const get = require('get-value'); const sha256 = require('sha256'); const { prepareProxyRequest, httpSend, httpPOST } = require('../../../adapters/network'); -const { REFRESH_TOKEN } = require('../../../adapters/networkhandler/authConstants'); const { isHttpStatusSuccess, getHashFromArray, isDefinedAndNotNullAndNotEmpty, + getAuthErrCategoryFromStCode, } = require('../../util'); const { getConversionActionId } = require('./utils'); const Cache = require('../../util/cache'); @@ -24,21 +24,6 @@ const tags = require('../../util/tags'); const conversionCustomVariableCache = new Cache(CONVERSION_CUSTOM_VARIABLE_CACHE_TTL); -/** - * This function helps to determine the type of error occurred. We set the authErrorCategory - * as per the destination response that is received and take the decision whether - * to refresh the access_token or disable the destination. - * @param {*} status - * @returns - */ -const getAuthErrCategory = (status) => { - if (status === 401) { - // UNAUTHORIZED - return REFRESH_TOKEN; - } - return ''; -}; - const createJob = async (endpoint, headers, payload) => { const endPoint = `${endpoint}:create`; let createJobResponse = await httpPOST( @@ -57,7 +42,7 @@ const createJob = async (endpoint, headers, payload) => { `[Google Ads Offline Conversions]:: ${response?.error?.message} during google_ads_offline_store_conversions Job Creation`, status, response, - getAuthErrCategory(status), + getAuthErrCategoryFromStCode(status), ); } return response.resourceName.split('/')[3]; @@ -80,7 +65,7 @@ const addConversionToJob = async (endpoint, headers, jobId, payload) => { `[Google Ads Offline Conversions]:: ${addConversionToJobResponse.response?.error?.message} during google_ads_offline_store_conversions Add Conversion`, addConversionToJobResponse.status, addConversionToJobResponse.response, - getAuthErrCategory(get(addConversionToJobResponse, 'status')), + getAuthErrCategoryFromStCode(get(addConversionToJobResponse, 'status')), ); } return true; @@ -131,7 +116,7 @@ const getConversionCustomVariable = async (headers, params) => { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(searchStreamResponse.status), }, searchStreamResponse?.response || searchStreamResponse, - getAuthErrCategory(searchStreamResponse.status), + getAuthErrCategoryFromStCode(searchStreamResponse.status), ); } const conversionCustomVariable = get(searchStreamResponse, 'response.0.results'); @@ -292,7 +277,7 @@ const responseHandler = (destinationResponse) => { `[Google Ads Offline Conversions]:: ${response?.error?.message} during google_ads_offline_conversions response transformation`, status, response, - getAuthErrCategory(status), + getAuthErrCategoryFromStCode(status), ); }; diff --git a/src/v0/destinations/google_adwords_offline_conversions/utils.js b/src/v0/destinations/google_adwords_offline_conversions/utils.js index 6131b5dde7..37f17c9fd0 100644 --- a/src/v0/destinations/google_adwords_offline_conversions/utils.js +++ b/src/v0/destinations/google_adwords_offline_conversions/utils.js @@ -11,8 +11,9 @@ const { getFieldValueFromMessage, isDefinedAndNotNullAndNotEmpty, isDefinedAndNotNull, + getAuthErrCategoryFromStCode, + getAccessToken, } = require('../../util'); -const { REFRESH_TOKEN } = require('../../../adapters/networkhandler/authConstants'); const { SEARCH_STREAM, CONVERSION_ACTION_ID_CACHE_TTL, @@ -26,7 +27,6 @@ const { processAxiosResponse } = require('../../../adapters/utils/networkUtils') const Cache = require('../../util/cache'); const { AbortedError, - OAuthSecretError, ConfigurationError, InstrumentationError, } = require('../../util/errorTypes'); @@ -43,34 +43,6 @@ const validateDestinationConfig = ({ Config }) => { } }; -/** - * for OAuth destination - * get access_token from metadata.secret{ ... } - * @param {*} param0 - * @returns - */ -const getAccessToken = ({ secret }) => { - if (!secret) { - throw new OAuthSecretError('OAuth - access token not found'); - } - return secret.access_token; -}; - -/** - * This function helps to determine the type of error occured. We set the authErrorCategory - * as per the destination response that is received and take the decision whether - * to refresh the access_token or disable the destination. - * @param {*} status - * @returns - */ -const getAuthErrCategory = (status) => { - if (status === 401) { - // UNAUTHORIZED - return REFRESH_TOKEN; - } - return ''; -}; - /** * get conversionAction using the conversion name using searchStream endpoint * @param {*} customerId @@ -100,7 +72,7 @@ const getConversionActionId = async (headers, params) => { )} during google_ads_offline_conversions response transformation`, searchStreamResponse.status, searchStreamResponse.response, - getAuthErrCategory(get(searchStreamResponse, 'status')), + getAuthErrCategoryFromStCode(get(searchStreamResponse, 'status')), ); } const conversionAction = get( @@ -194,7 +166,7 @@ const requestBuilder = ( } response.body.JSON = payload; response.headers = { - Authorization: `Bearer ${getAccessToken(metadata)}`, + Authorization: `Bearer ${getAccessToken(metadata, 'access_token')}`, 'Content-Type': 'application/json', 'developer-token': get(metadata, 'secret.developer_token'), }; @@ -390,7 +362,6 @@ const getClickConversionPayloadAndEndpoint = (message, Config, filteredCustomerI module.exports = { validateDestinationConfig, generateItemListFromProducts, - getAccessToken, getConversionActionId, removeHashToSha256TypeFromMappingJson, getStoreConversionPayload, diff --git a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js index ceea51f6a2..979f263fcc 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/networkHandler.js @@ -1,7 +1,8 @@ const { httpSend, prepareProxyRequest } = require('../../../adapters/network'); -const { isHttpStatusSuccess } = require('../../util/index'); - -const { REFRESH_TOKEN } = require('../../../adapters/networkhandler/authConstants'); +const { + isHttpStatusSuccess, + getAuthErrCategoryFromErrDetailsAndStCode, +} = require('../../util/index'); const { processAxiosResponse, @@ -117,19 +118,6 @@ const gaAudienceProxyRequest = async (request) => { return thirdResponse; }; -/** - * This function helps to detarmine type of error occured. According to the response - * we set authErrorCategory to take decision if we need to refresh the access_token - * or need to disable the destination. - * @param {*} code - * @param {*} response - * @returns - */ -const getAuthErrCategory = (code, response) => { - if (code === 401 && !response.error.details) return REFRESH_TOKEN; - return ''; -}; - const gaAudienceRespHandler = (destResponse, stageMsg) => { const { status, response } = destResponse; // const respAttributes = response["@attributes"] || null; @@ -142,7 +130,7 @@ const gaAudienceRespHandler = (destResponse, stageMsg) => { [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(status), }, response, - getAuthErrCategory(status, response), + getAuthErrCategoryFromErrDetailsAndStCode(status, response), ); }; diff --git a/src/v0/destinations/google_adwords_remarketing_lists/transform.js b/src/v0/destinations/google_adwords_remarketing_lists/transform.js index e847e9f72a..f8cd7c7037 100644 --- a/src/v0/destinations/google_adwords_remarketing_lists/transform.js +++ b/src/v0/destinations/google_adwords_remarketing_lists/transform.js @@ -1,6 +1,6 @@ const sha256 = require('sha256'); -const logger = require('../../../logger'); const get = require('get-value'); +const logger = require('../../../logger'); const { isDefinedAndNotNullAndNotEmpty, returnArrayOfSubarrays, @@ -11,13 +11,10 @@ const { removeHyphens, simpleProcessRouterDest, getDestinationExternalIDInfoForRetl, + getAccessToken, } = require('../../util'); -const { - InstrumentationError, - ConfigurationError, - OAuthSecretError, -} = require('../../util/errorTypes'); +const { InstrumentationError, ConfigurationError } = require('../../util/errorTypes'); const { offlineDataJobsMapping, addressInfoMapping, @@ -38,27 +35,6 @@ const hashEncrypt = (object) => { }); }; -/** - * Get access token to be bound to the event req headers - * - * Note: - * This method needs to be implemented particular to the destination - * As the schema that we'd get in `metadata.secret` can be different - * for different destinations - * - * @param {Object} metadata - * @returns - */ -const getAccessToken = (metadata) => { - // OAuth for this destination - const { secret } = metadata; - // we would need to verify if secret is present and also if the access token field is present in secret - if (!secret || !secret.access_token) { - throw new OAuthSecretError('Empty/Invalid access token'); - } - return secret.access_token; -}; - /** * This function is used for building the response. It create a default rudder response * and populate headers, params and body.JSON @@ -73,11 +49,14 @@ const responseBuilder = (metadata, body, { Config }, message) => { const filteredCustomerId = removeHyphens(Config.customerId); response.endpoint = `${BASE_ENDPOINT}/${filteredCustomerId}/offlineUserDataJobs`; response.body.JSON = removeUndefinedAndNullValues(payload); - const accessToken = getAccessToken(metadata); + const accessToken = getAccessToken(metadata, 'access_token'); let operationAudienceId = Config.audienceId || Config.listId; const mappedToDestination = get(message, MappedToDestinationKey); if (!operationAudienceId && mappedToDestination) { - const { objectType } = getDestinationExternalIDInfoForRetl(message, 'GOOGLE_ADWORDS_REMARKETING_LISTS'); + const { objectType } = getDestinationExternalIDInfoForRetl( + message, + 'GOOGLE_ADWORDS_REMARKETING_LISTS', + ); operationAudienceId = objectType; } if (!isDefinedAndNotNullAndNotEmpty(operationAudienceId)) { diff --git a/src/v0/destinations/pardot/transform.js b/src/v0/destinations/pardot/transform.js index 128d72cb14..b9f78a6c34 100644 --- a/src/v0/destinations/pardot/transform.js +++ b/src/v0/destinations/pardot/transform.js @@ -45,34 +45,14 @@ const { getSuccessRespEvents, checkInvalidRtTfEvents, handleRtTfSingleEventError, + getAccessToken, } = require('../../util'); const { CONFIG_CATEGORIES } = require('./config'); const { - OAuthSecretError, ConfigurationError, InstrumentationError, } = require('../../util/errorTypes'); -/** - * Get access token to be bound to the event req headers - * - * Note: - * This method needs to be implemented particular to the destination - * As the schema that we'd get in `metadata.secret` can be different - * for different destinations - * - * @param {Object} metadata - * @returns - */ -const getAccessToken = (metadata) => { - // OAuth for this destination - const { secret } = metadata; - if (!secret) { - throw new OAuthSecretError('Empty/Invalid access token'); - } - return secret.access_token; -}; - const buildResponse = (payload, url, destination, token) => { const responseBody = removeUndefinedValues(payload); const response = defaultRequestConfig(); @@ -122,7 +102,7 @@ const getUrl = (urlParams) => { const processIdentify = ({ message, metadata }, destination, category) => { const { campaignId } = destination.Config; - const accessToken = getAccessToken(metadata); + const accessToken = getAccessToken(metadata, 'access_token'); let extId = get(message, 'context.externalId'); extId = extId && extId.length > 0 ? extId[0] : null; diff --git a/src/v0/destinations/snapchat_custom_audience/networkHandler.js b/src/v0/destinations/snapchat_custom_audience/networkHandler.js index 0f75d36a56..49ea452550 100644 --- a/src/v0/destinations/snapchat_custom_audience/networkHandler.js +++ b/src/v0/destinations/snapchat_custom_audience/networkHandler.js @@ -1,7 +1,9 @@ -const { removeUndefinedValues } = require('../../util'); +const { removeUndefinedValues, getAuthErrCategoryFromErrDetailsAndStCode } = require('../../util'); const { prepareProxyRequest, getPayloadData, httpSend } = require('../../../adapters/network'); const { isHttpStatusSuccess } = require('../../util/index'); -const { REFRESH_TOKEN } = require('../../../adapters/networkhandler/authConstants'); +const { + REFRESH_TOKEN, +} = require('../../../adapters/networkhandler/authConstants'); const tags = require('../../util/tags'); const { getDynamicErrorType, @@ -30,22 +32,6 @@ const prepareProxyReq = (request) => { }); }; -/** - * This function helps to determine type of error occurred. According to the response - * we set authErrorCategory to take decision if we need to refresh the access_token - * or need to disable the destination. - * @param {*} code - * @param {*} response - * @returns - */ -const getAuthErrCategory = (code, response) => { - let authErrCategory = ''; - if (code === 401) { - authErrCategory = !response.error?.details ? REFRESH_TOKEN : ''; - } - return authErrCategory; -}; - const scAudienceProxyRequest = async (request) => { const { endpoint, data, method, params, headers } = prepareProxyReq(request); @@ -65,7 +51,7 @@ const scAudienceProxyRequest = async (request) => { const scaAudienceRespHandler = (destResponse, stageMsg) => { const { status, response } = destResponse; - const authErrCategory = getAuthErrCategory(status, response); + const authErrCategory = getAuthErrCategoryFromErrDetailsAndStCode(status, response); if (authErrCategory === REFRESH_TOKEN) { throw new RetryableError( diff --git a/src/v0/destinations/snapchat_custom_audience/transform.js b/src/v0/destinations/snapchat_custom_audience/transform.js index 1c16115494..7938236594 100644 --- a/src/v0/destinations/snapchat_custom_audience/transform.js +++ b/src/v0/destinations/snapchat_custom_audience/transform.js @@ -4,33 +4,13 @@ const { removeUndefinedAndNullValues, simpleProcessRouterDest, isDefinedAndNotNullAndNotEmpty, + getAccessToken, } = require('../../util'); -const { ConfigurationError, OAuthSecretError } = require('../../util/errorTypes'); +const { ConfigurationError } = require('../../util/errorTypes'); const { BASE_URL, schemaType } = require('./config'); const { validatePayload, validateFields } = require('./utils'); const { JSON_MIME_TYPE } = require('../../util/constant'); -/** - * Get access token to be bound to the event req headers - * - * Note: - * This method needs to be implemented particular to the destination - * As the schema that we'd get in `metadata.secret` can be different - * for different destinations - * - * @param {Object} metadata - * @returns - */ -const getAccessToken = (metadata) => { - // OAuth for this destination - const { secret } = metadata; - // we would need to verify if secret is present and also if the access token field is present in secret - if (!secret || !secret.access_token) { - throw new OAuthSecretError('Empty/Invalid access token'); - } - return secret.access_token; -}; - const generateResponse = (groupedData, schema, segmentId, metadata, type) => { const payload = { users: [] }; const userPayload = { schema: [schema], data: groupedData }; @@ -43,7 +23,7 @@ const generateResponse = (groupedData, schema, segmentId, metadata, type) => { response.endpoint = `${BASE_URL}/segments/${segmentId}/users`; response.body.JSON = removeUndefinedAndNullValues(payload); - const accessToken = getAccessToken(metadata); + const accessToken = getAccessToken(metadata, 'access_token'); response.headers = { Authorization: `Bearer ${accessToken}`, 'Content-Type': JSON_MIME_TYPE, diff --git a/src/v0/util/index.js b/src/v0/util/index.js index b04d64deb6..8a20104dd0 100644 --- a/src/v0/util/index.js +++ b/src/v0/util/index.js @@ -28,6 +28,10 @@ const { } = require('./errorTypes'); const { client: errNotificationClient } = require('../../util/errorNotifier'); const { HTTP_STATUS_CODES } = require('./constant'); +const { + REFRESH_TOKEN, + AUTH_STATUS_INACTIVE, +} = require('../../adapters/networkhandler/authConstants'); // ======================================================================== // INLINERS // ======================================================================== @@ -1829,8 +1833,8 @@ const getAccessToken = (metadata, accessTokenKey) => { // OAuth for this destination const { secret } = metadata; // we would need to verify if secret is present and also if the access token field is present in secret - if (!secret || !secret[accessTokenKey]) { - throw new OAuthSecretError('Empty/Invalid access token'); + if (!secret?.[accessTokenKey]) { + throw new OAuthSecretError('OAuth - access token not found'); } return secret[accessTokenKey]; }; @@ -1912,6 +1916,51 @@ const batchMultiplexedEvents = (transformedEventsList, maxBatchSize) => { return batchedEvents; }; +/** + * This function helps to detarmine type of error occured. According to the response + * we set authErrorCategory to take decision if we need to refresh the access_token + * or need to de-activate authStatus for the destination. + * + * **Scenarios**: + * - statusCode=401, response.error.details !== "" -> REFRESH_TOKEN + * - statusCode=403, response.error.details !== "" -> AUTH_STATUS_INACTIVE + * + * @param {*} code + * @param {*} response + * @returns + */ +const getAuthErrCategoryFromErrDetailsAndStCode = (code, response) => { + if (code === 401 && (!get(response, 'error.details') || typeof response === 'string')) + return REFRESH_TOKEN; + if (code === 403 && (!get(response, 'error.details') || typeof response === 'string')) + return AUTH_STATUS_INACTIVE; + return ''; +}; + +/** + * This function helps to determine the type of error occurred. We set the authErrorCategory + * as per the destination response that is received and take the decision whether + * to refresh the access_token or de-activate authStatus. + * + * **Scenarios**: + * - statusCode=401 -> REFRESH_TOKEN + * - statusCode=403 -> AUTH_STATUS_INACTIVE + * + * @param {*} status + * @returns + */ +const getAuthErrCategoryFromStCode = (status) => { + if (status === 401) { + // UNAUTHORIZED + return REFRESH_TOKEN; + } + if (status === 403) { + // ACCESS_DENIED + return AUTH_STATUS_INACTIVE; + } + return ''; +}; + // ======================================================================== // EXPORTS // ======================================================================== @@ -2012,4 +2061,6 @@ module.exports = { checkAndCorrectUserId, getAccessToken, formatValues, + getAuthErrCategoryFromErrDetailsAndStCode, + getAuthErrCategoryFromStCode, }; diff --git a/test/__tests__/data/google_adwords_enhanced_conversions_output.json b/test/__tests__/data/google_adwords_enhanced_conversions_output.json index d260502926..292a849673 100644 --- a/test/__tests__/data/google_adwords_enhanced_conversions_output.json +++ b/test/__tests__/data/google_adwords_enhanced_conversions_output.json @@ -177,7 +177,7 @@ "secret": null }, "statusCode": 400, - "error": "Empty/Invalid access token", + "error": "OAuth - access token not found", "statTags": { "destination": "google_adwords_enhanced_conversions", "stage": "transform", diff --git a/test/__tests__/data/google_adwords_enhanced_conversions_router_output.json b/test/__tests__/data/google_adwords_enhanced_conversions_router_output.json index bab6c05cc9..c283549e50 100644 --- a/test/__tests__/data/google_adwords_enhanced_conversions_router_output.json +++ b/test/__tests__/data/google_adwords_enhanced_conversions_router_output.json @@ -137,7 +137,7 @@ }, "batched": false, "statusCode": 500, - "error": "Empty/Invalid access token", + "error": "OAuth - access token not found", "statTags": { "errorCategory": "platform", "errorType": "oAuthSecret" diff --git a/test/__tests__/data/google_adwords_remarketing_lists_output.json b/test/__tests__/data/google_adwords_remarketing_lists_output.json index 2a6d04bea1..cd92d314b6 100644 --- a/test/__tests__/data/google_adwords_remarketing_lists_output.json +++ b/test/__tests__/data/google_adwords_remarketing_lists_output.json @@ -5779,7 +5779,7 @@ "secret": null }, "statusCode": 500, - "error": "Empty/Invalid access token", + "error": "OAuth - access token not found", "statTags": { "destination": "google_adwords_remarketing_lists", "stage": "transform", diff --git a/test/__tests__/data/pardot_router_output.json b/test/__tests__/data/pardot_router_output.json index 913e35ce94..889cc8fd96 100644 --- a/test/__tests__/data/pardot_router_output.json +++ b/test/__tests__/data/pardot_router_output.json @@ -376,7 +376,7 @@ ], "batched": false, "statusCode": 500, - "error": "Empty/Invalid access token", + "error": "OAuth - access token not found", "statTags": { "errorCategory": "platform", "errorType": "oAuthSecret" diff --git a/test/__tests__/data/snapchat_custom_audience_proxy_output.json b/test/__tests__/data/snapchat_custom_audience_proxy_output.json index 654d94c45c..e5bcc4faa8 100644 --- a/test/__tests__/data/snapchat_custom_audience_proxy_output.json +++ b/test/__tests__/data/snapchat_custom_audience_proxy_output.json @@ -43,6 +43,7 @@ }, { "output": { + "authErrorCategory": "AUTH_STATUS_INACTIVE", "status": 400, "destinationResponse": { "response": { diff --git a/test/integrations/destinations/pardot/router/data.ts b/test/integrations/destinations/pardot/router/data.ts index 573f9192b5..28680deafc 100644 --- a/test/integrations/destinations/pardot/router/data.ts +++ b/test/integrations/destinations/pardot/router/data.ts @@ -999,7 +999,7 @@ export const data = [ ], batched: false, statusCode: 500, - error: 'Empty/Invalid access token', + error: 'OAuth - access token not found', statTags: { destType: 'PARDOT', feature: 'router',