From 3d342bad4d63bd79a9b4c58d70b089f55a642635 Mon Sep 17 00:00:00 2001 From: Leonel Sanches <113376080+seg-leonelsanches@users.noreply.github.com> Date: Fri, 7 Jul 2023 09:51:32 -0700 Subject: [PATCH] Custom validation in Google Ads for email address to allow hashed email addresses. (#1365) * Custom validation for email address to allow hashed email addresses. * Using `ajv-formats` email Regex to validate email. * Adding `ajv-formats` as advised in PR comment. --- packages/destination-actions/package.json | 1 + .../google-enhanced-conversions/functions.ts | 15 ++++++++++++++- .../uploadClickConversion/index.ts | 7 ++++--- .../uploadConversionAdjustment/index.ts | 7 ++++--- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/packages/destination-actions/package.json b/packages/destination-actions/package.json index c462067cf5..79445c183e 100644 --- a/packages/destination-actions/package.json +++ b/packages/destination-actions/package.json @@ -43,6 +43,7 @@ "@segment/actions-core": "^3.69.0", "@segment/actions-shared": "^1.51.0", "@types/node": "^18.11.15", + "ajv-formats": "^2.1.1", "aws4": "^1.12.0", "cheerio": "^1.0.0-rc.10", "dayjs": "^1.10.7", diff --git a/packages/destination-actions/src/destinations/google-enhanced-conversions/functions.ts b/packages/destination-actions/src/destinations/google-enhanced-conversions/functions.ts index f8081f9a9c..a5ca4e69b1 100644 --- a/packages/destination-actions/src/destinations/google-enhanced-conversions/functions.ts +++ b/packages/destination-actions/src/destinations/google-enhanced-conversions/functions.ts @@ -1,8 +1,9 @@ import { createHash } from 'crypto' import { ConversionCustomVariable, PartialErrorResponse, QueryResponse } from './types' -import { ModifiedResponse, RequestClient, IntegrationError } from '@segment/actions-core' +import { ModifiedResponse, RequestClient, IntegrationError, PayloadValidationError } from '@segment/actions-core' import { StatsContext } from '@segment/actions-core/src/destination-kit' import { Features } from '@segment/actions-core/src/mapping-kit' +import { fullFormats } from 'ajv-formats/dist/formats' export const API_VERSION = 'v12' export const CANARY_API_VERSION = 'v13' @@ -95,3 +96,15 @@ export function getApiVersion(features?: Features, statsContext?: StatsContext): } export const isHashedEmail = (email: string): boolean => new RegExp(/[0-9abcdef]{64}/gi).test(email) +export const commonHashedEmailValidation = (email: string): string => { + if (isHashedEmail(email)) { + return email + } + + // https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts#L64-L65 + if (!(fullFormats.email as RegExp).test(email)) { + throw new PayloadValidationError("Email provided doesn't seem to be in a valid format.") + } + + return String(hash(email)) +} diff --git a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadClickConversion/index.ts b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadClickConversion/index.ts index c8ac554173..21b17910c1 100644 --- a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadClickConversion/index.ts +++ b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadClickConversion/index.ts @@ -9,7 +9,7 @@ import { handleGoogleErrors, convertTimestamp, getApiVersion, - isHashedEmail + commonHashedEmailValidation } from '../functions' import { ModifiedResponse } from '@segment/actions-core' @@ -56,7 +56,6 @@ const action: ActionDefinition = { description: 'Email address of the individual who triggered the conversion event. Segment will hash this value before sending to Google.', type: 'string', - format: 'email', default: { '@if': { exists: { '@path': '$.properties.email' }, @@ -240,8 +239,10 @@ const action: ActionDefinition = { } if (payload.email_address) { + const validatedEmail: string = commonHashedEmailValidation(payload.email_address) + request_object.userIdentifiers.push({ - hashedEmail: isHashedEmail(payload.email_address) ? payload.email_address : hash(payload.email_address) + hashedEmail: validatedEmail }) } diff --git a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadConversionAdjustment/index.ts b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadConversionAdjustment/index.ts index dd3069cf6d..7801da3bc1 100644 --- a/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadConversionAdjustment/index.ts +++ b/packages/destination-actions/src/destinations/google-enhanced-conversions/uploadConversionAdjustment/index.ts @@ -1,5 +1,5 @@ import { ActionDefinition, PayloadValidationError } from '@segment/actions-core' -import { hash, handleGoogleErrors, convertTimestamp, getApiVersion, isHashedEmail } from '../functions' +import { hash, handleGoogleErrors, convertTimestamp, getApiVersion, commonHashedEmailValidation } from '../functions' import type { Settings } from '../generated-types' import type { Payload } from './generated-types' import { PartialErrorResponse } from '../types' @@ -79,7 +79,6 @@ const action: ActionDefinition = { description: 'Email address of the individual who triggered the conversion event. Segment will hash this value before sending to Google.', type: 'string', - format: 'email', default: { '@if': { exists: { '@path': '$.properties.email' }, @@ -235,8 +234,10 @@ const action: ActionDefinition = { } if (payload.email_address) { + const validatedEmail: string = commonHashedEmailValidation(payload.email_address) + request_object.userIdentifiers.push({ - hashedEmail: isHashedEmail(payload.email_address) ? payload.email_address : hash(payload.email_address) + hashedEmail: validatedEmail }) }