UNPKG

@shopify/shopify-api

Version:

Shopify API Library for Node - accelerate development with support for authentication, graphql proxy, webhooks

1 lines 8.42 kB
{"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';\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\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 ...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 const hmac = getHeader(request.headers, ShopifyHeader.Hmac);\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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"}