diff --git a/lib/Client.js b/lib/Client.js index a618926..48d97c0 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -1,6 +1,6 @@ 'use strict'; -const $request = require('request'); +const rock = require('rock-req'); const constants = require('./consts'); const Keys = require('./keymanagers/Keys'); @@ -52,7 +52,6 @@ const stringifyKeys = (keys) => { * @property {string} storageLocation - Location where to store the files that are downloaded. This can be a network share for example. */ - module.exports = class Client { /** *Creates an instance of Client. @@ -71,18 +70,17 @@ module.exports = class Client { languageCode, storageLocation, }) { - if (!url) - throw new Error('EBICS URL is required'); - if (!partnerId) - throw new Error('partnerId is required'); - if (!userId) - throw new Error('userId is required'); - if (!hostId) - throw new Error('hostId is required'); - if (!passphrase) - throw new Error('passphrase is required'); + if (!url) throw new Error('EBICS URL is required'); + if (!partnerId) throw new Error('partnerId is required'); + if (!userId) throw new Error('userId is required'); + if (!hostId) throw new Error('hostId is required'); + if (!passphrase) throw new Error('passphrase is required'); - if (!keyStorage || typeof keyStorage.read !== 'function' || typeof keyStorage.write !== 'function') + if ( + !keyStorage + || typeof keyStorage.read !== 'function' + || typeof keyStorage.write !== 'function' + ) throw new Error('keyStorage implementation missing or wrong'); this.url = url; @@ -99,17 +97,25 @@ module.exports = class Client { } async send(order) { - const isInObject = ('operation' in order); + const isInObject = 'operation' in order; if (!isInObject) throw new Error('Operation for the order needed'); - if (order.operation.toUpperCase() === constants.orderOperations.ini) return this.initialization(order); + if (order.operation.toUpperCase() === constants.orderOperations.ini) + return this.initialization(order); const keys = await this.keys(); - if (keys === null) throw new Error('No keys provided. Can not send the order or any other order for that matter.'); + if (keys === null) + throw new Error( + 'No keys provided. Can not send the order or any other order for that matter.', + ); - if (order.operation.toUpperCase() === constants.orderOperations.upload) return this.upload(order); - if (order.operation.toUpperCase() === constants.orderOperations.download) return this.download(order); + if (order.operation.toUpperCase() === constants.orderOperations.upload) + return this.upload(order); + if ( + order.operation.toUpperCase() === constants.orderOperations.download + ) + return this.download(order); throw new Error('Wrong order operation provided'); } @@ -118,8 +124,7 @@ module.exports = class Client { const keys = await this.keys(); if (keys === null) await this._generateKeys(); - if (this.tracesStorage) - this.tracesStorage.new().ofType('ORDER.INI'); + if (this.tracesStorage) this.tracesStorage.new().ofType('ORDER.INI'); const res = await this.ebicsRequest(order); const xml = res.orderData(); @@ -132,7 +137,9 @@ module.exports = class Client { technicalCode: returnedTechnicalCode, technicalCodeSymbol: res.technicalSymbol(), - technicalCodeShortText: res.technicalShortText(returnedTechnicalCode), + technicalCodeShortText: res.technicalShortText( + returnedTechnicalCode, + ), technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode), businessCode: returnedBusinessCode, @@ -167,7 +174,9 @@ module.exports = class Client { technicalCode: returnedTechnicalCode, technicalCodeSymbol: res.technicalSymbol(), - technicalCodeShortText: res.technicalShortText(returnedTechnicalCode), + technicalCodeShortText: res.technicalShortText( + returnedTechnicalCode, + ), technicalCodeMeaning: res.technicalMeaning(returnedTechnicalCode), businessCode: returnedBusinessCode, @@ -178,8 +187,7 @@ module.exports = class Client { } async upload(order) { - if (this.tracesStorage) - this.tracesStorage.new().ofType('ORDER.UPLOAD'); + if (this.tracesStorage) this.tracesStorage.new().ofType('ORDER.UPLOAD'); let res = await this.ebicsRequest(order); const transactionId = res.transactionId(); const orderId = res.orderId(); @@ -197,32 +205,46 @@ module.exports = class Client { return new Promise(async (resolve, reject) => { const { version } = order; const keys = await this.keys(); - const r = signer.version(version).sign((await serializer.use(order, this)).toXML(), keys.x()); + const r = signer + .version(version) + .sign((await serializer.use(order, this)).toXML(), keys.x()); if (this.tracesStorage) - this.tracesStorage.label(`REQUEST.${order.orderDetails.OrderType}`).data(r).persist(); + this.tracesStorage + .label(`REQUEST.${order.orderDetails.OrderType}`) + .data(r) + .persist(); - $request.post({ - url: this.url, - body: r, - headers: { 'content-type': 'text/xml;charset=UTF-8' }, - }, (err, res, data) => { - if (err) reject(err); + rock.post( + this.url, + { + body: r, + headers: { 'content-type': 'text/xml;charset=UTF-8' }, + }, + (err, res, data) => { + if (err) reject(err); - const ebicsResponse = response.version(version)(data, keys); + const ebicsResponse = response.version(version)(data, keys); - if (this.tracesStorage) - this.tracesStorage.label(`RESPONSE.${order.orderDetails.OrderType}`).connect().data(ebicsResponse.toXML()).persist(); + if (this.tracesStorage) + this.tracesStorage + .label(`RESPONSE.${order.orderDetails.OrderType}`) + .connect() + .data(ebicsResponse.toXML()) + .persist(); - resolve(ebicsResponse); - }); + resolve(ebicsResponse); + }, + ); }); } async signOrder(order) { const { version } = order; const keys = await this.keys(); - return signer.version(version).sign((await serializer.use(order, this)).toXML(), keys.x()); + return signer + .version(version) + .sign((await serializer.use(order, this)).toXML(), keys.x()); } async keys() { @@ -253,6 +275,8 @@ module.exports = class Client { } _writeKeys(keysObject) { - return this.keyStorage.write(this.keyEncryptor.encrypt(stringifyKeys(keysObject.keys))); + return this.keyStorage.write( + this.keyEncryptor.encrypt(stringifyKeys(keysObject.keys)), + ); } }; diff --git a/lib/middleware/signer.js b/lib/middleware/signer.js index f836798..8c67038 100644 --- a/lib/middleware/signer.js +++ b/lib/middleware/signer.js @@ -1,11 +1,11 @@ -"use strict"; +'use strict'; -const H004Signer = require("../orders/H004/signer"); +const H004Signer = require('../orders/H004/signer'); module.exports = { version(v) { - if (v.toUpperCase() === "H004") return H004Signer; + if (v.toUpperCase() === 'H004') return H004Signer; - throw Error("Error from middleware/signer.js: Invalid version number"); + throw Error('Error from middleware/signer.js: Invalid version number'); }, }; diff --git a/lib/orders/H004/response.js b/lib/orders/H004/response.js index 5b66d15..db364ea 100644 --- a/lib/orders/H004/response.js +++ b/lib/orders/H004/response.js @@ -1,13 +1,13 @@ -"use strict"; +'use strict'; -const zlib = require("zlib"); -const crypto = require("crypto"); +const zlib = require('zlib'); +const crypto = require('crypto'); -const Crypto = require("../../crypto/Crypto"); +const Crypto = require('../../crypto/Crypto'); -const { DOMParser, XMLSerializer } = require("@xmldom/xmldom"); -const xpath = require("xpath"); -const errors = require("./errors"); +const { DOMParser, XMLSerializer } = require('@xmldom/xmldom'); +const xpath = require('xpath'); +const errors = require('./errors'); const DEFAULT_IV = Buffer.from(Array(16).fill(0, 0, 15)); @@ -21,23 +21,23 @@ const lastChild = (node) => { module.exports = (xml, keys) => ({ keys, - doc: new DOMParser().parseFromString(xml, "text/xml"), + doc: new DOMParser().parseFromString(xml, 'text/xml'), isSegmented() { - const select = xpath.useNamespaces({ xmlns: "urn:org:ebics:H004" }); + const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' }); const node = select( - "//xmlns:header/xmlns:mutable/xmlns:SegmentNumber", - this.doc + '//xmlns:header/xmlns:mutable/xmlns:SegmentNumber', + this.doc, ); return !!node.length; }, isLastSegment() { - const select = xpath.useNamespaces({ xmlns: "urn:org:ebics:H004" }); + const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' }); const node = select( "//xmlns:header/xmlns:mutable/*[@lastSegment='true']", - this.doc + this.doc, ); return !!node.length; @@ -45,20 +45,20 @@ module.exports = (xml, keys) => ({ orderData() { const orderDataNode = this.doc.getElementsByTagNameNS( - "urn:org:ebics:H004", - "OrderData" + 'urn:org:ebics:H004', + 'OrderData', ); if (!orderDataNode.length) return {}; const orderData = orderDataNode[0].textContent; const decipher = crypto - .createDecipheriv("aes-128-cbc", this.transactionKey(), DEFAULT_IV) + .createDecipheriv('aes-128-cbc', this.transactionKey(), DEFAULT_IV) .setAutoPadding(false); const data = Buffer.from( - decipher.update(orderData, "base64", "binary") + - decipher.final("binary"), - "binary" + decipher.update(orderData, 'base64', 'binary') + + decipher.final('binary'), + 'binary', ); return zlib.inflateSync(data); @@ -66,40 +66,40 @@ module.exports = (xml, keys) => ({ transactionKey() { const keyNodeText = this.doc.getElementsByTagNameNS( - "urn:org:ebics:H004", - "TransactionKey" + 'urn:org:ebics:H004', + 'TransactionKey', )[0].textContent; return Crypto.privateDecrypt( this.keys.e(), - Buffer.from(keyNodeText, "base64") + Buffer.from(keyNodeText, 'base64'), ); }, transactionId() { - const select = xpath.useNamespaces({ xmlns: "urn:org:ebics:H004" }); + const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' }); const node = select( - "//xmlns:header/xmlns:static/xmlns:TransactionID", - this.doc + '//xmlns:header/xmlns:static/xmlns:TransactionID', + this.doc, ); - return node.length ? node[0].textContent : ""; + return node.length ? node[0].textContent : ''; }, orderId() { - const select = xpath.useNamespaces({ xmlns: "urn:org:ebics:H004" }); + const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' }); const node = select( - ".//xmlns:header/xmlns:mutable/xmlns:OrderID", - this.doc + './/xmlns:header/xmlns:mutable/xmlns:OrderID', + this.doc, ); - return node.length ? node[0].textContent : ""; + return node.length ? node[0].textContent : ''; }, businessCode() { - const select = xpath.useNamespaces({ xmlns: "urn:org:ebics:H004" }); - const node = select("//xmlns:body/xmlns:ReturnCode", this.doc); + const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' }); + const node = select('//xmlns:body/xmlns:ReturnCode', this.doc); - return node.length ? node[0].textContent : ""; + return node.length ? node[0].textContent : ''; }, businessSymbol(code) { @@ -115,23 +115,23 @@ module.exports = (xml, keys) => ({ }, technicalCode() { - const select = xpath.useNamespaces({ xmlns: "urn:org:ebics:H004" }); + const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' }); const node = select( - "//xmlns:header/xmlns:mutable/xmlns:ReturnCode", - this.doc + '//xmlns:header/xmlns:mutable/xmlns:ReturnCode', + this.doc, ); - return node.length ? node[0].textContent : ""; + return node.length ? node[0].textContent : ''; }, technicalSymbol() { - const select = xpath.useNamespaces({ xmlns: "urn:org:ebics:H004" }); + const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' }); const node = select( - "//xmlns:header/xmlns:mutable/xmlns:ReportText", - this.doc + '//xmlns:header/xmlns:mutable/xmlns:ReportText', + this.doc, ); - return node.length ? node[0].textContent : ""; + return node.length ? node[0].textContent : ''; }, technicalShortText(code) { @@ -146,9 +146,9 @@ module.exports = (xml, keys) => ({ const orderData = this.orderData().toString(); if (!Object.keys(orderData).length) return {}; - const doc = new DOMParser().parseFromString(orderData, "text/xml"); - const select = xpath.useNamespaces({ xmlns: "urn:org:ebics:H004" }); - const keyNodes = select("//xmlns:PubKeyValue", doc); + const doc = new DOMParser().parseFromString(orderData, 'text/xml'); + const select = xpath.useNamespaces({ xmlns: 'urn:org:ebics:H004' }); + const keyNodes = select('//xmlns:PubKeyValue', doc); const bankKeys = {}; if (!keyNodes.length) return {}; @@ -157,15 +157,15 @@ module.exports = (xml, keys) => ({ const type = lastChild(keyNodes[i].parentNode).textContent; const modulus = xpath.select( ".//*[local-name(.)='Modulus']", - keyNodes[i] + keyNodes[i], )[0].textContent; const exponent = xpath.select( ".//*[local-name(.)='Exponent']", - keyNodes[i] + keyNodes[i], )[0].textContent; - const mod = Buffer.from(modulus, "base64"); - const exp = Buffer.from(exponent, "base64"); + const mod = Buffer.from(modulus, 'base64'); + const exp = Buffer.from(exponent, 'base64'); bankKeys[`bank${type}`] = { mod, exp, diff --git a/lib/orders/H004/signer.js b/lib/orders/H004/signer.js index 2d89445..15f8a8e 100644 --- a/lib/orders/H004/signer.js +++ b/lib/orders/H004/signer.js @@ -1,49 +1,48 @@ -"use strict"; +'use strict'; // const crypto = require('crypto'); -const Crypto = require("../../crypto/Crypto"); +const Crypto = require('../../crypto/Crypto'); -const { DOMParser, XMLSerializer } = require("@xmldom/xmldom"); -const xpath = require("xpath"); -const C14n = - require("xml-crypto/lib/c14n-canonicalization").C14nCanonicalization; +const { DOMParser, XMLSerializer } = require('@xmldom/xmldom'); +const xpath = require('xpath'); +const C14n = require('xml-crypto/lib/c14n-canonicalization').C14nCanonicalization; const digest = (doc) => { // get the xml node, where the digested value is supposed to be - const nodeDigestValue = doc.getElementsByTagName("ds:DigestValue")[0]; + const nodeDigestValue = doc.getElementsByTagName('ds:DigestValue')[0]; // canonicalize the node that has authenticate='true' attribute const contentToDigest = xpath .select("//*[@authenticate='true']", doc) - .map((x) => new C14n().process(x)) - .join(""); + .map(x => new C14n().process(x)) + .join(''); // fix the canonicalization const fixedContent = contentToDigest.replace( /xmlns="urn:org:ebics:H004"/g, - 'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"' + 'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', ); if (nodeDigestValue) nodeDigestValue.textContent = Crypto.digestWithHash(fixedContent) - .toString("base64") + .toString('base64') .trim(); return doc; }; const sign = (doc, key) => { - const nodeSignatureValue = doc.getElementsByTagName("ds:SignatureValue")[0]; + const nodeSignatureValue = doc.getElementsByTagName('ds:SignatureValue')[0]; if (nodeSignatureValue) { const select = xpath.useNamespaces({ - ds: "http://www.w3.org/2000/09/xmldsig#", + ds: 'http://www.w3.org/2000/09/xmldsig#', }); const contentToSign = new C14n() - .process(select("//ds:SignedInfo", doc)[0]) + .process(select('//ds:SignedInfo', doc)[0]) .replace( 'xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', - 'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"' + 'xmlns="urn:org:ebics:H004" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"', ); nodeSignatureValue.textContent = Crypto.privateSign(key, contentToSign); // this.keys.x().key.sign(contentToSign, 'base64'); @@ -52,11 +51,11 @@ const sign = (doc, key) => { return doc; }; -const toXML = (doc) => new XMLSerializer().serializeToString(doc); +const toXML = doc => new XMLSerializer().serializeToString(doc); module.exports = { sign(data, keyX) { - const doc = new DOMParser().parseFromString(data, "text/xml"); + const doc = new DOMParser().parseFromString(data, 'text/xml'); return toXML(sign(digest(doc), keyX)); }, diff --git a/package-lock.json b/package-lock.json index 7de68fd..d15d09a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "js2xmlparser": "^5.0.0", "node-forge": "^1.3.1", "request": "^2.88.2", + "rock-req": "^5.1.3", "uuid": "^9.0.1", "xml-crypto": "^4.0.1", "xpath": "0.0.32" @@ -4277,6 +4278,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rock-req": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/rock-req/-/rock-req-5.1.3.tgz", + "integrity": "sha512-aorqEtp9YKws/KRiHlEbAHDWof/ILjTViTXMTe9juAgLjNrT9jsuQNM/cMbpK+zZsrFEHSHMCSK/wzi0DWelZA==", + "engines": { + "node": ">=16" + } + }, "node_modules/run-async": { "version": "2.4.1", "dev": true, diff --git a/package.json b/package.json index b0e4610..ce8dfe5 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ ], "scripts": { "lint": "eslint .", + "lint-fix": "eslint --fix .", "test": "nyc mocha test/**/*.js", "coverage": "nyc report --reporter=text-lcov | coveralls", "version": "auto-changelog -p -t changelog-template.hbs && git add CHANGELOG.md" @@ -59,13 +60,13 @@ ], "license": "GPL-3.0-only", "dependencies": { + "@xmldom/xmldom": "^0.8.10", "handlebars": "^4.7.8", "js2xmlparser": "^5.0.0", "node-forge": "^1.3.1", - "request": "^2.88.2", + "rock-req": "^5.1.3", "uuid": "^9.0.1", "xml-crypto": "^4.0.1", - "@xmldom/xmldom": "^0.8.10", "xpath": "0.0.32" }, "devDependencies": { diff --git a/test/spec/H004.js b/test/spec/H004.js index 03344e0..12936a4 100644 --- a/test/spec/H004.js +++ b/test/spec/H004.js @@ -1,19 +1,19 @@ -"use strict"; +'use strict'; /* eslint-env node, mocha */ -const { assert } = require("chai"); +const { assert } = require('chai'); -const path = require("path"); -const fs = require("fs"); +const path = require('path'); +const fs = require('fs'); -const ebics = require("../../"); +const ebics = require('../../'); -const libxml = require("libxmljs"); +const libxml = require('libxmljs'); -const schemaPath = path.resolve(__dirname, "../xsd/ebics_H004.xsd"); +const schemaPath = path.resolve(__dirname, '../xsd/ebics_H004.xsd'); const schemaDoc = libxml.parseXml( - fs.readFileSync(schemaPath, { encoding: "utf8" }) + fs.readFileSync(schemaPath, { encoding: 'utf8' }), ); const schemaDir = path.dirname(schemaPath); @@ -32,13 +32,13 @@ const validateXML = (str) => { }; const client = new ebics.Client({ - url: "https://iso20022test.credit-suisse.com/ebicsweb/ebicsweb", - partnerId: "CRS04381", - userId: "CRS04381", - hostId: "CRSISOTB", - passphrase: "test", + url: 'https://iso20022test.credit-suisse.com/ebicsweb/ebicsweb', + partnerId: 'CRS04381', + userId: 'CRS04381', + hostId: 'CRSISOTB', + passphrase: 'test', keyStorage: ebics.fsKeysStorage( - path.resolve(__dirname, "../support/TEST_KEYS.key") + path.resolve(__dirname, '../support/TEST_KEYS.key'), ), }); @@ -56,8 +56,8 @@ const CCS = require('./CCS'); const XE3 = require('./XE3'); const XCT = require('./XCT'); */ -const uploadBuilder = (fn) => fn(""); -const dateBuilder = (fn) => fn("2018-01-01", "2019-01-01"); +const uploadBuilder = fn => fn(''); +const dateBuilder = fn => fn('2018-01-01', '2019-01-01'); const fnOrders = { // upload | document @@ -87,12 +87,12 @@ const fnOrders = { }; const getOrderObject = (name, order) => { - if (typeof order === "object") return order; + if (typeof order === 'object') return order; if (fnOrders[name]) return fnOrders[name](order); return null; }; -describe("H004 order generation", () => { +describe('H004 order generation', () => { // eslint-disable-next-line no-restricted-syntax for (const [name, orderDefinition] of Object.entries(Orders)) { const order = getOrderObject(name, orderDefinition);