273 lines
7.6 KiB
JavaScript
273 lines
7.6 KiB
JavaScript
/**
|
|
* Support utilities for anonymising data
|
|
*
|
|
*/
|
|
'use strict';
|
|
|
|
const _ = require('lodash');
|
|
const utils = require('../ComServe/utils.js');
|
|
|
|
module.exports = {
|
|
anonymisePhoneNumber,
|
|
anonymiseAccountNumber,
|
|
anonymiseSortCode,
|
|
anonymiseWorldpayServiceKey,
|
|
anonymiseCardPAN,
|
|
anonymiseMerchantID,
|
|
anonymiseAccount,
|
|
anonymiseDevice,
|
|
anonymiseAddress,
|
|
anonymiseKYC
|
|
};
|
|
|
|
/**
|
|
* Helper function to anonymise a phone number. This converts something like
|
|
* +441506592361
|
|
* into
|
|
* +44 1*** ***361
|
|
*
|
|
* @param {string} phoneNumber - the phone number string
|
|
* @returns {string} - the anonymised number
|
|
*/
|
|
function anonymisePhoneNumber(phoneNumber) {
|
|
let tempString;
|
|
|
|
/**
|
|
* We display 8 digits, so make sure we aren't returning almost everything.
|
|
*/
|
|
if (phoneNumber.length > 10) {
|
|
tempString =
|
|
phoneNumber.substr(0, 3) +
|
|
' ' +
|
|
phoneNumber.substr(3, 1) +
|
|
'*** ***' +
|
|
phoneNumber.substr(-3);
|
|
} else {
|
|
/**
|
|
* To short to be a good number, so just return an empty string (as if there was no number)
|
|
*/
|
|
tempString = '';
|
|
}
|
|
|
|
return tempString;
|
|
}
|
|
|
|
/**
|
|
* Anonymises an account number which is passed as a string. It retains the last 3 characters
|
|
* and adds 5 stars at the beginning regardless of actual length.
|
|
* - AccountNumber 12345678 => *****678
|
|
*
|
|
* @type {Function} anonymiseAccountNumber
|
|
* @param {!string} accountNumber - Expected input is an 8 digit string.
|
|
* @returns {!string} Anonymised account number.
|
|
*/
|
|
function anonymiseAccountNumber(accountNumber) {
|
|
if (!accountNumber) {
|
|
return '';
|
|
}
|
|
return ('*****' + accountNumber.substr(-3));
|
|
}
|
|
|
|
/**
|
|
* Anonymises a sort code which is passed as a string. It retains the last 2 characters
|
|
* and adds 4 stars and dashes at the beginning regardless of actual length.
|
|
* - SortCode 12-34-56 => **-**-56
|
|
*
|
|
* @type {Function} anonymiseSortCode
|
|
* @param {!string} sortCode - Expected input is an 8 character string.
|
|
* @returns {!string} Anonymised sort code number.
|
|
*/
|
|
function anonymiseSortCode(sortCode) {
|
|
if (!sortCode) {
|
|
return '';
|
|
}
|
|
return ('**-**-' + sortCode.substr(-2));
|
|
}
|
|
|
|
/**
|
|
* Anonymises a worldpay service key which is passed as a string. It retains the first 1 and last 4 characters,
|
|
* replaces all Hex characters with stars retaining dashes.
|
|
* - serviceKey T_S_713d2a60-a20b-4047-bc3a-3e863a11e414 => T_S_********-****-****-****-********e414
|
|
* The function does not work with 8 or less characters so simply returns what it received.
|
|
*
|
|
* @type {Function} anonymiseWorldpayServiceKey
|
|
* @param {!string} serviceKey - Expected input is an 40 character string.
|
|
* @returns {!string} Anonymised card PAN.
|
|
*/
|
|
function anonymiseWorldpayServiceKey(serviceKey) {
|
|
if (!serviceKey) {
|
|
throw new Error('service key not set');
|
|
}
|
|
if ((/^(?:T_S_|T_C_|L_S_|L_C_)[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(serviceKey))) {
|
|
let anonServiceKey = serviceKey.slice(0);
|
|
anonServiceKey = anonServiceKey.substr(0, 4) + '********-****-****-****-********' + anonServiceKey.substr(anonServiceKey.length - 4);
|
|
return anonServiceKey;
|
|
} else {
|
|
throw new Error('service key not consistent with a Worldpay service key');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Anonymises a card PAN which is passed as a string. It retains the first 1 and last 3 characters,
|
|
* adds stars and spaces after every quad in the middle regardless of actual length.
|
|
* - CardPAN 0123 4567 8901 2345=> 0*** **** **** *345
|
|
* The function does not work with 4 or less characters so simply returns what it received.
|
|
*
|
|
* @type {Function} anonymiseCardPAN
|
|
* @param {!string} cardPAN - Expected input is an 8 character string.
|
|
* @returns {!string} Anonymised card PAN.
|
|
*/
|
|
function anonymiseCardPAN(cardPAN) {
|
|
if (!cardPAN) {
|
|
throw new Error('cardPAN not set');
|
|
}
|
|
|
|
const tempCardPAN = cardPAN.slice(0).replace(/ /g, '');
|
|
|
|
if (tempCardPAN.length < 5) {
|
|
return tempCardPAN;
|
|
}
|
|
|
|
/**
|
|
* CardPAN is not always 16 digits.
|
|
*/
|
|
let anonPAN = tempCardPAN.substr(0, 1);
|
|
for (let xx = 1; xx < tempCardPAN.length; xx++) {
|
|
if ((xx % 4) === 0) {
|
|
anonPAN += ' ';
|
|
}
|
|
if (xx > (tempCardPAN.length - 4)) {
|
|
anonPAN += tempCardPAN.substr(xx, 1);
|
|
} else {
|
|
anonPAN += '*';
|
|
}
|
|
}
|
|
|
|
return anonPAN;
|
|
}
|
|
|
|
/**
|
|
* Anonymises a merchant acquirer ID which is passed as a string. It retains the last 3 characters
|
|
* and adds 5 stars at the beginning regardless of actual length.
|
|
* - AcquirerMerchantID ABCDEFGH => *****FGH
|
|
*
|
|
* @type {Function} anonymiseMerchantID
|
|
* @param {!string} merchantID - Expected input is an 8 digit string.
|
|
* @returns {!string} Anonymised merchant ID.
|
|
*/
|
|
function anonymiseMerchantID(merchantID) {
|
|
if (!merchantID) {
|
|
return '';
|
|
}
|
|
return ('*****' + merchantID.substr(-3));
|
|
}
|
|
|
|
/**
|
|
* Anonymises the given account by:
|
|
* 1. Deleting any fields that are not appropriate for this type of account
|
|
* 2. Anonymising any remaining fields.
|
|
*
|
|
* @param {Object} account - the account object to anonymise
|
|
* W074 cyclomatic complexity problem ignored on line. Suspected software error.
|
|
*/
|
|
function anonymiseAccount(account) { // jshint ignore:line
|
|
if (!account) {
|
|
return;
|
|
}
|
|
|
|
const fields = ['AccountNumber', 'SortCode', 'CardPAN', 'AcquirerMerchantID'];
|
|
const keep = [];
|
|
|
|
switch (account.AccountType) {
|
|
case utils.PaymentInstrumentType.CREDIT_DEBIT_PAYMENT_CARD:
|
|
keep.push('CardPAN');
|
|
break;
|
|
case 'Bank Account':
|
|
keep.push('AccountNumber');
|
|
keep.push('SortCode');
|
|
break;
|
|
case 'Credit/Debit Receiving Account':
|
|
keep.push('AcquirerMerchantID');
|
|
break;
|
|
default:
|
|
// Not a known type, so delete everything.
|
|
break;
|
|
}
|
|
|
|
_.forEach(
|
|
fields,
|
|
(value) => {
|
|
if (keep.indexOf(value) === -1) {
|
|
// Not in the keep list, so delete
|
|
delete account[value];
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Now anonymise anything that's left
|
|
*/
|
|
if (account.AccountNumber !== undefined) {
|
|
account.AccountNumber = anonymiseAccountNumber(account.AccountNumber);
|
|
}
|
|
if (account.SortCode !== undefined) {
|
|
account.SortCode = anonymiseSortCode(account.SortCode);
|
|
}
|
|
if (account.AcquirerMerchantID !== undefined) {
|
|
account.AcquirerMerchantID = anonymiseMerchantID(account.AcquirerMerchantID);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Anonymises the given device by:
|
|
* 1. Anonymising fields as follows:
|
|
* - DeviceNumber => +44 7*** ***234
|
|
*
|
|
* @param {Object} device - the device object to anonymise
|
|
*/
|
|
function anonymiseDevice(device) {
|
|
if (!device) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Anonymise fields
|
|
*/
|
|
if (device.DeviceNumber !== undefined) {
|
|
device.DeviceNumber = anonymisePhoneNumber(device.DeviceNumber);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Anonymises the given address by:
|
|
* 1. Anonymising fields as follows:
|
|
* - PhoneNumber => +44 1*** ***234
|
|
*
|
|
* @param {Object} address - the address object to anonymise
|
|
*/
|
|
function anonymiseAddress(address) {
|
|
if (!address) {
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Anonymise fields
|
|
*/
|
|
if (address.PhoneNumber) {
|
|
address.PhoneNumber = anonymisePhoneNumber(address.PhoneNumber);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Anonymises KYC data:
|
|
* 1. Remove the date of birth
|
|
*
|
|
* @param {Object} kyc - The object to be anonymised
|
|
*/
|
|
function anonymiseKYC(kyc) {
|
|
if (!kyc) {
|
|
return;
|
|
}
|
|
|
|
kyc.DateOfBirth = null;
|
|
}
|