{"version":3,"file":"hmac-validator.mjs","sources":["../../../../../../lib/utils/hmac-validator.ts"],"sourcesContent":["import {logger} from '../logger';\nimport {ShopifyHeader} from '../types';\nimport {\n  AdapterArgs,\n  abstractConvertRequest,\n  getHeader,\n} from '../../runtime/http';\nimport {ConfigInterface} from '../base-types';\nimport {createSHA256HMAC} from '../../runtime/crypto';\nimport {HashFormat} from '../../runtime/crypto/types';\nimport {AuthQuery} from '../auth/oauth/types';\nimport * as ShopifyErrors from '../error';\nimport {safeCompare} from '../auth/oauth/safe-compare';\nimport {WEBHOOK_HEADER_NAMES, WebhookTypeValue} from '../webhooks/types';\n\nimport ProcessedQuery from './processed-query';\nimport {\n  ValidationErrorReason,\n  ValidationInvalid,\n  HmacValidationType,\n  ValidationValid,\n  ValidationErrorReasonType,\n} from './types';\n\nconst HMAC_TIMESTAMP_PERMITTED_CLOCK_TOLERANCE_SEC = 90;\n\nexport type HMACSignator = 'admin' | 'appProxy';\n\nexport interface ValidateParams extends AdapterArgs {\n  /**\n   * The type of validation to perform, either 'flow' or 'webhook'.\n   */\n  type: HmacValidationType;\n  /**\n   * The raw body of the request.\n   */\n  rawBody: string;\n  /**\n   * The webhook type for header selection (optional, only for webhooks).\n   */\n  webhookType?: WebhookTypeValue;\n}\n\nfunction stringifyQueryForAdmin(query: AuthQuery): string {\n  const processedQuery = new ProcessedQuery();\n  Object.keys(query)\n    .sort((val1, val2) => val1.localeCompare(val2))\n    .forEach((key: string) => processedQuery.put(key, query[key]));\n\n  return processedQuery.stringify(true);\n}\n\nfunction stringifyQueryForAppProxy(query: AuthQuery): string {\n  return Object.entries(query)\n    .sort(([val1], [val2]) => val1.localeCompare(val2))\n    .reduce((acc, [key, value]) => {\n      return `${acc}${key}=${Array.isArray(value) ? value.join(',') : value}`;\n    }, '');\n}\n\nexport function generateLocalHmac(config: ConfigInterface) {\n  return async (\n    params: AuthQuery,\n    signator: HMACSignator = 'admin',\n  ): Promise<string> => {\n    const {hmac, signature, ...query} = params;\n\n    const queryString =\n      signator === 'admin'\n        ? stringifyQueryForAdmin(query)\n        : stringifyQueryForAppProxy(query);\n\n    return createSHA256HMAC(config.apiSecretKey, queryString, HashFormat.Hex);\n  };\n}\n\nexport function validateHmac(config: ConfigInterface) {\n  return async (\n    query: AuthQuery,\n    {signator}: {signator: HMACSignator} = {signator: 'admin'},\n  ): Promise<boolean> => {\n    if (signator === 'admin' && !query.hmac) {\n      throw new ShopifyErrors.InvalidHmacError(\n        'Query does not contain an HMAC value.',\n      );\n    }\n\n    if (signator === 'appProxy' && !query.signature) {\n      throw new ShopifyErrors.InvalidHmacError(\n        'Query does not contain a signature value.',\n      );\n    }\n\n    validateHmacTimestamp(query);\n\n    const hmac = signator === 'appProxy' ? query.signature : query.hmac;\n    const localHmac = await generateLocalHmac(config)(query, signator);\n\n    return safeCompare(hmac as string, localHmac);\n  };\n}\n\nexport async function validateHmacString(\n  config: ConfigInterface,\n  data: string,\n  hmac: string,\n  format: HashFormat,\n) {\n  const localHmac = await createSHA256HMAC(config.apiSecretKey, data, format);\n\n  return safeCompare(hmac, localHmac);\n}\n\nexport function getCurrentTimeInSec() {\n  return Math.trunc(Date.now() / 1000);\n}\n\nexport function validateHmacFromRequestFactory(config: ConfigInterface) {\n  return async function validateHmacFromRequest({\n    type,\n    rawBody,\n    webhookType,\n    ...adapterArgs\n  }: ValidateParams): Promise<ValidationInvalid | ValidationValid> {\n    const request = await abstractConvertRequest(adapterArgs);\n    if (!rawBody.length) {\n      return fail(ValidationErrorReason.MissingBody, type, config);\n    }\n\n    // Use appropriate header based on webhook type\n    const hmacHeaderName = webhookType\n      ? WEBHOOK_HEADER_NAMES[webhookType].hmac\n      : ShopifyHeader.Hmac;\n\n    const hmac = getHeader(request.headers, hmacHeaderName);\n    if (!hmac) {\n      return fail(ValidationErrorReason.MissingHmac, type, config);\n    }\n    const validHmac = await validateHmacString(\n      config,\n      rawBody,\n      hmac,\n      HashFormat.Base64,\n    );\n    if (!validHmac) {\n      return fail(ValidationErrorReason.InvalidHmac, type, config);\n    }\n\n    return succeed(type, config);\n  };\n}\n\nfunction validateHmacTimestamp(query: AuthQuery) {\n  if (\n    Math.abs(getCurrentTimeInSec() - Number(query.timestamp)) >\n    HMAC_TIMESTAMP_PERMITTED_CLOCK_TOLERANCE_SEC\n  ) {\n    throw new ShopifyErrors.InvalidHmacError(\n      'HMAC timestamp is outside of the tolerance range',\n    );\n  }\n}\n\nasync function fail(\n  reason: ValidationErrorReasonType,\n  type: HmacValidationType,\n  config: ConfigInterface,\n): Promise<ValidationInvalid> {\n  const log = logger(config);\n  await log.debug(`${type} request is not valid`, {reason});\n\n  return {\n    valid: false,\n    reason,\n  };\n}\n\nasync function succeed(\n  type: HmacValidationType,\n  config: ConfigInterface,\n): Promise<ValidationValid> {\n  const log = logger(config);\n  await log.debug(`${type} request is valid`);\n\n  return {\n    valid: true,\n  };\n}\n"],"names":["ShopifyErrors.InvalidHmacError"],"mappings":"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"}