UNPKG

@shopify/shopify-api

Version:

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

104 lines (100 loc) 4.2 kB
'use strict'; var index$1 = require('../logger/index.js'); var types$1 = require('../types.js'); var index = require('../../runtime/http/index.js'); var types$2 = require('../../runtime/crypto/types.js'); var utils = require('../../runtime/crypto/utils.js'); var error = require('../error.js'); var safeCompare = require('../auth/oauth/safe-compare.js'); var processedQuery = require('./processed-query.js'); var types = require('./types.js'); var headers = require('../../runtime/http/headers.js'); const HMAC_TIMESTAMP_PERMITTED_CLOCK_TOLERANCE_SEC = 90; function stringifyQueryForAdmin(query) { const processedQuery$1 = new processedQuery.default(); Object.keys(query) .sort((val1, val2) => val1.localeCompare(val2)) .forEach((key) => processedQuery$1.put(key, query[key])); return processedQuery$1.stringify(true); } function stringifyQueryForAppProxy(query) { return Object.entries(query) .sort(([val1], [val2]) => val1.localeCompare(val2)) .reduce((acc, [key, value]) => { return `${acc}${key}=${Array.isArray(value) ? value.join(',') : value}`; }, ''); } function generateLocalHmac(config) { return async (params, signator = 'admin') => { const { hmac, signature, ...query } = params; const queryString = signator === 'admin' ? stringifyQueryForAdmin(query) : stringifyQueryForAppProxy(query); return utils.createSHA256HMAC(config.apiSecretKey, queryString, types$2.HashFormat.Hex); }; } function validateHmac(config) { return async (query, { signator } = { signator: 'admin' }) => { if (signator === 'admin' && !query.hmac) { throw new error.InvalidHmacError('Query does not contain an HMAC value.'); } if (signator === 'appProxy' && !query.signature) { throw new error.InvalidHmacError('Query does not contain a signature value.'); } validateHmacTimestamp(query); const hmac = signator === 'appProxy' ? query.signature : query.hmac; const localHmac = await generateLocalHmac(config)(query, signator); return safeCompare.safeCompare(hmac, localHmac); }; } async function validateHmacString(config, data, hmac, format) { const localHmac = await utils.createSHA256HMAC(config.apiSecretKey, data, format); return safeCompare.safeCompare(hmac, localHmac); } function getCurrentTimeInSec() { return Math.trunc(Date.now() / 1000); } function validateHmacFromRequestFactory(config) { return async function validateHmacFromRequest({ type, rawBody, ...adapterArgs }) { const request = await index.abstractConvertRequest(adapterArgs); if (!rawBody.length) { return fail(types.ValidationErrorReason.MissingBody, type, config); } const hmac = headers.getHeader(request.headers, types$1.ShopifyHeader.Hmac); if (!hmac) { return fail(types.ValidationErrorReason.MissingHmac, type, config); } const validHmac = await validateHmacString(config, rawBody, hmac, types$2.HashFormat.Base64); if (!validHmac) { return fail(types.ValidationErrorReason.InvalidHmac, type, config); } return succeed(type, config); }; } function validateHmacTimestamp(query) { if (Math.abs(getCurrentTimeInSec() - Number(query.timestamp)) > HMAC_TIMESTAMP_PERMITTED_CLOCK_TOLERANCE_SEC) { throw new error.InvalidHmacError('HMAC timestamp is outside of the tolerance range'); } } async function fail(reason, type, config) { const log = index$1.logger(config); await log.debug(`${type} request is not valid`, { reason }); return { valid: false, reason, }; } async function succeed(type, config) { const log = index$1.logger(config); await log.debug(`${type} request is valid`); return { valid: true, }; } exports.generateLocalHmac = generateLocalHmac; exports.getCurrentTimeInSec = getCurrentTimeInSec; exports.validateHmac = validateHmac; exports.validateHmacFromRequestFactory = validateHmacFromRequestFactory; exports.validateHmacString = validateHmacString; //# sourceMappingURL=hmac-validator.js.map