Skip to content

Commit

Permalink
Merge pull request #129 from alma/feature/ecom-1820-sfcc-add-hmac-ver…
Browse files Browse the repository at this point in the history
…ification-on-ipn

Add hmac verification on IPN
  • Loading branch information
joyet-simon authored Sep 12, 2024
2 parents 1f831b1 + 5a60c69 commit a91c150
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 5 deletions.
20 changes: 17 additions & 3 deletions cartridges/int_alma/cartridge/controllers/Alma.js
Original file line number Diff line number Diff line change
Expand Up @@ -198,23 +198,37 @@ server.get(
server.get('IPN', function (req, res, next) {
var paymentHelper = require('*/cartridge/scripts/helpers/almaPaymentHelper');
var orderHelper = require('*/cartridge/scripts/helpers/almaOrderHelper');
var almaSecurityHelper = require('*/cartridge/scripts/helpers/almaSecurityHelper');
var almaHelpers = require('*/cartridge/scripts/helpers/almaHelpers');
var paymentObj = null;
var paymentId = req.querystring.pid;
var signature = req.httpHeaders.get('X-Alma-Signature');

try {
paymentObj = buildPaymentObj(req.querystring.pid);
almaSecurityHelper.checkIpnSignature(signature, paymentId, almaHelpers.getApiKey());
} catch (e) {
res.setStatusCode(500);
res.render('error', {
message: e.message
});
return next();
}

try {
paymentObj = buildPaymentObj(paymentId);
} catch (e) {
res.setStatusCode(500);
res.render('error', {
message: 'Can not find any payment for this order. Your order will fail.'
});
return next();
}
var order = getOrderByAlmaPaymentId(req.querystring.pid);
var order = getOrderByAlmaPaymentId(paymentId);

if (!order) {
var basketUuid = paymentObj.custom_data.basket_id;
order = paymentHelper.createOrderFromBasketUUID(basketUuid);
orderHelper.addPidToOrder(order, req.querystring.pid);
orderHelper.addPidToOrder(order, paymentId);
}

if (!order) {
Expand Down
14 changes: 12 additions & 2 deletions cartridges/int_alma/cartridge/scripts/helpers/almaHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,22 @@ function getSfccVersion() {
return sfccMajor + '.' + sfccMinor;
}


/**
* Return current Api key
* @returns {string} current api key
*/
function getApiKey() {
return Site.getCurrent().getCustomPreferenceValue('ALMA_APIKey');
}

/**
* Adds common headers to request
* @param {dw.svc.Service} service - current service instance
* @returns {dw.svc.Service} service
*/
function addHeaders(service) {
var apiKey = Site.getCurrent().getCustomPreferenceValue('ALMA_APIKey');
var apiKey = getApiKey();
if (!apiKey) {
logger.error('Alma api key is not configured');
return service;
Expand Down Expand Up @@ -223,5 +232,6 @@ module.exports = {
isAlmaOnShipment: isAlmaOnShipment,
getSfccVersion: getSfccVersion,
haveExcludedCategory: haveExcludedCategory,
formatItem: formatItem
formatItem: formatItem,
getApiKey: getApiKey
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

var Mac = require('dw/crypto/Mac');
var Encoding = require('dw/crypto/Encoding');


/**
* Check is the IPN signature is valid
* @param {string|null} almaSignature signature to check
* @param {string} paymentId Paymewnt id
* @param {string} key key to check the signature
* @throws Error
*/
function checkIpnSignature(almaSignature, paymentId, key) {
if (!almaSignature) {
throw new Error('There is no signature in header');
}

var mac = new Mac(Mac.HMAC_SHA_256);
var hmac = mac.digest(paymentId, key);
var hmacHex = Encoding.toHex(hmac);

if (hmacHex !== almaSignature) {
throw new Error('Signature is not valid');
}
}

module.exports = {
checkIpnSignature: checkIpnSignature
};
30 changes: 30 additions & 0 deletions test/mocks/helpers/almaSecurityHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

var proxyquire = require('proxyquire')
.noCallThru()
.noPreserveCache();

function mac() {
return {
digest: function () {
return 'good_byte_signature';
}
};
}

var encoding = {
toHex: function () {
return '4545854d3b8704d4b21cf88bc8b5da5680c46b2ab9d45c8cffe6278d8a8b1860';
}
};

function proxyModel() {
return proxyquire('../../../cartridges/int_alma/cartridge/scripts/helpers/almaSecurityHelper', {
'dw/crypto/Mac': mac,
'dw/crypto/Encoding': encoding
});
}

module.exports = {
almaSecurityHelper: proxyModel()
};
30 changes: 30 additions & 0 deletions test/unit/int_alma/scripts/helpers/almaSecurityHelperTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

// almaSecurityHelper.js unit tests

var assert = require('chai').assert;
var almaSecurityHelper = require('../../../../mocks/helpers/almaSecurityHelper').almaSecurityHelper;
var PAYMENT_ID = 'payment_id_test';
var API_KEY = 'api_key_test';
var BAD_SIGNATURE = 'bad_signature';
var GOOD_SIGNATURE = '4545854d3b8704d4b21cf88bc8b5da5680c46b2ab9d45c8cffe6278d8a8b1860';

describe('Alma security helper', function () {
it('checkIpnSignature throw error without signature in header', function () {
assert.throw(function () {
almaSecurityHelper.checkIpnSignature(null, PAYMENT_ID, API_KEY);
}, 'There is no signature in header');
});

it('checkIpnSignature throw error with bad signature', function () {
assert.throw(function () {
almaSecurityHelper.checkIpnSignature(BAD_SIGNATURE, PAYMENT_ID, '');
}, 'Signature is not valid');
});

it('checkIpnSignature not throw error with good signature', function () {
assert.doesNotThrow(function () {
almaSecurityHelper.checkIpnSignature(GOOD_SIGNATURE, PAYMENT_ID, API_KEY);
});
});
});

0 comments on commit a91c150

Please sign in to comment.