Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hmac verification on IPN #129

Merged
merged 4 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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');
}

Benjamin-Freoua-Alma marked this conversation as resolved.
Show resolved Hide resolved
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 () {
Benjamin-Freoua-Alma marked this conversation as resolved.
Show resolved Hide resolved
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);
});
});
});