/** * 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; }