@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
JavaScript
;
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