node-ebics-client/lib/Client.js

249 lines
6.2 KiB
JavaScript
Raw Permalink Normal View History

2018-05-17 15:03:59 +00:00
'use strict';
2018-06-01 13:16:43 +00:00
const fs = require('fs');
const path = require('path');
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
const crypto = require('crypto');
const $request = require('request');
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
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');
2018-05-17 15:03:59 +00:00
const ParseResponse = require('./middleware/ParseResponse');
2018-06-01 13:16:43 +00:00
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');
2018-05-17 15:03:59 +00:00
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();
2018-06-01 13:16:43 +00:00
},
},
};
2018-05-17 15:03:59 +00:00
module.exports = class Client {
2018-06-01 13:16:43 +00:00
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() : {};
}
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
a() {
return this.keys.A006;
}
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
e() {
return this.keys.E002;
}
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
x() {
return this.keys.X002;
2018-05-17 15:03:59 +00:00
}
bankX() {
return this.keys[`${this.hostId}.X002`];
}
bankE() {
return this.keys[`${this.hostId}.E002`];
}
encrypt(data) {
2018-06-01 13:16:43 +00:00
const cipher = crypto.createCipher(this.encryptAlgorithm, this.passphrase);
2018-05-17 15:03:59 +00:00
const encrypted = cipher.update(data, 'utf8', 'hex') + cipher.final('hex');
return Buffer.from(encrypted).toString('base64');
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
decrypt(data) {
2018-06-01 13:16:43 +00:00
data = (new Buffer(data, 'base64')).toString(); // eslint-disable-line no-buffer-constructor
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
const decipher = crypto.createDecipher(this.encryptAlgorithm, this.passphrase);
2018-05-17 15:03:59 +00:00
const decrypted = decipher.update(data, 'hex', 'utf8') + decipher.final('utf8');
return decrypted;
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
static setup(passphrase, url, hostId, userId, partnerId, keysize = 2048) {
2018-05-17 15:03:59 +00:00
const client = new Client(null, passphrase, url, hostId, userId, partnerId);
2018-06-01 13:16:43 +00:00
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 }));
2018-05-17 15:03:59 +00:00
return client;
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
saveIniLetter(bankName, target, template) {
const letter = new BankLetter({
client: this,
bankName,
template: template || fs.readFileSync(defaultIniTemplatePath, { encoding: 'utf8' }),
});
2018-05-17 15:03:59 +00:00
try {
2018-06-01 13:16:43 +00:00
fs.writeFileSync(target, letter.generate());
2018-05-17 15:03:59 +00:00
} catch (error) {
throw error;
}
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
saveKeys(target) {
const data = {};
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
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());
2018-05-17 15:03:59 +00:00
try {
2018-06-01 13:16:43 +00:00
fs.writeFileSync(target, JSON.stringify(data));
} catch (error) {
2018-05-17 15:03:59 +00:00
throw error;
}
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
extractKeys() {
2018-06-01 13:16:43 +00:00
const keys = {};
2018-05-17 15:03:59 +00:00
const jsonData = JSON.parse(this.keysContent);
2018-06-01 13:16:43 +00:00
Object.keys(jsonData).forEach((key) => {
2018-05-17 15:03:59 +00:00
keys[key] = new Key(this.decrypt(jsonData[key]));
2018-06-01 13:16:43 +00:00
});
// for (const key in jsonData)
// keys[key] = new Key(this.decrypt(jsonData[key]));
2018-05-17 15:03:59 +00:00
return keys;
}
async download(order) {
const res = await this.ebicsRequest(order.toXML());
2018-06-01 13:16:43 +00:00
const ttt = res.toXML(); // TODO: keep this for debugging purposes
2018-05-17 15:03:59 +00:00
order.transactionId = res.transactionId();
if (res.isSegmented() && res.isLastSegment()) {
const receipt = await this.ebicsRequest(order.toReceiptXML());
2018-06-01 13:16:43 +00:00
const receiptXML = order.toReceiptXML(); // TODO: keep this for debugging purposes
const rX = receipt.toXML(); // TODO: keep this for debugging purposes
2018-05-17 15:03:59 +00:00
}
return res.orderData();
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
2018-05-31 09:08:07 +00:00
async upload(order) {
let res = await this.ebicsRequest(order.toXML());
order.transactionId = res.transactionId();
2018-06-01 13:16:43 +00:00
// const orderId = res.orderId();
2018-05-31 09:08:07 +00:00
res = await this.ebicsRequest(order.toTransferXML());
return res.transactionId();
}
2018-06-01 13:16:43 +00:00
async downloadAndUnzip(order) { // eslint-disable-line
2018-05-17 15:03:59 +00:00
}
ebicsRequest(order) {
return new Promise((resolve, reject) => {
$request.post({
2018-06-01 13:16:43 +00:00
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))));
2018-05-17 15:03:59 +00:00
});
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
async INI() {
2018-05-17 15:03:59 +00:00
return this.ebicsRequest((new EBICSINI(this)).toXML());
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
2018-06-01 13:16:43 +00:00
async HIA() {
2018-05-17 15:03:59 +00:00
return this.ebicsRequest((new EBICSHIA(this)).toXML());
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
async HPB() {
const data = await this.download(new EBICSHPB(this));
2018-06-01 13:16:43 +00:00
const doc = new DOMParser().parseFromString(data, 'text/xml');
const sel = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' });
const keyNodes = sel('//xmlns:PubKeyValue', doc);
2018-05-17 15:03:59 +00:00
// console.log(keyNodes);
2018-06-01 13:16:43 +00:00
function xmlLastChild(node) {
2018-05-17 15:03:59 +00:00
let y = node.lastChild;
2018-06-01 13:16:43 +00:00
while (y.nodeType !== 1) y = y.previousSibling;
2018-05-17 15:03:59 +00:00
return y;
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
for (let i = 0; i < keyNodes.length; i++) {
2018-06-01 13:16:43 +00:00
const type = xmlLastChild(keyNodes[i].parentNode).textContent;
const modulus = xpath.select("//*[local-name(.)='Modulus']", keyNodes[i])[0].textContent;
2018-05-17 15:03:59 +00:00
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()];
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
HKD() {
return this.download(new EBICSHKD(this));
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
HAA() {
return this.download(new EBICSHAA(this));
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
HTD() {
return this.download(new EBICSHTD(this));
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
HAC(from = null, to = null) {
return this.download(new EBICSHAC(this, from, to));
2018-06-01 13:16:43 +00:00
}
2018-05-17 15:03:59 +00:00
C52(from, to) {
return this.downloadAndUnzip(new EBICSC52(this, from, to));
}
};