node-ebics-client/lib/Client.js
2018-06-01 16:16:43 +03:00

249 lines
6.2 KiB
JavaScript

'use strict';
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const $request = require('request');
const BN = require('bn.js');
const xpath = require('xpath');
const NodeRSA = require('node-rsa');
const { DOMParser } = require('xmldom');
const Key = require('./Key');
const XMLSign = require('./middleware/XMLSign');
const ParseResponse = require('./middleware/ParseResponse');
const BankLetter = require('./BankLetter');
const EBICSINI = require('./orders/INI');
const EBICSHIA = require('./orders/HIA');
const EBICSHPB = require('./orders/HPB');
const EBICSHKD = require('./orders/HKD');
const EBICSHAA = require('./orders/HAA');
const EBICSHAC = require('./orders/HAC');
const EBICSHTD = require('./orders/HTD');
const EBICSC52 = require('./orders/C52');
const defaultIniTemplatePath = path.join(__dirname, '../templates/ini.hbs');
const utils = {
exponent: {
// str = 65537 => AQAB
toBase64(str) {
return new BN(str).toBuffer().toString('base64');
},
// str = AQAB => 65537
fromBase64(str) {
return new BN(Buffer.from(str, 'base64'), 2).toNumber();
},
},
};
module.exports = class Client {
constructor(keysContent, passphrase, url, hostId, userId, partnerId) {
this.keysContent = keysContent;
this.passphrase = passphrase;
this.url = url;
this.hostId = hostId;
this.userId = userId;
this.partnerId = partnerId;
this.encryptAlgorithm = 'aes-256-cbc';
this.keys = keysContent ? this.extractKeys() : {};
}
a() {
return this.keys.A006;
}
e() {
return this.keys.E002;
}
x() {
return this.keys.X002;
}
bankX() {
return this.keys[`${this.hostId}.X002`];
}
bankE() {
return this.keys[`${this.hostId}.E002`];
}
encrypt(data) {
const cipher = crypto.createCipher(this.encryptAlgorithm, this.passphrase);
const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
return Buffer.from(encrypted).toString('base64');
}
decrypt(data) {
data = (new Buffer(data, 'base64')).toString(); // eslint-disable-line no-buffer-constructor
const decipher = crypto.createDecipher(this.encryptAlgorithm, this.passphrase);
const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
return decrypted;
}
static setup(passphrase, url, hostId, userId, partnerId, keysize = 2048) {
const client = new Client(null, passphrase, url, hostId, userId, partnerId);
Object.keys({ A006: '', X002: '', E002: '' }).forEach((key) => {
client.keys[key] = new Key(new NodeRSA({ b: keysize }));
});
// for (const key in Object.keys({ A006: '', X002: '', E002: '' }))
// client.keys[key] = new Key(new NodeRSA({ b: keysize }));
return client;
}
saveIniLetter(bankName, target, template) {
const letter = new BankLetter({
client: this,
bankName,
template: template || fs.readFileSync(defaultIniTemplatePath, { encoding: 'utf8' }),
});
try {
fs.writeFileSync(target, letter.generate());
} catch (error) {
throw error;
}
}
saveKeys(target) {
const data = {};
Object.keys(this.keys).forEach((key) => {
data[key] = this.encrypt(this.keys[key].toPem());
});
// for (const key in this.keys)
// data[key] = this.encrypt(this.keys[key].toPem());
try {
fs.writeFileSync(target, JSON.stringify(data));
} catch (error) {
throw error;
}
}
extractKeys() {
const keys = {};
const jsonData = JSON.parse(this.keysContent);
Object.keys(jsonData).forEach((key) => {
keys[key] = new Key(this.decrypt(jsonData[key]));
});
// for (const key in jsonData)
// keys[key] = new Key(this.decrypt(jsonData[key]));
return keys;
}
async download(order) {
const res = await this.ebicsRequest(order.toXML());
const ttt = res.toXML(); // TODO: keep this for debugging purposes
order.transactionId = res.transactionId();
if (res.isSegmented() && res.isLastSegment()) {
const receipt = await this.ebicsRequest(order.toReceiptXML());
const receiptXML = order.toReceiptXML(); // TODO: keep this for debugging purposes
const rX = receipt.toXML(); // TODO: keep this for debugging purposes
}
return res.orderData();
}
async upload(order) {
let res = await this.ebicsRequest(order.toXML());
order.transactionId = res.transactionId();
// const orderId = res.orderId();
res = await this.ebicsRequest(order.toTransferXML());
return res.transactionId();
}
async downloadAndUnzip(order) { // eslint-disable-line
}
ebicsRequest(order) {
return new Promise((resolve, reject) => {
$request.post({
url: this.url,
body: XMLSign.go(this, order),
headers: { 'content-type': 'text/xml;charset=UTF-8' },
}, (err, res, data) => (err ? reject(err) : resolve(ParseResponse.go(this, data))));
});
}
async INI() {
return this.ebicsRequest((new EBICSINI(this)).toXML());
}
async HIA() {
return this.ebicsRequest((new EBICSHIA(this)).toXML());
}
async HPB() {
const data = await this.download(new EBICSHPB(this));
const doc = new DOMParser().parseFromString(data, 'text/xml');
const sel = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const keyNodes = sel('//xmlns:PubKeyValue', doc);
// console.log(keyNodes);
function xmlLastChild(node) {
let y = node.lastChild;
while (y.nodeType !== 1) y = y.previousSibling;
return y;
}
for (let i = 0; i < keyNodes.length; i++) {
const type = xmlLastChild(keyNodes[i].parentNode).textContent;
const modulus = xpath.select("//*[local-name(.)='Modulus']", keyNodes[i])[0].textContent;
const exponent = xpath.select("//*[local-name(.)='Exponent']", keyNodes[i])[0].textContent;
const mod = new BN(Buffer.from(modulus, 'base64'), 2).toBuffer();
const exp = utils.exponent.fromBase64(exponent);
const bank = new NodeRSA();
bank.importKey({ n: mod, e: exp }, 'components-public');
this.keys[`${this.hostId}.${type}`] = new Key(bank);
}
return [this.bankX(), this.bankE()];
}
HKD() {
return this.download(new EBICSHKD(this));
}
HAA() {
return this.download(new EBICSHAA(this));
}
HTD() {
return this.download(new EBICSHTD(this));
}
HAC(from = null, to = null) {
return this.download(new EBICSHAC(this, from, to));
}
C52(from, to) {
return this.downloadAndUnzip(new EBICSC52(this, from, to));
}
};