@code-nl/shopify-hmac
Version:
Shopify HMAC lib to verify Shopify calls
72 lines (58 loc) • 1.91 kB
JavaScript
;
const { hmacTimingSafeEqual, getHmacFromString } = require("./hmac");
/**
* Get the request object translated into a string as of Shopify would have signed it.
*
* @param {Object} request The request object
* @return {String} The query string as it would have been signed by Shopify
*/
exports.getSignableMessage = (request) => {
// clone the query object
const map = Object.assign({}, request.query);
// leave out the hmac itself
delete map.signature;
// get the querystring as a string
const message = Object.keys(map)
.sort()
.reduce((accumulator, keyName) => {
// value can be both a plain value and an array
const value = Array.isArray(map[keyName]) ? map[keyName].join(",") : map[keyName];
return [accumulator, `${keyName}=${value}`].join("");
}, '');
return message;
};
/**
* Generate a signature based on a query object.
*
* @param {Object} request The request object
* @param {String} secret API Secret
* @return {String} Generated HMAC
*/
exports.generate = (request, secret) => {
// get a signable message
const message = module.exports.getSignableMessage(request);
// generate hmac based on the message
const generatedHmac = getHmacFromString(message, secret, "hex");
return generatedHmac;
};
/**
* Verify request is from Shopify.
* HMAC verification based on the query string that is transmitted.
*
* @param {Object} request Request Query object
* @param {String} secret API Secret
* @return {Bool}
*/
exports.verify = (request, secret) => {
// get included hmac
const { signature } = request.query;
// guard: hmac is a required property
if (typeof signature !== "string" || !signature) {
return false;
}
// generate HMAC
const generatedHmac = module.exports.generate(request, secret);
// verify
const valid = hmacTimingSafeEqual(signature, generatedHmac);
return valid;
};