From 021f4e0d1b70f29644e6f0fcc94fd6ad243d0cce Mon Sep 17 00:00:00 2001 From: mdconaway Date: Mon, 23 May 2022 20:03:11 -0400 Subject: [PATCH] Closes #1024 -Add native SSL support -Add deprecation warning for missing host value -Add self-signed certificate factory -Add prettier formatter to package.json -Update README --- .github/README.md | 18 +- .github/docs/Application_configurations.md | 22 +- .github/docs/RTL_SSL_setup.md | 2 +- Sample-RTL-Config.json | 1 + backend/utils/app.js | 80 ++- backend/utils/authCheck.js | 2 +- backend/utils/certificateFactory.js | 287 ++++++++++ backend/utils/certificateIdentity.js | 42 ++ backend/utils/common.js | 394 +++++++++++--- backend/utils/config.js | 183 +++++-- backend/utils/cors.js | 10 +- backend/utils/csrf.js | 12 +- backend/utils/ssl.js | 91 ++++ package-lock.json | 27 +- package.json | 3 + rtl.js | 41 +- server/utils/app.ts | 88 +++- server/utils/authCheck.ts | 2 +- server/utils/certificateFactory.ts | 321 +++++++++++ server/utils/certificateIdentity.ts | 42 ++ server/utils/common.ts | 586 ++++++++++++++++----- server/utils/config.ts | 220 ++++++-- server/utils/cors.ts | 23 +- server/utils/csrf.ts | 15 +- server/utils/ssl.ts | 108 ++++ 25 files changed, 2289 insertions(+), 331 deletions(-) create mode 100644 backend/utils/certificateFactory.js create mode 100644 backend/utils/certificateIdentity.js create mode 100644 backend/utils/ssl.js create mode 100644 server/utils/certificateFactory.ts create mode 100644 server/utils/certificateIdentity.ts create mode 100644 server/utils/ssl.ts diff --git a/.github/README.md b/.github/README.md index bf5f07aa5..74b0fa608 100644 --- a/.github/README.md +++ b/.github/README.md @@ -79,6 +79,22 @@ Example RTL-Config.json: "multiPass": "password", "port": "3000", "defaultNodeIndex": 1, + "ssl": { + "key": null, + "cert": null, + "ca": null, + "altIp": "127.0.0.1", + "commonName": "localhost", + "countryName": "US", + "encryptionBits": 2048, + "stateName": "Florida", + "localityName": "Miami", + "organizationName": "RTL", + "organizationalUnit": "RTL", + "validForYears": 10, + "rejectUnauthorized": true, + "requestCert": false + }, "SSO": { "rtlSSO": 0, "rtlCookiePath": "", @@ -173,7 +189,7 @@ E.g. if the IP address of your node is 192.168.0.15 then open your browser at th 3. Config tweaks for running RTL server and LND on separate devices on the same network can be found [here](./docs/RTL_setups.md). 4. Any Other setup: **Please be advised, if you are accessing your node remotely via RTL, its critical to encrypt the communication via use of https. You can use solutions like nginx and letsencrypt or TOR to setup secure access for RTL.** -- Sample SSL setup guide can be found [here](./docs/RTL_SSL_setup.md) +- RTL now supports native SSL! See config options [here](./docs/Application_configurations.md). Alternatively, you can run nginx as an SSL proxy. An example nginx SSL setup guide can be found [here](./docs/RTL_SSL_setup.md) - (For advanced users) A sample SSL guide to serve remote access over an encrypted Tor connection can be found [here](./docs/RTL_TOR_setup.md) ### Troubleshooting diff --git a/.github/docs/Application_configurations.md b/.github/docs/Application_configurations.md index eb7e1f1cd..ab9e78a4e 100644 --- a/.github/docs/Application_configurations.md +++ b/.github/docs/Application_configurations.md @@ -1,6 +1,7 @@ RTL allows the user to configure and control specific application parameters for app customization and integration.
-The parameters can be configured via RTL-Config.json file or through environment variables defined at the OS level. Required
-parameters have `default` values for initial setup and can be updated after RTL server initial start.
+The parameters can be configured via RTL-Config.json file or through environment variables defined at the OS level.
+Required parameters have `default` values for initial setup and can be updated after RTL server initial start.
+SSL config parameter can be set as `undefined`, `true` (which uses self-signed certs), `false` (http only), or an `` as outlined below. `key`, `cert` and `ca` parameters are reserved for static file paths to valid SSL key files. If `key`, `cert`, or `ca` are used, no self-signed certificate options will be used. `rejectUnauthorized` and `requestCert` can be used in all cases to enforce x509 certificate validation.

### RTL-Config.json
``` @@ -9,6 +10,22 @@ parameters have `default` values for initial setup and can be updated after RTL "port": "", "host": "", "defaultNodeIndex": , + "ssl": { + "key": "", + "cert": "", + "ca": , + "altIp": "", + "commonName": "", + "countryName": "", + "stateName": "", + "localityName": "", + "organizationName": "", + "organizationalUnit": "", + "encryptionBits": , + "validForYears": , + "rejectUnauthorized": , + "requestCert": + }, "SSO": { "rtlSSO": , "rtlCookiePath": "", @@ -50,6 +67,7 @@ If the environment variables are set, it will take precedence over the parameter
PORT (port number for the rtl node server, default 3000, Required)
HOST (host for the rtl node server, default localhost, Optional)
+SSL (true, false, or a stringified JSON object formatted like the 'ssl' property above)
APP_PASSWORD (Plaintext password to be provided by the parent container, NOT suggested for standalone RTL applications, to be used by Umbrel) (Optional)
LN_IMPLEMENTATION (LND/CLN/ECL. Default 'LND', Required)
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://localhost:8080) (Required)
diff --git a/.github/docs/RTL_SSL_setup.md b/.github/docs/RTL_SSL_setup.md index 3abf92d4c..b4d7a8bcc 100644 --- a/.github/docs/RTL_SSL_setup.md +++ b/.github/docs/RTL_SSL_setup.md @@ -1,4 +1,4 @@ -### Setup https access for RTL +### Setup https forwarding for RTL Forward the ports 80 and 3002 on the router to the device running RTL. Allow the ports through the firewall of the device. diff --git a/Sample-RTL-Config.json b/Sample-RTL-Config.json index a633d7b7e..235eadf70 100644 --- a/Sample-RTL-Config.json +++ b/Sample-RTL-Config.json @@ -2,6 +2,7 @@ "multiPass": "password", "port": "3000", "defaultNodeIndex": 1, + "ssl": false, "SSO": { "rtlSSO": 0, "rtlCookiePath": "", diff --git a/backend/utils/app.js b/backend/utils/app.js index a5973ff88..13d33fd03 100644 --- a/backend/utils/app.js +++ b/backend/utils/app.js @@ -6,6 +6,7 @@ import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import CORS from './cors.js'; import CSRF from './csrf.js'; +import CertificateIdentity from './certificateIdentity.js'; import sharedRoutes from '../routes/shared/index.js'; import lndRoutes from '../routes/lnd/index.js'; import clnRoutes from '../routes/cln/index.js'; @@ -31,53 +32,106 @@ export class ExpressApplication { this.loadConfiguration = () => { this.config.setServerConfiguration(); }; - this.setCORS = () => { CORS.mount(this.app); }; - this.setCSRF = () => { CSRF.mount(this.app); }; + this.setCORS = () => { + CORS.mount(this.app); + }; + this.setCSRF = () => { + CSRF.mount(this.app); + }; this.setApplicationRoutes = () => { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Setting up Application Routes..' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'App', + msg: 'Setting up Application Routes..' + }); this.app.use(this.common.baseHref + '/api', sharedRoutes); this.app.use(this.common.baseHref + '/api/lnd', lndRoutes); this.app.use(this.common.baseHref + '/api/cln', clnRoutes); this.app.use(this.common.baseHref + '/api/ecl', eclRoutes); this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend'))); this.app.use((req, res, next) => { - res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); + res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : '', { secure: this.common.ssl ? true : false }); res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html')); }); this.app.use((err, req, res, next) => this.handleApplicationErrors(err, res)); - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'App', + msg: 'Application Routes Set' + }); }; this.handleApplicationErrors = (err, res) => { switch (err.code) { case 'EACCES': - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server requires elevated privileges' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'Server requires elevated privileges' + }); res.status(406).send('Server requires elevated privileges.'); break; case 'EADDRINUSE': - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is already in use' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'Server is already in use' + }); res.status(409).send('Server is already in use.'); break; case 'ECONNREFUSED': - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is down/locked' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'Server is down/locked' + }); res.status(401).send('Server is down/locked.'); break; case 'EBADCSRFTOKEN': - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Invalid CSRF token. Form tempered.' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'Invalid CSRF token. Form tempered.' + }); res.status(403).send('Invalid CSRF token, form tempered.'); break; default: - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'DEFUALT ERROR', error: err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'DEFUALT ERROR', + error: err + }); res.status(400).send(JSON.stringify(err)); break; } }; - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Starting Express Application..' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'App', + msg: 'Starting Express Application..' + }); + this.loadConfiguration(); this.app.set('trust proxy', true); - this.app.use(sessions({ secret: this.common.secret_key, saveUninitialized: true, cookie: { secure: false, maxAge: ONE_DAY }, resave: false })); + if (this.common.ssl && this.common.ssl.requestCert) { + this.app.use(CertificateIdentity(this.common.ssl.rejectUnauthorized)); + } + this.app.use(sessions({ + secret: this.common.secret_key, + saveUninitialized: true, + cookie: { secure: this.common.ssl ? true : false, maxAge: ONE_DAY }, + resave: false + })); this.app.use(cookieParser(this.common.secret_key)); this.app.use(bodyParser.json({ limit: '25mb' })); this.app.use(bodyParser.urlencoded({ extended: false, limit: '25mb' })); - this.loadConfiguration(); this.setCORS(); this.setCSRF(); this.setApplicationRoutes(); diff --git a/backend/utils/authCheck.js b/backend/utils/authCheck.js index f01cad295..0847a099e 100644 --- a/backend/utils/authCheck.js +++ b/backend/utils/authCheck.js @@ -4,7 +4,7 @@ import { Common } from './common.js'; import { Logger } from './logger.js'; const common = Common; const logger = Logger; -const csurfProtection = csurf({ cookie: true }); +const csurfProtection = csurf({ cookie: true, secure: Common.ssl ? true : false }); export const isAuthenticated = (req, res, next) => { try { const token = req.headers.authorization.split(' ')[1]; diff --git a/backend/utils/certificateFactory.js b/backend/utils/certificateFactory.js new file mode 100644 index 000000000..23d767c58 --- /dev/null +++ b/backend/utils/certificateFactory.js @@ -0,0 +1,287 @@ +import forge from 'node-forge'; +forge.options.usePureJavaScript = true; +//--------------------------------------------------------------------------------------- +//Private Module Variables +//--------------------------------------------------------------------------------------- +const { assign, freeze, keys } = Object; +const max32BitInt = 2147483647; +//--------------------------------------------------------------------------------------- +class CertificateFactory { + //--------------------------------------------------------------------------------------- + //Constructor + // + //Input: obj (options object) + //Output: certificate factory instance + //--------------------------------------------------------------------------------------- + constructor(opts = {}) { + this.forge = forge; + this.certBundle = null; + this.encryptionBits = 2048; + this.certType = 'component'; + this.validForYears = 10; + this.commonName = 'localhost'; + this.organizationalUnit = 'RTL'; + this.organizationName = 'RTL'; + this.countryName = 'US'; + this.stateName = 'New York'; + this.localityName = 'New York'; + this.altName = 'https://localhost'; + this.altIp = '127.0.0.1'; + const { altIp = '127.0.0.1', certType = 'component', commonName = 'localhost', countryName = 'US', encryptionBits = '2048', stateName = 'New York', localityName = 'New York', organizationName = 'RTL', organizationalUnit = 'RTL', validForYears = '10' } = opts; + assign(this, { + certBundle: null, + altName: `${commonName}`, + altIp, + certType, + commonName, + countryName, + encryptionBits: parseInt(encryptionBits, 10), + stateName, + localityName, + organizationName, + organizationalUnit, + validForYears: parseInt(validForYears, 10) + }); + } + //--------------------------------------------------------------------------------------- + //Method: getStaticBundle + // + //Input: type + //Output: bundle + // + //This method will generate a constant static cert bundle that can be "shared" between + //many classes that may require a common certificate set. It has a single input argument + //that allows external classes to specify which format they would like their cert bundle + //to be in. (string or buffer) + //--------------------------------------------------------------------------------------- + getStaticBundle(type = 'string', password = null) { + if (!this.certBundle) { + this.certBundle = freeze(this.generateRandomCerts()); + } + return this._convertBundle(this.certBundle, type, password); + } + //--------------------------------------------------------------------------------------- + //Method: _convertBundle + // + //Input: bundle , type + //Output: bundle + // + //This method will converts all key value pairs of a certificate bundle to the desired + //type and returns a new bundle. This also performs a defacto copy of the bundle to + //prevent outside callers from tampering with each other's returned bundle instance, or + //the internal cached bundle referenced by getStaticBundle. + //--------------------------------------------------------------------------------------- + _convertBundle(bundle, type, password) { + const newBundle = {}; + const bundleKeys = keys(bundle); + const converter = type === 'string' ? this._valueToString : this._valueToBuffer; + if (type === 'p12') { + return this._bundleToP12(bundle, password); + } + bundleKeys.forEach((k) => { + if (bundle[k]) { + newBundle[k] = converter(bundle[k]); + } + else { + newBundle[k] = bundle[k]; + } + }); + return newBundle; + } + //--------------------------------------------------------------------------------------- + //Method: _valueToString + // + //Input: v + //Output: v + // + //This method converts its input value to a string and returns it. + //--------------------------------------------------------------------------------------- + _valueToString(v) { + return `${v}`; + } + //--------------------------------------------------------------------------------------- + //Method: _valueToBuffer + // + //Input: v + //Output: v + // + //This method converts its input value to a buffer and returns it. + //--------------------------------------------------------------------------------------- + _valueToBuffer(v) { + return Buffer.from(v); + } + //--------------------------------------------------------------------------------------- + //Method: _bundleToP12 + // + //Input: bundle + //Output: p12Base64 + // + //This method converts a certificate bundle to a .p12 file that can be used in a browser. + //--------------------------------------------------------------------------------------- + _bundleToP12(bundle, password) { + const { forge: { asn1, pki, pkcs12, util } } = this; + const pemCertificate = pki.certificateFromPem(bundle.cert); + const pemKey = pki.privateKeyFromPem(bundle.key); + const p12Asn1 = pkcs12.toPkcs12Asn1(pemKey, pemCertificate, password, { + algorithm: '3des' + }); + const p12Bytes = asn1.toDer(p12Asn1).getBytes(); + const p12Base64 = util.encode64(p12Bytes); + return p12Base64; + } + //--------------------------------------------------------------------------------------- + //Method: generateRandomCerts + // + //Input: none + //Output: bundle + // + //This method utilizes node-forge to create a key/cert/ca bundle entirely in javascript. + //It uses the factory properties set in the factory constructor to configure the new + //key/cert/ca bundle, and then returns the newly generated bundle. Directly calling this + //method will result in new bundles with different private keys, but the certificate + //x509 attributes will match across all bundles generated with the same factory instance. + //--------------------------------------------------------------------------------------- + generateRandomCerts() { + const { forge: { pki } } = this; + //generate a keypair and create an X.509v3 certificate + const keys = pki.rsa.generateKeyPair(this.encryptionBits); + const cert = pki.createCertificate(); + cert.publicKey = keys.publicKey; + cert.serialNumber = Math.round(Math.random() * max32BitInt).toString(16); + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + this.validForYears); + const altNames = []; + let attrs; + if (this.certType === 'person') { + const OUArray = Array.isArray(this.organizationalUnit) + ? this.organizationalUnit.map((v) => { + return { + shortName: 'OU', + value: v + }; + }) + : [ + { + shortName: 'OU', + value: 'RTL' + }, + { + shortName: 'OU', + value: 'BTC' + }, + { + shortName: 'OU', + value: 'People' + } + ]; + attrs = [].concat([ + { + name: 'countryName', + value: this.countryName + }, + { + name: 'organizationName', + value: this.organizationName + } + ], OUArray, [ + { + name: 'commonName', + value: this.commonName + } + ]); + } + else { + attrs = [ + { + name: 'commonName', + value: this.commonName + }, + { + name: 'countryName', + value: this.countryName + }, + { + shortName: 'ST', + value: this.stateName + }, + { + name: 'localityName', + value: this.localityName + }, + { + name: 'organizationName', + value: this.organizationName + }, + { + shortName: 'OU', + value: this.organizationalUnit + } + ]; + if (this.altName) { + altNames.push({ + type: 6, + value: this.altName + }); + } + if (this.altIp) { + altNames.push({ + type: 7, + ip: this.altIp + }); + } + } + cert.setSubject(attrs); + cert.setIssuer(attrs); + cert.setExtensions([ + { + name: 'basicConstraints', + cA: true + }, + { + name: 'keyUsage', + keyCertSign: true, + digitalSignature: true, + nonRepudiation: true, + keyEncipherment: true, + dataEncipherment: true + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true, + codeSigning: true, + emailProtection: true, + timeStamping: true + }, + { + name: 'nsCertType', + client: true, + server: true, + email: true, + objsign: true, + sslCA: true, + emailCA: true, + objCA: true + }, + { + name: 'subjectAltName', + altNames: altNames + }, + { + name: 'subjectKeyIdentifier' + } + ]); + cert.sign(keys.privateKey); + // convert a Forge certificate to PEM + // for self generated certs, ca will be undefined + const pem = pki.certificateToPem(cert); + const certBundle = { + key: pki.privateKeyToPem(keys.privateKey), + cert: pem, + ca: undefined + }; + return certBundle; + } +} +export default CertificateFactory; diff --git a/backend/utils/certificateIdentity.js b/backend/utils/certificateIdentity.js new file mode 100644 index 000000000..338425719 --- /dev/null +++ b/backend/utils/certificateIdentity.js @@ -0,0 +1,42 @@ +function subjectToString(subject) { + const aggr = []; + Object.keys(subject).forEach((k) => { + if (typeof subject[k] === 'string') { + aggr.push(`${k}=${subject[k]}`); + } + else if (Array.isArray(subject[k]) && subject[k].length > 0) { + const temp = []; + subject[k].forEach((v) => { + temp.push(`${k}=${v}`); + }); + aggr.push(temp.reverse().join(',')); + } + }); + return aggr.reverse().join(','); +} +export default (rejectUnauthorized = false) => { + return function (req, res, next) { + const { connection, protocol } = req; + if (protocol === 'https' && (connection.authorized || rejectUnauthorized === false)) { + try { + const { subject } = connection.getPeerCertificate(); + const CN = subject.CN; + const DN = subjectToString(subject); + req.identity = { + DN, + CN + }; + return next(); + } + catch (e) { + //if this server requsts certs, but there is an error getting them, DO NOT PASS GO! + return res.status(500).json({ + message: e.toString() + }); + } + } + return res.status(403).json({ + message: 'forbidden' + }); + }; +}; diff --git a/backend/utils/common.js b/backend/utils/common.js index 012ea6d62..39f8c2295 100644 --- a/backend/utils/common.js +++ b/backend/utils/common.js @@ -12,6 +12,7 @@ export class CommonService { this.rtl_conf_file_path = ''; this.port = 3000; this.host = null; + this.ssl = false; this.rtl_pass = ''; this.flg_allow_password_update = true; this.rtl_secret2fa = ''; @@ -24,7 +25,20 @@ export class CommonService { this.read_dummy_data = false; this.baseHref = '/rtl'; this.dummy_data_array_from_file = []; - this.MONTHS = [{ name: 'JAN', days: 31 }, { name: 'FEB', days: 28 }, { name: 'MAR', days: 31 }, { name: 'APR', days: 30 }, { name: 'MAY', days: 31 }, { name: 'JUN', days: 30 }, { name: 'JUL', days: 31 }, { name: 'AUG', days: 31 }, { name: 'SEP', days: 30 }, { name: 'OCT', days: 31 }, { name: 'NOV', days: 30 }, { name: 'DEC', days: 31 }]; + this.MONTHS = [ + { name: 'JAN', days: 31 }, + { name: 'FEB', days: 28 }, + { name: 'MAR', days: 31 }, + { name: 'APR', days: 30 }, + { name: 'MAY', days: 31 }, + { name: 'JUN', days: 30 }, + { name: 'JUL', days: 31 }, + { name: 'AUG', days: 31 }, + { name: 'SEP', days: 30 }, + { name: 'OCT', days: 31 }, + { name: 'NOV', days: 30 }, + { name: 'DEC', days: 31 } + ]; this.getSwapServerOptions = (req) => { const swapOptions = { url: req.session.selectedNode.swap_server_url, @@ -34,13 +48,29 @@ export class CommonService { }; if (req.session.selectedNode.swap_macaroon_path) { try { - swapOptions.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.swap_macaroon_path, 'loop.macaroon')).toString('hex') }; + swapOptions.headers = { + 'Grpc-Metadata-macaroon': fs + .readFileSync(join(req.session.selectedNode.swap_macaroon_path, 'loop.macaroon')) + .toString('hex') + }; } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Loop macaroon Error', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Loop macaroon Error', + error: err + }); } } - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Swap Options', data: swapOptions }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Swap Options', + data: swapOptions + }); return swapOptions; }; this.getBoltzServerOptions = (req) => { @@ -52,23 +82,42 @@ export class CommonService { }; if (req.session.selectedNode.boltz_macaroon_path) { try { - boltzOptions.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.boltz_macaroon_path, 'admin.macaroon')).toString('hex') }; + boltzOptions.headers = { + 'Grpc-Metadata-macaroon': fs + .readFileSync(join(req.session.selectedNode.boltz_macaroon_path, 'admin.macaroon')) + .toString('hex') + }; } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Boltz macaroon Error', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Boltz macaroon Error', + error: err + }); } } - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Boltz Options', data: boltzOptions }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Boltz Options', + data: boltzOptions + }); return boltzOptions; }; this.getOptions = (req) => { if (req.session.selectedNode && req.session.selectedNode.options) { - req.session.selectedNode.options.method = (req.session.selectedNode.ln_implementation && req.session.selectedNode.ln_implementation.toUpperCase() !== 'ECL') ? 'GET' : 'POST'; + req.session.selectedNode.options.method = + req.session.selectedNode.ln_implementation && req.session.selectedNode.ln_implementation.toUpperCase() !== 'ECL' + ? 'GET' + : 'POST'; delete req.session.selectedNode.options.form; req.session.selectedNode.options.qs = {}; return req.session.selectedNode.options; } - return this.handleError({ statusCode: 401, message: 'Session expired after a day\'s inactivity' }, 'Session Expired', 'Session Expiry Error', this.initSelectedNode); + return this.handleError({ statusCode: 401, message: "Session expired after a day's inactivity" }, 'Session Expired', 'Session Expiry Error', this.initSelectedNode); }; this.updateSelectedNodeOptions = (req) => { if (!req.session.selectedNode) { @@ -84,18 +133,32 @@ export class CommonService { if (req.session.selectedNode && req.session.selectedNode.ln_implementation) { switch (req.session.selectedNode.ln_implementation.toUpperCase()) { case 'CLN': - req.session.selectedNode.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'access.macaroon'))).toString('base64') }; + req.session.selectedNode.options.headers = { + macaroon: Buffer.from(fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'access.macaroon'))).toString('base64') + }; break; case 'ECL': - req.session.selectedNode.options.headers = { authorization: 'Basic ' + Buffer.from(':' + req.session.selectedNode.ln_api_password).toString('base64') }; + req.session.selectedNode.options.headers = { + authorization: 'Basic ' + Buffer.from(':' + req.session.selectedNode.ln_api_password).toString('base64') + }; break; default: - req.session.selectedNode.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'admin.macaroon')).toString('hex') }; + req.session.selectedNode.options.headers = { + 'Grpc-Metadata-macaroon': fs + .readFileSync(join(req.session.selectedNode.macaroon_path, 'admin.macaroon')) + .toString('hex') + }; break; } } if (req.session.selectedNode) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Updated Node Options for ' + req.session.selectedNode.ln_node, data: req.session.selectedNode.options }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Updated Node Options for ' + req.session.selectedNode.ln_node, + data: req.session.selectedNode.options + }); } return { status: 200, message: 'Updated Successfully' }; } @@ -106,7 +169,13 @@ export class CommonService { json: true, form: null }; - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Update Selected Node Options Error', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Update Selected Node Options Error', + error: err + }); return { status: 502, message: err }; } }; @@ -126,19 +195,31 @@ export class CommonService { if (node.ln_implementation) { switch (node.ln_implementation.toUpperCase()) { case 'CLN': - node.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') }; + node.options.headers = { + macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') + }; break; case 'ECL': - node.options.headers = { authorization: 'Basic ' + Buffer.from(':' + node.ln_api_password).toString('base64') }; + node.options.headers = { + authorization: 'Basic ' + Buffer.from(':' + node.ln_api_password).toString('base64') + }; break; default: - node.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(node.macaroon_path, 'admin.macaroon')).toString('hex') }; + node.options.headers = { + 'Grpc-Metadata-macaroon': fs.readFileSync(join(node.macaroon_path, 'admin.macaroon')).toString('hex') + }; break; } } } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Common Set Options Error', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Common Set Options Error', + error: err + }); node.options = { url: '', rejectUnauthorized: false, @@ -146,7 +227,13 @@ export class CommonService { form: '' }; } - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Set Node Options for ' + node.ln_node, data: node.options }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Set Node Options for ' + node.ln_node, + data: node.options + }); }); this.updateSelectedNodeOptions(req); } @@ -168,23 +255,33 @@ export class CommonService { minutes = +minutes < 10 ? '0' + minutes : minutes; let seconds = myDate.getSeconds().toString(); seconds = +seconds < 10 ? '0' + seconds : seconds; - return days + '/' + this.MONTHS[myDate.getMonth()].name + '/' + myDate.getFullYear() + ' ' + hours + ':' + minutes + ':' + seconds; + return (days + + '/' + + this.MONTHS[myDate.getMonth()].name + + '/' + + myDate.getFullYear() + + ' ' + + hours + + ':' + + minutes + + ':' + + seconds); }; this.sortAscByKey = (array, key) => array.sort((a, b) => { const x = +a[key]; const y = +b[key]; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + return x < y ? -1 : x > y ? 1 : 0; }); this.sortAscByStrKey = (array, key) => array.sort((a, b) => { const x = a[key] ? a[key].toUpperCase() : ''; const y = b[key] ? b[key].toUpperCase() : ''; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); + return x < y ? -1 : x > y ? 1 : 0; }); this.sortDescByKey = (array, key) => { const temp = array.sort((a, b) => { const x = +a[key] ? +a[key] : 0; const y = +b[key] ? +b[key] : 0; - return (x > y) ? -1 : ((x < y) ? 1 : 0); + return x > y ? -1 : x < y ? 1 : 0; }); return temp; }; @@ -192,7 +289,7 @@ export class CommonService { const temp = array.sort((a, b) => { const x = a[key] ? a[key].toUpperCase() : ''; const y = b[key] ? b[key].toUpperCase() : ''; - return (x > y) ? -1 : ((x < y) ? 1 : 0); + return x > y ? -1 : x < y ? 1 : 0; }); return temp; }; @@ -211,7 +308,10 @@ export class CommonService { if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) { delete err.options.headers['Grpc-Metadata-macaroon']; } - if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) { + if (err.response && + err.response.request && + err.response.request.headers && + err.response.request.headers['Grpc-Metadata-macaroon']) { delete err.response.request.headers['Grpc-Metadata-macaroon']; } break; @@ -219,7 +319,10 @@ export class CommonService { if (err.options && err.options.headers && err.options.headers.macaroon) { delete err.options.headers.macaroon; } - if (err.response && err.response.request && err.response.request.headers && err.response.request.headers.macaroon) { + if (err.response && + err.response.request && + err.response.request.headers && + err.response.request.headers.macaroon) { delete err.response.request.headers.macaroon; } break; @@ -227,7 +330,10 @@ export class CommonService { if (err.options && err.options.headers && err.options.headers.authorization) { delete err.options.headers.authorization; } - if (err.response && err.response.request && err.response.request.headers && err.response.request.headers.authorization) { + if (err.response && + err.response.request && + err.response.request.headers && + err.response.request.headers.authorization) { delete err.response.request.headers.authorization; } break; @@ -237,24 +343,45 @@ export class CommonService { } break; } - this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : (typeof err === 'string') ? err : 'Unknown Error') }); + this.logger.log({ + selectedNode: selectedNode, + level: 'ERROR', + fileName: fileName, + msg: errMsg, + error: typeof err === 'object' ? JSON.stringify(err) : typeof err === 'string' ? err : 'Unknown Error' + }); const newErrorObj = { - statusCode: err.statusCode ? err.statusCode : err.status ? err.status : (err.error && err.error.code && err.error.code === 'ECONNREFUSED') ? 503 : 500, - message: (err.error && err.error.message) ? err.error.message : err.message ? err.message : errMsg, - error: ((err.error && err.error.error && err.error.error.error && typeof err.error.error.error === 'string') ? err.error.error.error : - (err.error && err.error.error && typeof err.error.error === 'string') ? err.error.error : - (err.error && err.error.error && err.error.error.message && typeof err.error.error.message === 'string') ? err.error.error.message : - (err.error && err.error.message && typeof err.error.message === 'string') ? err.error.message : - (err.error && typeof err.error === 'string') ? err.error : - (err.message && typeof err.message === 'string') ? err.message : (typeof err === 'string') ? err : 'Unknown Error') + statusCode: err.statusCode + ? err.statusCode + : err.status + ? err.status + : err.error && err.error.code && err.error.code === 'ECONNREFUSED' + ? 503 + : 500, + message: err.error && err.error.message ? err.error.message : err.message ? err.message : errMsg, + error: err.error && err.error.error && err.error.error.error && typeof err.error.error.error === 'string' + ? err.error.error.error + : err.error && err.error.error && typeof err.error.error === 'string' + ? err.error.error + : err.error && err.error.error && err.error.error.message && typeof err.error.error.message === 'string' + ? err.error.error.message + : err.error && err.error.message && typeof err.error.message === 'string' + ? err.error.message + : err.error && typeof err.error === 'string' + ? err.error + : err.message && typeof err.message === 'string' + ? err.message + : typeof err === 'string' + ? err + : 'Unknown Error' }; return newErrorObj; }; - this.getRequestIP = (req) => ((typeof req.headers['x-forwarded-for'] === 'string' && req.headers['x-forwarded-for'].split(',').shift()) || + this.getRequestIP = (req) => (typeof req.headers['x-forwarded-for'] === 'string' && req.headers['x-forwarded-for'].split(',').shift()) || req.ip || req.connection.remoteAddress || req.socket.remoteAddress || - (req.connection.socket ? req.connection.socket.remoteAddress : null)); + (req.connection.socket ? req.connection.socket.remoteAddress : null); this.getDummyData = (dataKey, lnImplementation) => { const dummyDataFile = this.rtl_conf_file_path + sep + 'ECLDummyData.log'; return new Promise((resolve, reject) => { @@ -262,10 +389,20 @@ export class CommonService { fs.readFile(dummyDataFile, 'utf8', (err, data) => { if (err) { if (err.code === 'ENOENT') { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Dummy data file does not exist' }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Dummy data file does not exist' + }); } else { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Getting dummy data failed' }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Getting dummy data failed' + }); } } else { @@ -286,7 +423,12 @@ export class CommonService { this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8'); } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading cookie: \n' + err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while reading cookie: \n' + err + }); throw new Error(err); } } @@ -298,7 +440,12 @@ export class CommonService { this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8'); } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading the cookie: \n' + err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while reading the cookie: \n' + err + }); throw new Error(err); } } @@ -309,7 +456,13 @@ export class CommonService { this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8'); } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while refreshing cookie', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Something went wrong while refreshing cookie', + error: err + }); throw new Error(err); } }; @@ -336,18 +489,31 @@ export class CommonService { }, initDir); }; this.replacePasswordWithHash = (multiPassHashed) => { - this.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(dirname(fileURLToPath(import.meta.url)), '../..'); + this.rtl_conf_file_path = process.env.RTL_CONFIG_PATH + ? process.env.RTL_CONFIG_PATH + : join(dirname(fileURLToPath(import.meta.url)), '../..'); try { const RTLConfFile = this.rtl_conf_file_path + sep + 'RTL-Config.json'; const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8')); config.multiPassHashed = multiPassHashed; delete config.multiPass; fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8'); - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Please note that, RTL has encrypted the plaintext password into its corresponding hash' }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Please note that, RTL has encrypted the plaintext password into its corresponding hash' + }); return config.multiPassHashed; } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Password hashing failed', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Password hashing failed', + error: err + }); } }; this.getAllNodeAllChannelBackup = (node) => { @@ -358,28 +524,63 @@ export class CommonService { json: true, headers: { 'Grpc-Metadata-macaroon': fs.readFileSync(node.macaroon_path + '/admin.macaroon').toString('hex') } }; - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Getting Channel Backup for Node ' + node.ln_node + '..' }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Getting Channel Backup for Node ' + node.ln_node + '..' + }); request(options).then((body) => { fs.writeFile(channel_backup_file, JSON.stringify(body), (err) => { if (err) { if (node.ln_node) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for Node ' + node.ln_node, error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Error in Channel Backup for Node ' + node.ln_node, + error: err + }); } else { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for File ' + channel_backup_file, error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Error in Channel Backup for File ' + channel_backup_file, + error: err + }); } } else { if (node.ln_node) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Successful in Channel Backup for Node ' + node.ln_node, data: body }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Successful in Channel Backup for Node ' + node.ln_node, + data: body + }); } else { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Successful in Channel Backup for File ' + channel_backup_file, data: body }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Successful in Channel Backup for File ' + channel_backup_file, + data: body + }); } } }); }, (err) => { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for Node ' + node.ln_node, error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Error in Channel Backup for Node ' + node.ln_node, + error: err + }); fs.writeFile(channel_backup_file, '', () => { }); }); }; @@ -387,27 +588,84 @@ export class CommonService { if (currentVersion) { const versionsArr = currentVersion.trim().replace('v', '').split('-')[0].split('.') || []; const checkVersionsArr = checkVersion.split('.'); - return (+versionsArr[0] > +checkVersionsArr[0]) || + return (+versionsArr[0] > +checkVersionsArr[0] || (+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) || - (+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] === +checkVersionsArr[1] && +versionsArr[2] >= +checkVersionsArr[2]); + (+versionsArr[0] === +checkVersionsArr[0] && + +versionsArr[1] === +checkVersionsArr[1] && + +versionsArr[2] >= +checkVersionsArr[2])); } return false; }; - this.getMonthDays = (selMonth, selYear) => ((selMonth === 1 && selYear % 4 === 0) ? (this.MONTHS[selMonth].days + 1) : this.MONTHS[selMonth].days); + this.getMonthDays = (selMonth, selYear) => selMonth === 1 && selYear % 4 === 0 ? this.MONTHS[selMonth].days + 1 : this.MONTHS[selMonth].days; this.logEnvVariables = (req) => { const selNode = req.session.selectedNode; if (selNode && selNode.index) { - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'PORT: ' + this.port }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'HOST: ' + this.host }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'SSO: ' + this.rtl_sso }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DEFAULT NODE INDEX: ' + selNode.index }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'INDEX: ' + selNode.index }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN NODE: ' + selNode.ln_node }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN IMPLEMENTATION: ' + selNode.ln_implementation }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'FIAT CONVERSION: ' + selNode.fiat_conversion }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'CURRENCY UNIT: ' + selNode.currency_unit }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN SERVER URL: ' + selNode.ln_server_url }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LOGOUT REDIRECT LINK: ' + this.logout_redirect_link + '\r\n' }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'PORT: ' + this.port + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'HOST: ' + this.host + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'SSO: ' + this.rtl_sso + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'DEFAULT NODE INDEX: ' + selNode.index + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'INDEX: ' + selNode.index + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'LN NODE: ' + selNode.ln_node + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'LN IMPLEMENTATION: ' + selNode.ln_implementation + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'FIAT CONVERSION: ' + selNode.fiat_conversion + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'CURRENCY UNIT: ' + selNode.currency_unit + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'LN SERVER URL: ' + selNode.ln_server_url + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'LOGOUT REDIRECT LINK: ' + this.logout_redirect_link + '\r\n' + }); } }; this.filterData = (dataKey, lnImplementation) => { @@ -484,7 +742,9 @@ export class CommonService { } } const foundDataLine = this.dummy_data_array_from_file.find((dataItem) => dataItem.includes(search_string)); - const dataStr = foundDataLine ? foundDataLine.substring((foundDataLine.indexOf(search_string)) + search_string.length) : '{}'; + const dataStr = foundDataLine + ? foundDataLine.substring(foundDataLine.indexOf(search_string) + search_string.length) + : '{}'; return JSON.parse(dataStr); }; } diff --git a/backend/utils/config.js b/backend/utils/config.js index d06d40f9d..55e71d51a 100644 --- a/backend/utils/config.js +++ b/backend/utils/config.js @@ -86,9 +86,43 @@ export class ConfigService { } return false; }; + this.normalizeSSL = (val) => { + let ssl; + if (typeof val === 'string') { + ssl = val === 'true' ? true : val === 'false' ? false : JSON.parse(val); + } + else if (typeof val === 'undefined') { + return false; + } + else if (typeof val === 'boolean') { + return val; + } + else { + ssl = val; + } + const { key = undefined, cert = undefined, ca = undefined, altIp = '127.0.0.1', commonName = 'localhost', countryName = 'US', encryptionBits = 2048, stateName = 'New York', localityName = 'New York', organizationName = 'RTL', organizationalUnit = 'RTL', validForYears = 10, requestCert = false, rejectUnauthorized = false } = ssl; + return { + key, + cert, + ca, + altIp, + commonName, + countryName, + encryptionBits, + stateName, + localityName, + organizationName, + organizationalUnit, + validForYears, + requestCert, + rejectUnauthorized + }; + }; this.updateLogByLevel = () => { let updateLogFlag = false; - this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..'); + this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH + ? process.env.RTL_CONFIG_PATH + : join(this.directoryName, '../..'); try { const RTLConfFile = this.common.rtl_conf_file_path + sep + 'RTL-Config.json'; const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8')); @@ -108,7 +142,7 @@ export class ConfigService { } }; this.validateNodeConfig = (config) => { - if ((+process.env.RTL_SSO === 0) || (typeof process.env.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) { + if (+process.env.RTL_SSO === 0 || (typeof process.env.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) { if (process.env.APP_PASSWORD && process.env.APP_PASSWORD.trim() !== '') { this.common.rtl_pass = this.hash.update(process.env.APP_PASSWORD).digest('hex'); this.common.flg_allow_password_update = false; @@ -120,7 +154,8 @@ export class ConfigService { this.common.rtl_pass = this.common.replacePasswordWithHash(this.hash.update(config.multiPass).digest('hex')); } else { - this.errMsg = this.errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json'; + this.errMsg = + this.errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json'; } this.common.rtl_secret2fa = config.secret2fa; } @@ -129,21 +164,39 @@ export class ConfigService { this.errMsg = this.errMsg + '\nRTL Password cannot be set with SSO. Please set SSO as 0 or remove password.'; } } - this.common.port = (process.env.PORT) ? this.normalizePort(process.env.PORT) : (config.port) ? this.normalizePort(config.port) : 3000; - this.common.host = (process.env.HOST) ? process.env.HOST : (config.host) ? config.host : null; + this.common.port = process.env.PORT + ? this.normalizePort(process.env.PORT) + : config.port + ? this.normalizePort(config.port) + : 3000; + this.common.ssl = process.env.SSL + ? this.normalizeSSL(process.env.SSL) + : config.ssl + ? this.normalizeSSL(config.ssl) + : false; + this.common.host = process.env.HOST ? process.env.HOST : config.host ? config.host : null; if (config.nodes && config.nodes.length > 0) { config.nodes.forEach((node, idx) => { this.common.nodes[idx] = {}; this.common.nodes[idx].index = node.index; this.common.nodes[idx].ln_node = node.lnNode; - this.common.nodes[idx].ln_implementation = (process.env.LN_IMPLEMENTATION) ? process.env.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND'; + this.common.nodes[idx].ln_implementation = process.env.LN_IMPLEMENTATION + ? process.env.LN_IMPLEMENTATION + : node.lnImplementation + ? node.lnImplementation + : 'LND'; if (this.common.nodes[idx].ln_implementation === 'CLT') { this.common.nodes[idx].ln_implementation = 'CLN'; } - if (this.common.nodes[idx].ln_implementation !== 'ECL' && process.env.MACAROON_PATH && process.env.MACAROON_PATH.trim() !== '') { + if (this.common.nodes[idx].ln_implementation !== 'ECL' && + process.env.MACAROON_PATH && + process.env.MACAROON_PATH.trim() !== '') { this.common.nodes[idx].macaroon_path = process.env.MACAROON_PATH; } - else if (this.common.nodes[idx].ln_implementation !== 'ECL' && node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') { + else if (this.common.nodes[idx].ln_implementation !== 'ECL' && + node.Authentication && + node.Authentication.macaroonPath && + node.Authentication.macaroonPath.trim() !== '') { this.common.nodes[idx].macaroon_path = node.Authentication.macaroonPath; } else if (this.common.nodes[idx].ln_implementation !== 'ECL') { @@ -169,14 +222,18 @@ export class ConfigService { else { this.common.nodes[idx].config_path = ''; } - if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '' && this.common.nodes[idx].config_path !== '') { + if (this.common.nodes[idx].ln_implementation === 'ECL' && + this.common.nodes[idx].ln_api_password === '' && + this.common.nodes[idx].config_path !== '') { try { const exists = fs.existsSync(this.common.nodes[idx].config_path); if (exists) { try { const configFile = fs.readFileSync(this.common.nodes[idx].config_path, 'utf-8'); const iniParsed = ini.parse(configFile); - this.common.nodes[idx].ln_api_password = iniParsed['eclair.api.password'] ? iniParsed['eclair.api.password'] : parseHocon(configFile).eclair.api.password; + this.common.nodes[idx].ln_api_password = iniParsed['eclair.api.password'] + ? iniParsed['eclair.api.password'] + : parseHocon(configFile).eclair.api.password; } catch (err) { this.errMsg = this.errMsg + '\nSomething went wrong while reading config file: \n' + err; @@ -191,22 +248,35 @@ export class ConfigService { } } if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '') { - this.errMsg = this.errMsg + '\nPlease set config path Or api password for node index ' + node.index + ' in RTL-Config.json! It is mandatory for Eclair authentication!'; + this.errMsg = + this.errMsg + + '\nPlease set config path Or api password for node index ' + + node.index + + ' in RTL-Config.json! It is mandatory for Eclair authentication!'; } if (process.env.LN_SERVER_URL && process.env.LN_SERVER_URL.trim() !== '') { - this.common.nodes[idx].ln_server_url = process.env.LN_SERVER_URL.endsWith('/v1') ? process.env.LN_SERVER_URL.slice(0, -3) : process.env.LN_SERVER_URL; + this.common.nodes[idx].ln_server_url = process.env.LN_SERVER_URL.endsWith('/v1') + ? process.env.LN_SERVER_URL.slice(0, -3) + : process.env.LN_SERVER_URL; } else if (process.env.LND_SERVER_URL && process.env.LND_SERVER_URL.trim() !== '') { - this.common.nodes[idx].ln_server_url = process.env.LND_SERVER_URL.endsWith('/v1') ? process.env.LND_SERVER_URL.slice(0, -3) : process.env.LND_SERVER_URL; + this.common.nodes[idx].ln_server_url = process.env.LND_SERVER_URL.endsWith('/v1') + ? process.env.LND_SERVER_URL.slice(0, -3) + : process.env.LND_SERVER_URL; } else if (node.Settings.lnServerUrl && node.Settings.lnServerUrl.trim() !== '') { - this.common.nodes[idx].ln_server_url = node.Settings.lnServerUrl.endsWith('/v1') ? node.Settings.lnServerUrl.slice(0, -3) : node.Settings.lnServerUrl; + this.common.nodes[idx].ln_server_url = node.Settings.lnServerUrl.endsWith('/v1') + ? node.Settings.lnServerUrl.slice(0, -3) + : node.Settings.lnServerUrl; } else if (node.Settings.lndServerUrl && node.Settings.lndServerUrl.trim() !== '') { - this.common.nodes[idx].ln_server_url = node.Settings.lndServerUrl.endsWith('/v1') ? node.Settings.lndServerUrl.slice(0, -3) : node.Settings.lndServerUrl; + this.common.nodes[idx].ln_server_url = node.Settings.lndServerUrl.endsWith('/v1') + ? node.Settings.lndServerUrl.slice(0, -3) + : node.Settings.lndServerUrl; } else { - this.errMsg = this.errMsg + '\nPlease set LN Server URL for node index ' + node.index + ' in RTL-Config.json!'; + this.errMsg = + this.errMsg + '\nPlease set LN Server URL for node index ' + node.index + ' in RTL-Config.json!'; } this.common.nodes[idx].user_persona = node.Settings.userPersona ? node.Settings.userPersona : 'MERCHANT'; this.common.nodes[idx].theme_mode = node.Settings.themeMode ? node.Settings.themeMode : 'DAY'; @@ -217,32 +287,56 @@ export class ConfigService { this.common.nodes[idx].currency_unit = node.Settings.currencyUnit ? node.Settings.currencyUnit : 'USD'; } if (process.env.SWAP_SERVER_URL && process.env.SWAP_SERVER_URL.trim() !== '') { - this.common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') ? process.env.SWAP_SERVER_URL.slice(0, -3) : process.env.SWAP_SERVER_URL; + this.common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') + ? process.env.SWAP_SERVER_URL.slice(0, -3) + : process.env.SWAP_SERVER_URL; this.common.nodes[idx].swap_macaroon_path = process.env.SWAP_MACAROON_PATH; } else if (node.Settings.swapServerUrl && node.Settings.swapServerUrl.trim() !== '') { - this.common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') ? node.Settings.swapServerUrl.slice(0, -3) : node.Settings.swapServerUrl; - this.common.nodes[idx].swap_macaroon_path = node.Authentication.swapMacaroonPath ? node.Authentication.swapMacaroonPath : ''; + this.common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') + ? node.Settings.swapServerUrl.slice(0, -3) + : node.Settings.swapServerUrl; + this.common.nodes[idx].swap_macaroon_path = node.Authentication.swapMacaroonPath + ? node.Authentication.swapMacaroonPath + : ''; } else { this.common.nodes[idx].swap_server_url = ''; this.common.nodes[idx].swap_macaroon_path = ''; } if (process.env.BOLTZ_SERVER_URL && process.env.BOLTZ_SERVER_URL.trim() !== '') { - this.common.nodes[idx].boltz_server_url = process.env.BOLTZ_SERVER_URL.endsWith('/v1') ? process.env.BOLTZ_SERVER_URL.slice(0, -3) : process.env.BOLTZ_SERVER_URL; + this.common.nodes[idx].boltz_server_url = process.env.BOLTZ_SERVER_URL.endsWith('/v1') + ? process.env.BOLTZ_SERVER_URL.slice(0, -3) + : process.env.BOLTZ_SERVER_URL; this.common.nodes[idx].boltz_macaroon_path = process.env.BOLTZ_MACAROON_PATH; } else if (node.Settings.boltzServerUrl && node.Settings.boltzServerUrl.trim() !== '') { - this.common.nodes[idx].boltz_server_url = node.Settings.boltzServerUrl.endsWith('/v1') ? node.Settings.boltzServerUrl.slice(0, -3) : node.Settings.boltzServerUrl; - this.common.nodes[idx].boltz_macaroon_path = node.Authentication.boltzMacaroonPath ? node.Authentication.boltzMacaroonPath : ''; + this.common.nodes[idx].boltz_server_url = node.Settings.boltzServerUrl.endsWith('/v1') + ? node.Settings.boltzServerUrl.slice(0, -3) + : node.Settings.boltzServerUrl; + this.common.nodes[idx].boltz_macaroon_path = node.Authentication.boltzMacaroonPath + ? node.Authentication.boltzMacaroonPath + : ''; } else { this.common.nodes[idx].boltz_server_url = ''; this.common.nodes[idx].boltz_macaroon_path = ''; } - this.common.nodes[idx].enable_offers = process.env.ENABLE_OFFERS ? process.env.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false; - this.common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : ''; - this.common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH ? process.env.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index; + this.common.nodes[idx].enable_offers = process.env.ENABLE_OFFERS + ? process.env.ENABLE_OFFERS + : node.Settings.enableOffers + ? node.Settings.enableOffers + : false; + this.common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH + ? process.env.BITCOIND_CONFIG_PATH + : node.Settings.bitcoindConfigPath + ? node.Settings.bitcoindConfigPath + : ''; + this.common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH + ? process.env.CHANNEL_BACKUP_PATH + : node.Settings.channelBackupPath + ? node.Settings.channelBackupPath + : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index; try { this.common.createDirectory(this.common.nodes[idx].channel_backup_path); const exists = fs.existsSync(this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak'); @@ -257,15 +351,30 @@ export class ConfigService { } } catch (err) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating backup file: \n' + err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while creating backup file: \n' + err + }); } } } catch (err) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating the backup directory: \n' + err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while creating the backup directory: \n' + err + }); } this.common.nodes[idx].log_file = this.common.rtl_conf_file_path + '/logs/RTL-Node-' + node.index + '.log'; - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'Config', msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'Config', + msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) + }); const log_file = this.common.nodes[idx].log_file; if (fs.existsSync(log_file)) { fs.writeFile(log_file, '', () => { }); @@ -278,7 +387,12 @@ export class ConfigService { createStream.end(); } catch (err) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating log file ' + log_file + ': \n' + err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while creating log file ' + log_file + ': \n' + err + }); } } }); @@ -329,7 +443,9 @@ export class ConfigService { }; this.setServerConfiguration = () => { try { - this.common.rtl_conf_file_path = (process.env.RTL_CONFIG_PATH) ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..'); + this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH + ? process.env.RTL_CONFIG_PATH + : join(this.directoryName, '../..'); const confFileFullPath = this.common.rtl_conf_file_path + sep + 'RTL-Config.json'; if (!fs.existsSync(confFileFullPath)) { fs.writeFileSync(confFileFullPath, JSON.stringify(this.setDefaultConfig())); @@ -340,7 +456,12 @@ export class ConfigService { this.setSelectedNode(config); } catch (err) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while configuring the node server: \n' + err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while configuring the node server: \n' + err + }); throw new Error(err); } }; diff --git a/backend/utils/cors.js b/backend/utils/cors.js index 4137b13d4..316881d1d 100644 --- a/backend/utils/cors.js +++ b/backend/utils/cors.js @@ -6,7 +6,12 @@ class CORS { this.common = Common; } mount(app) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CORS', msg: 'Setting up CORS..' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'CORS', + msg: 'Setting up CORS..' + }); app.use((req, res, next) => { res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, filePath'); @@ -20,6 +25,5 @@ class CORS { this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CORS', msg: 'CORS Set' }); return app; } - ; } -export default new CORS; +export default new CORS(); diff --git a/backend/utils/csrf.js b/backend/utils/csrf.js index bca5ab20d..a9e4c790c 100644 --- a/backend/utils/csrf.js +++ b/backend/utils/csrf.js @@ -3,18 +3,22 @@ import { Logger } from './logger.js'; import { Common } from './common.js'; class CSRF { constructor() { - this.csrfProtection = csurf({ cookie: true }); + this.csrfProtection = csurf({ cookie: true, secure: Common.ssl ? true : false }); this.logger = Logger; this.common = Common; } mount(app) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CSRF', msg: 'Setting up CSRF..' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'CSRF', + msg: 'Setting up CSRF..' + }); if (process.env.NODE_ENV !== 'development') { app.use((req, res, next) => this.csrfProtection(req, res, next)); } this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CSRF', msg: 'CSRF Set' }); return app; } - ; } -export default new CSRF; +export default new CSRF(); diff --git a/backend/utils/ssl.js b/backend/utils/ssl.js new file mode 100644 index 000000000..8fd50370d --- /dev/null +++ b/backend/utils/ssl.js @@ -0,0 +1,91 @@ +import * as fs from 'fs'; +import CertificateFactory from './certificateFactory.js'; +import { Logger } from './logger.js'; +const { assign } = Object; +class SSL { + constructor(options = true) { + this.logger = Logger; + this.key = undefined; + this.cert = undefined; + this.ca = undefined; + this.requestCert = false; + this.rejectUnauthorized = false; + const factoryOptions = {}; + const filePaths = {}; + if (typeof options === 'object') { + const { altIp, commonName, countryName, encryptionBits, stateName, localityName, organizationName, organizationalUnit, validForYears, requestCert, rejectUnauthorized } = options; + assign(factoryOptions, { + altIp, + commonName, + countryName, + encryptionBits, + stateName, + localityName, + organizationName, + organizationalUnit, + validForYears + }); + filePaths.key = options.key; + filePaths.cert = options.cert; + filePaths.ca = options.ca; + if (typeof requestCert === 'boolean') { + this.requestCert = requestCert; + } + if (typeof rejectUnauthorized === 'boolean') { + this.rejectUnauthorized = rejectUnauthorized; + } + } + try { + if (filePaths.key) { + this.key = fs.readFileSync(filePaths.key); + } + if (filePaths.cert) { + this.cert = fs.readFileSync(filePaths.cert); + } + if (filePaths.ca) { + this.ca = fs.readFileSync(filePaths.ca); + } + } + catch (e) { + this.logger.log({ + level: 'ERROR', + fileName: 'SSL', + msg: `Error reading certificate files - ${e.toString()}` + }); + throw e; + } + if (!this.key && !this.cert && !this.ca) { + try { + const selfSignedCertificateFactory = new CertificateFactory(factoryOptions); + assign(this, selfSignedCertificateFactory.getStaticBundle('buffer')); + } + catch (e) { + this.logger.log({ + level: 'ERROR', + fileName: 'SSL', + msg: `Error generating self-signed certificates - ${e.toString()}` + }); + throw e; + } + } + } + //--------------------------------------------------------------------------------------- + //Method: toObject + // + //Input: none + //Output: ssl server configuration object + // + //This method returns https server config options generated by this class + //--------------------------------------------------------------------------------------- + toObject() { + const { key, cert, ca, rejectUnauthorized, requestCert } = this; + return { + key, + cert, + ca, + rejectUnauthorized, + requestCert + }; + } +} +export default SSL; diff --git a/package-lock.json b/package-lock.json index 692f62d00..8e9e2e3dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "material-design-icons": "^3.0.1", "ng-qrcode": "^6.0.0", "ngx-perfect-scrollbar": "^10.0.1", + "node-forge": "^1.3.1", "otplib": "^12.0.1", "pdfmake": "^0.2.4", "request": "^2.88.2", @@ -78,6 +79,7 @@ "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "nodemon": "~2.0.6", + "prettier": "^2.6.2", "protractor": "~7.0.0", "stream-browserify": "^3.0.0", "ts-node": "~8.3.0" @@ -11262,7 +11264,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, "engines": { "node": ">= 6.13.0" } @@ -12807,6 +12808,21 @@ "node": ">=4" } }, + "node_modules/prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -25109,8 +25125,7 @@ "node-forge": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" }, "node-gyp": { "version": "8.4.1", @@ -26236,6 +26251,12 @@ "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=", "dev": true }, + "prettier": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz", + "integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==", + "dev": true + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", diff --git a/package.json b/package.json index b0018a6e2..e0de7facf 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "buildfrontend": "ng build --configuration production", "buildbackend": "tsc --project tsconfig.json", "watchbackend": "tsc --project tsconfig.json --watch", + "formatserver": "node ./node_modules/.bin/prettier --single-quote --tab-width 2 --print-width 120 --trailing-comma none --write '{server/**/*.ts,rtl.js}'", "server": "set NODE_ENV=development&&nodemon ./rtl.js", "serverUbuntu": "NODE_ENV=development nodemon ./rtl.js", "testdev": "ng test --watch=true --code-coverage", @@ -52,6 +53,7 @@ "material-design-icons": "^3.0.1", "ng-qrcode": "^6.0.0", "ngx-perfect-scrollbar": "^10.0.1", + "node-forge": "^1.3.1", "otplib": "^12.0.1", "pdfmake": "^0.2.4", "request": "^2.88.2", @@ -89,6 +91,7 @@ "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "^1.5.0", "nodemon": "~2.0.6", + "prettier": "^2.6.2", "protractor": "~7.0.0", "stream-browserify": "^3.0.0", "ts-node": "~8.3.0" diff --git a/rtl.js b/rtl.js index 6fcdecacc..99faeb969 100644 --- a/rtl.js +++ b/rtl.js @@ -1,23 +1,34 @@ import http from 'http'; +import https from 'https'; import App from './backend/utils/app.js'; import { Logger } from './backend/utils/logger.js'; import { Common } from './backend/utils/common.js'; import { WSServer } from './backend/utils/webSocketServer.js'; +import SSL from './backend/utils/ssl.js'; const logger = Logger; const common = Common; const wsServer = WSServer; const app = new App(); +const appInstance = app.getApp(); +//Change 'global' to '127.0.0.1' for breaking change version release +const hostUndefined = common.host ? false : true; +const resolvedHost = hostUndefined ? 'global' : common.host; //'127.0.0.1' <-- this will become new default someday. swap with 'global' for breaking change +const resolvedPort = common.port; +const tcpModule = common.ssl ? https : http; +const fullUrl = `${common.ssl ? 'https' : 'http'}://${(hostUndefined || resolvedHost === 'global') ? 'localhost' : resolvedHost}:${resolvedPort}`; const onError = (error) => { - if (error.syscall !== 'listen') { throw error; } + if (error.syscall !== 'listen') { + throw error; + } switch (error.code) { case 'EACCES': - logger.log({ level: 'ERROR', fileName: 'RTL', msg: 'http://' + (common.host ? common.host : 'localhost') + ':' + common.port + ' requires elevated privileges' }); + logger.log({ level: 'ERROR', fileName: 'RTL', msg: `${fullUrl} requires elevated privileges` }); process.exit(1); break; case 'EADDRINUSE': - logger.log({ level: 'ERROR', fileName: 'RTL', msg: 'http://' + (common.host ? common.host : 'localhost') + ':' + common.port + ' is already in use' }); + logger.log({ level: 'ERROR', fileName: 'RTL', msg: `${fullUrl} is already in use` }); process.exit(1); break; case 'ECONNREFUSED': @@ -35,18 +46,28 @@ const onError = (error) => { }; const onListening = () => { - logger.log({ level: 'INFO', fileName: 'RTL', msg: 'Server is up and running, please open the UI at http://' + (common.host ? common.host : 'localhost') + ':' + common.port }); + logger.log({ level: 'INFO', fileName: 'RTL', msg: `Server is up and running, please open the UI at ${fullUrl}` }); + //Remove the following IF block after sufficient version releases for users to update configs + if (hostUndefined) { + logger.log({ + level: 'WARN', + fileName: 'RTL', + msg: `DEPRECATION! Server defaulted to listening on external facing port ${resolvedPort} because "host" option is undefined. This feature will change soon. Please manually specify "host": "global" in RTL-Config.json if you want to continue exposing RTL to your entire LAN or gateway in future versions. The default value will change to '127.0.0.1' in the future so that RTL is only accessible on the RTL host machine by default` + }); + } }; -let server = http.createServer(app.getApp()); +let serverArgs = []; +if (common.ssl) { + let sslConfig = new SSL(common.ssl); + serverArgs.push(sslConfig.toObject()); +} +serverArgs.push(appInstance); +let server = tcpModule.createServer(...serverArgs); server.on('error', onError); server.on('listening', onListening); wsServer.mount(server); -if (common.host) { - server.listen(common.port, common.host); -} else { - server.listen(common.port); -} +server.listen(resolvedPort, resolvedHost === 'global' ? undefined : resolvedHost); diff --git a/server/utils/app.ts b/server/utils/app.ts index f7c42e26f..a4837c9d8 100644 --- a/server/utils/app.ts +++ b/server/utils/app.ts @@ -6,6 +6,7 @@ import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import CORS from './cors.js'; import CSRF from './csrf.js'; +import CertificateIdentity from './certificateIdentity.js'; import sharedRoutes from '../routes/shared/index.js'; import lndRoutes from '../routes/lnd/index.js'; @@ -21,7 +22,6 @@ import { LNDWSClient, LNDWebSocketClient } from '../controllers/lnd/webSocketCli const ONE_DAY = 1000 * 60 * 60 * 24; export class ExpressApplication { - public app = express(); public logger: LoggerService = Logger; public common: CommonService = Common; @@ -32,14 +32,29 @@ export class ExpressApplication { public directoryName = dirname(fileURLToPath(import.meta.url)); constructor() { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Starting Express Application..' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'App', + msg: 'Starting Express Application..' + }); + this.loadConfiguration(); this.app.set('trust proxy', true); - this.app.use(sessions({ secret: this.common.secret_key, saveUninitialized: true, cookie: { secure: false, maxAge: ONE_DAY }, resave: false })); + if (this.common.ssl && this.common.ssl.requestCert) { + this.app.use(CertificateIdentity(this.common.ssl.rejectUnauthorized)); + } + this.app.use( + sessions({ + secret: this.common.secret_key, + saveUninitialized: true, + cookie: { secure: this.common.ssl ? true : false, maxAge: ONE_DAY }, + resave: false + }) + ); this.app.use(cookieParser(this.common.secret_key)); this.app.use(bodyParser.json({ limit: '25mb' })); this.app.use(bodyParser.urlencoded({ extended: false, limit: '25mb' })); - this.loadConfiguration(); this.setCORS(); this.setCSRF(); this.setApplicationRoutes(); @@ -49,52 +64,91 @@ export class ExpressApplication { public loadConfiguration = () => { this.config.setServerConfiguration(); - } + }; - public setCORS = () => { CORS.mount(this.app); } + public setCORS = () => { + CORS.mount(this.app); + }; - public setCSRF = () => { CSRF.mount(this.app); } + public setCSRF = () => { + CSRF.mount(this.app); + }; public setApplicationRoutes = () => { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Setting up Application Routes..' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'App', + msg: 'Setting up Application Routes..' + }); this.app.use(this.common.baseHref + '/api', sharedRoutes); this.app.use(this.common.baseHref + '/api/lnd', lndRoutes); this.app.use(this.common.baseHref + '/api/cln', clnRoutes); this.app.use(this.common.baseHref + '/api/ecl', eclRoutes); this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend'))); this.app.use((req: any, res, next) => { - res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); + res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : '', { secure: this.common.ssl ? true : false }); res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html')); }); this.app.use((err, req, res, next) => this.handleApplicationErrors(err, res)); - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' }); - } + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'App', + msg: 'Application Routes Set' + }); + }; public handleApplicationErrors = (err, res) => { switch (err.code) { case 'EACCES': - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server requires elevated privileges' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'Server requires elevated privileges' + }); res.status(406).send('Server requires elevated privileges.'); break; case 'EADDRINUSE': - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is already in use' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'Server is already in use' + }); res.status(409).send('Server is already in use.'); break; case 'ECONNREFUSED': - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is down/locked' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'Server is down/locked' + }); res.status(401).send('Server is down/locked.'); break; case 'EBADCSRFTOKEN': - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Invalid CSRF token. Form tempered.' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'Invalid CSRF token. Form tempered.' + }); res.status(403).send('Invalid CSRF token, form tempered.'); break; default: - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'DEFUALT ERROR', error: err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'App', + msg: 'DEFUALT ERROR', + error: err + }); res.status(400).send(JSON.stringify(err)); break; } }; - } export default ExpressApplication; diff --git a/server/utils/authCheck.ts b/server/utils/authCheck.ts index bef8a631f..7e015f2ec 100644 --- a/server/utils/authCheck.ts +++ b/server/utils/authCheck.ts @@ -5,7 +5,7 @@ import { Logger, LoggerService } from './logger.js'; const common: CommonService = Common; const logger: LoggerService = Logger; -const csurfProtection = csurf({ cookie: true }); +const csurfProtection = csurf({ cookie: true, secure: Common.ssl ? true : false }); export const isAuthenticated = (req, res, next) => { try { diff --git a/server/utils/certificateFactory.ts b/server/utils/certificateFactory.ts new file mode 100644 index 000000000..391f7a2a7 --- /dev/null +++ b/server/utils/certificateFactory.ts @@ -0,0 +1,321 @@ +import forge from 'node-forge'; +forge.options.usePureJavaScript = true; + +//--------------------------------------------------------------------------------------- +//Private Module Variables +//--------------------------------------------------------------------------------------- +const { assign, freeze, keys } = Object; +const max32BitInt = 2147483647; +//--------------------------------------------------------------------------------------- + +class CertificateFactory { + private forge: any = forge; + private certBundle: any = null; + private encryptionBits: Number = 2048; + private certType: String = 'component'; + private validForYears: Number = 10; + private commonName: String = 'localhost'; + private organizationalUnit: any = 'RTL'; + private organizationName: String = 'RTL'; + private countryName: String = 'US'; + private stateName: String = 'New York'; + private localityName: String = 'New York'; + private altName: String = 'https://localhost'; + private altIp: String = '127.0.0.1'; + + //--------------------------------------------------------------------------------------- + //Constructor + // + //Input: obj (options object) + //Output: certificate factory instance + //--------------------------------------------------------------------------------------- + constructor(opts: any = {}) { + const { + altIp = '127.0.0.1', + certType = 'component', + commonName = 'localhost', + countryName = 'US', + encryptionBits = '2048', + stateName = 'New York', + localityName = 'New York', + organizationName = 'RTL', + organizationalUnit = 'RTL', + validForYears = '10' + } = opts; + assign(this, { + certBundle: null, + altName: `${commonName}`, + altIp, + certType, + commonName, + countryName, + encryptionBits: parseInt(encryptionBits, 10), + stateName, + localityName, + organizationName, + organizationalUnit, + validForYears: parseInt(validForYears, 10) + }); + } + + //--------------------------------------------------------------------------------------- + //Method: getStaticBundle + // + //Input: type + //Output: bundle + // + //This method will generate a constant static cert bundle that can be "shared" between + //many classes that may require a common certificate set. It has a single input argument + //that allows external classes to specify which format they would like their cert bundle + //to be in. (string or buffer) + //--------------------------------------------------------------------------------------- + public getStaticBundle(type = 'string', password = null) { + if (!this.certBundle) { + this.certBundle = freeze(this.generateRandomCerts()); + } + + return this._convertBundle(this.certBundle, type, password); + } + + //--------------------------------------------------------------------------------------- + //Method: _convertBundle + // + //Input: bundle , type + //Output: bundle + // + //This method will converts all key value pairs of a certificate bundle to the desired + //type and returns a new bundle. This also performs a defacto copy of the bundle to + //prevent outside callers from tampering with each other's returned bundle instance, or + //the internal cached bundle referenced by getStaticBundle. + //--------------------------------------------------------------------------------------- + private _convertBundle(bundle, type, password) { + const newBundle = {}; + const bundleKeys = keys(bundle); + const converter = type === 'string' ? this._valueToString : this._valueToBuffer; + + if (type === 'p12') { + return this._bundleToP12(bundle, password); + } + + bundleKeys.forEach((k) => { + if (bundle[k]) { + newBundle[k] = converter(bundle[k]); + } else { + newBundle[k] = bundle[k]; + } + }); + + return newBundle; + } + + //--------------------------------------------------------------------------------------- + //Method: _valueToString + // + //Input: v + //Output: v + // + //This method converts its input value to a string and returns it. + //--------------------------------------------------------------------------------------- + private _valueToString(v) { + return `${v}`; + } + + //--------------------------------------------------------------------------------------- + //Method: _valueToBuffer + // + //Input: v + //Output: v + // + //This method converts its input value to a buffer and returns it. + //--------------------------------------------------------------------------------------- + private _valueToBuffer(v) { + return Buffer.from(v); + } + + //--------------------------------------------------------------------------------------- + //Method: _bundleToP12 + // + //Input: bundle + //Output: p12Base64 + // + //This method converts a certificate bundle to a .p12 file that can be used in a browser. + //--------------------------------------------------------------------------------------- + private _bundleToP12(bundle, password) { + const { + forge: { asn1, pki, pkcs12, util } + } = this; + const pemCertificate = pki.certificateFromPem(bundle.cert); + const pemKey = pki.privateKeyFromPem(bundle.key); + const p12Asn1 = pkcs12.toPkcs12Asn1(pemKey, pemCertificate, password, { + algorithm: '3des' + }); + const p12Bytes = asn1.toDer(p12Asn1).getBytes(); + const p12Base64 = util.encode64(p12Bytes); + + return p12Base64; + } + + //--------------------------------------------------------------------------------------- + //Method: generateRandomCerts + // + //Input: none + //Output: bundle + // + //This method utilizes node-forge to create a key/cert/ca bundle entirely in javascript. + //It uses the factory properties set in the factory constructor to configure the new + //key/cert/ca bundle, and then returns the newly generated bundle. Directly calling this + //method will result in new bundles with different private keys, but the certificate + //x509 attributes will match across all bundles generated with the same factory instance. + //--------------------------------------------------------------------------------------- + public generateRandomCerts() { + const { + forge: { pki } + } = this; + //generate a keypair and create an X.509v3 certificate + const keys = pki.rsa.generateKeyPair(this.encryptionBits); + const cert = pki.createCertificate(); + cert.publicKey = keys.publicKey; + cert.serialNumber = Math.round(Math.random() * max32BitInt).toString(16); + cert.validity.notBefore = new Date(); + cert.validity.notAfter = new Date(); + cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + this.validForYears); + const altNames = []; + let attrs; + if (this.certType === 'person') { + const OUArray = Array.isArray(this.organizationalUnit) + ? this.organizationalUnit.map((v) => { + return { + shortName: 'OU', + value: v + }; + }) + : [ + { + shortName: 'OU', + value: 'RTL' + }, + { + shortName: 'OU', + value: 'BTC' + }, + { + shortName: 'OU', + value: 'People' + } + ]; + attrs = [].concat( + [ + { + name: 'countryName', + value: this.countryName + }, + { + name: 'organizationName', + value: this.organizationName + } + ], + OUArray, + [ + { + name: 'commonName', + value: this.commonName + } + ] + ); + } else { + attrs = [ + { + name: 'commonName', + value: this.commonName + }, + { + name: 'countryName', + value: this.countryName + }, + { + shortName: 'ST', + value: this.stateName + }, + { + name: 'localityName', + value: this.localityName + }, + { + name: 'organizationName', + value: this.organizationName + }, + { + shortName: 'OU', + value: this.organizationalUnit + } + ]; + if (this.altName) { + altNames.push({ + type: 6, // URI + value: this.altName + }); + } + if (this.altIp) { + altNames.push({ + type: 7, // IP + ip: this.altIp + }); + } + } + cert.setSubject(attrs); + cert.setIssuer(attrs); + cert.setExtensions([ + { + name: 'basicConstraints', + cA: true + }, + { + name: 'keyUsage', + keyCertSign: true, + digitalSignature: true, + nonRepudiation: true, + keyEncipherment: true, + dataEncipherment: true + }, + { + name: 'extKeyUsage', + serverAuth: true, + clientAuth: true, + codeSigning: true, + emailProtection: true, + timeStamping: true + }, + { + name: 'nsCertType', + client: true, + server: true, + email: true, + objsign: true, + sslCA: true, + emailCA: true, + objCA: true + }, + { + name: 'subjectAltName', + altNames: altNames + }, + { + name: 'subjectKeyIdentifier' + } + ]); + cert.sign(keys.privateKey); + + // convert a Forge certificate to PEM + // for self generated certs, ca will be undefined + const pem = pki.certificateToPem(cert); + const certBundle = { + key: pki.privateKeyToPem(keys.privateKey), + cert: pem, + ca: undefined + }; + + return certBundle; + } +} + +export default CertificateFactory; diff --git a/server/utils/certificateIdentity.ts b/server/utils/certificateIdentity.ts new file mode 100644 index 000000000..12110b582 --- /dev/null +++ b/server/utils/certificateIdentity.ts @@ -0,0 +1,42 @@ +function subjectToString(subject) { + const aggr = []; + Object.keys(subject).forEach((k) => { + if (typeof subject[k] === 'string') { + aggr.push(`${k}=${subject[k]}`); + } else if (Array.isArray(subject[k]) && subject[k].length > 0) { + const temp = []; + subject[k].forEach((v) => { + temp.push(`${k}=${v}`); + }); + aggr.push(temp.reverse().join(',')); + } + }); + return aggr.reverse().join(','); +} + +export default (rejectUnauthorized = false) => { + return function (req, res, next) { + const { connection, protocol } = req; + + if (protocol === 'https' && (connection.authorized || rejectUnauthorized === false)) { + try { + const { subject } = connection.getPeerCertificate(); + const CN = subject.CN; + const DN = subjectToString(subject); + req.identity = { + DN, + CN + }; + return next(); + } catch (e) { + //if this server requsts certs, but there is an error getting them, DO NOT PASS GO! + return res.status(500).json({ + message: e.toString() + }); + } + } + return res.status(403).json({ + message: 'forbidden' + }); + }; +}; diff --git a/server/utils/common.ts b/server/utils/common.ts index d11d11b57..8a850adc7 100644 --- a/server/utils/common.ts +++ b/server/utils/common.ts @@ -7,13 +7,13 @@ import { Logger, LoggerService } from './logger.js'; import { CommonSelectedNode } from '../models/config.model.js'; export class CommonService { - public logger: LoggerService = Logger; public nodes: CommonSelectedNode[] = []; public initSelectedNode: CommonSelectedNode = null; public rtl_conf_file_path = ''; public port = 3000; public host = null; + public ssl: any = false; public rtl_pass = ''; public flg_allow_password_update = true; public rtl_secret2fa = ''; @@ -26,9 +26,22 @@ export class CommonService { public read_dummy_data = false; public baseHref = '/rtl'; private dummy_data_array_from_file = []; - private MONTHS = [{ name: 'JAN', days: 31 }, { name: 'FEB', days: 28 }, { name: 'MAR', days: 31 }, { name: 'APR', days: 30 }, { name: 'MAY', days: 31 }, { name: 'JUN', days: 30 }, { name: 'JUL', days: 31 }, { name: 'AUG', days: 31 }, { name: 'SEP', days: 30 }, { name: 'OCT', days: 31 }, { name: 'NOV', days: 30 }, { name: 'DEC', days: 31 }]; - - constructor() { } + private MONTHS = [ + { name: 'JAN', days: 31 }, + { name: 'FEB', days: 28 }, + { name: 'MAR', days: 31 }, + { name: 'APR', days: 30 }, + { name: 'MAY', days: 31 }, + { name: 'JUN', days: 30 }, + { name: 'JUL', days: 31 }, + { name: 'AUG', days: 31 }, + { name: 'SEP', days: 30 }, + { name: 'OCT', days: 31 }, + { name: 'NOV', days: 30 }, + { name: 'DEC', days: 31 } + ]; + + constructor() {} public getSwapServerOptions = (req) => { const swapOptions = { @@ -39,12 +52,28 @@ export class CommonService { }; if (req.session.selectedNode.swap_macaroon_path) { try { - swapOptions.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.swap_macaroon_path, 'loop.macaroon')).toString('hex') }; + swapOptions.headers = { + 'Grpc-Metadata-macaroon': fs + .readFileSync(join(req.session.selectedNode.swap_macaroon_path, 'loop.macaroon')) + .toString('hex') + }; } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Loop macaroon Error', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Loop macaroon Error', + error: err + }); } } - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Swap Options', data: swapOptions }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Swap Options', + data: swapOptions + }); return swapOptions; }; @@ -57,23 +86,47 @@ export class CommonService { }; if (req.session.selectedNode.boltz_macaroon_path) { try { - boltzOptions.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.boltz_macaroon_path, 'admin.macaroon')).toString('hex') }; + boltzOptions.headers = { + 'Grpc-Metadata-macaroon': fs + .readFileSync(join(req.session.selectedNode.boltz_macaroon_path, 'admin.macaroon')) + .toString('hex') + }; } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Boltz macaroon Error', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Boltz macaroon Error', + error: err + }); } } - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Boltz Options', data: boltzOptions }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Boltz Options', + data: boltzOptions + }); return boltzOptions; }; public getOptions = (req) => { if (req.session.selectedNode && req.session.selectedNode.options) { - req.session.selectedNode.options.method = (req.session.selectedNode.ln_implementation && req.session.selectedNode.ln_implementation.toUpperCase() !== 'ECL') ? 'GET' : 'POST'; + req.session.selectedNode.options.method = + req.session.selectedNode.ln_implementation && req.session.selectedNode.ln_implementation.toUpperCase() !== 'ECL' + ? 'GET' + : 'POST'; delete req.session.selectedNode.options.form; req.session.selectedNode.options.qs = {}; return req.session.selectedNode.options; } - return this.handleError({ statusCode: 401, message: 'Session expired after a day\'s inactivity' }, 'Session Expired', 'Session Expiry Error', this.initSelectedNode); + return this.handleError( + { statusCode: 401, message: "Session expired after a day's inactivity" }, + 'Session Expired', + 'Session Expiry Error', + this.initSelectedNode + ); }; public updateSelectedNodeOptions = (req) => { @@ -90,20 +143,36 @@ export class CommonService { if (req.session.selectedNode && req.session.selectedNode.ln_implementation) { switch (req.session.selectedNode.ln_implementation.toUpperCase()) { case 'CLN': - req.session.selectedNode.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'access.macaroon'))).toString('base64') }; + req.session.selectedNode.options.headers = { + macaroon: Buffer.from( + fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'access.macaroon')) + ).toString('base64') + }; break; case 'ECL': - req.session.selectedNode.options.headers = { authorization: 'Basic ' + Buffer.from(':' + req.session.selectedNode.ln_api_password).toString('base64') }; + req.session.selectedNode.options.headers = { + authorization: 'Basic ' + Buffer.from(':' + req.session.selectedNode.ln_api_password).toString('base64') + }; break; default: - req.session.selectedNode.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'admin.macaroon')).toString('hex') }; + req.session.selectedNode.options.headers = { + 'Grpc-Metadata-macaroon': fs + .readFileSync(join(req.session.selectedNode.macaroon_path, 'admin.macaroon')) + .toString('hex') + }; break; } } if (req.session.selectedNode) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Updated Node Options for ' + req.session.selectedNode.ln_node, data: req.session.selectedNode.options }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Updated Node Options for ' + req.session.selectedNode.ln_node, + data: req.session.selectedNode.options + }); } return { status: 200, message: 'Updated Successfully' }; } catch (err) { @@ -113,13 +182,21 @@ export class CommonService { json: true, form: null }; - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Update Selected Node Options Error', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Update Selected Node Options Error', + error: err + }); return { status: 502, message: err }; } }; public setOptions = (req) => { - if (this.nodes[0].options && this.nodes[0].options.headers) { return; } + if (this.nodes[0].options && this.nodes[0].options.headers) { + return; + } if (this.nodes && this.nodes.length > 0) { this.nodes.forEach((node) => { node.options = { @@ -132,20 +209,32 @@ export class CommonService { if (node.ln_implementation) { switch (node.ln_implementation.toUpperCase()) { case 'CLN': - node.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') }; + node.options.headers = { + macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') + }; break; case 'ECL': - node.options.headers = { authorization: 'Basic ' + Buffer.from(':' + node.ln_api_password).toString('base64') }; + node.options.headers = { + authorization: 'Basic ' + Buffer.from(':' + node.ln_api_password).toString('base64') + }; break; default: - node.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(node.macaroon_path, 'admin.macaroon')).toString('hex') }; + node.options.headers = { + 'Grpc-Metadata-macaroon': fs.readFileSync(join(node.macaroon_path, 'admin.macaroon')).toString('hex') + }; break; } } } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Common Set Options Error', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Common Set Options Error', + error: err + }); node.options = { url: '', rejectUnauthorized: false, @@ -153,7 +242,13 @@ export class CommonService { form: '' }; } - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Set Node Options for ' + node.ln_node, data: node.options }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Set Node Options for ' + node.ln_node, + data: node.options + }); }); this.updateSelectedNodeOptions(req); } @@ -179,26 +274,40 @@ export class CommonService { minutes = +minutes < 10 ? '0' + minutes : minutes; let seconds = myDate.getSeconds().toString(); seconds = +seconds < 10 ? '0' + seconds : seconds; - return days + '/' + this.MONTHS[myDate.getMonth()].name + '/' + myDate.getFullYear() + ' ' + hours + ':' + minutes + ':' + seconds; + return ( + days + + '/' + + this.MONTHS[myDate.getMonth()].name + + '/' + + myDate.getFullYear() + + ' ' + + hours + + ':' + + minutes + + ':' + + seconds + ); }; - public sortAscByKey = (array, key) => array.sort((a, b) => { - const x = +a[key]; - const y = +b[key]; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }); + public sortAscByKey = (array, key) => + array.sort((a, b) => { + const x = +a[key]; + const y = +b[key]; + return x < y ? -1 : x > y ? 1 : 0; + }); - public sortAscByStrKey = (array, key) => array.sort((a, b) => { - const x = a[key] ? a[key].toUpperCase() : ''; - const y = b[key] ? b[key].toUpperCase() : ''; - return ((x < y) ? -1 : ((x > y) ? 1 : 0)); - }); + public sortAscByStrKey = (array, key) => + array.sort((a, b) => { + const x = a[key] ? a[key].toUpperCase() : ''; + const y = b[key] ? b[key].toUpperCase() : ''; + return x < y ? -1 : x > y ? 1 : 0; + }); public sortDescByKey = (array, key) => { const temp = array.sort((a, b) => { const x = +a[key] ? +a[key] : 0; const y = +b[key] ? +b[key] : 0; - return (x > y) ? -1 : ((x < y) ? 1 : 0); + return x > y ? -1 : x < y ? 1 : 0; }); return temp; }; @@ -207,26 +316,36 @@ export class CommonService { const temp = array.sort((a, b) => { const x = a[key] ? a[key].toUpperCase() : ''; const y = b[key] ? b[key].toUpperCase() : ''; - return (x > y) ? -1 : ((x < y) ? 1 : 0); + return x > y ? -1 : x < y ? 1 : 0; }); return temp; }; public newestOnTop = (array, key, value) => { - const newlyAddedRecord = array.splice(array.findIndex((item) => item[key] === value), 1); + const newlyAddedRecord = array.splice( + array.findIndex((item) => item[key] === value), + 1 + ); array.unshift(newlyAddedRecord[0]); return array; }; public handleError = (errRes, fileName, errMsg, selectedNode: CommonSelectedNode) => { const err = JSON.parse(JSON.stringify(errRes)); - if (!selectedNode) { selectedNode = this.initSelectedNode; } + if (!selectedNode) { + selectedNode = this.initSelectedNode; + } switch (selectedNode.ln_implementation) { case 'LND': if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) { delete err.options.headers['Grpc-Metadata-macaroon']; } - if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) { + if ( + err.response && + err.response.request && + err.response.request.headers && + err.response.request.headers['Grpc-Metadata-macaroon'] + ) { delete err.response.request.headers['Grpc-Metadata-macaroon']; } break; @@ -235,7 +354,12 @@ export class CommonService { if (err.options && err.options.headers && err.options.headers.macaroon) { delete err.options.headers.macaroon; } - if (err.response && err.response.request && err.response.request.headers && err.response.request.headers.macaroon) { + if ( + err.response && + err.response.request && + err.response.request.headers && + err.response.request.headers.macaroon + ) { delete err.response.request.headers.macaroon; } break; @@ -244,36 +368,64 @@ export class CommonService { if (err.options && err.options.headers && err.options.headers.authorization) { delete err.options.headers.authorization; } - if (err.response && err.response.request && err.response.request.headers && err.response.request.headers.authorization) { + if ( + err.response && + err.response.request && + err.response.request.headers && + err.response.request.headers.authorization + ) { delete err.response.request.headers.authorization; } break; default: - if (err.options && err.options.headers) { delete err.options.headers; } + if (err.options && err.options.headers) { + delete err.options.headers; + } break; } - this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : (typeof err === 'string') ? err : 'Unknown Error') }); + this.logger.log({ + selectedNode: selectedNode, + level: 'ERROR', + fileName: fileName, + msg: errMsg, + error: typeof err === 'object' ? JSON.stringify(err) : typeof err === 'string' ? err : 'Unknown Error' + }); const newErrorObj = { - statusCode: err.statusCode ? err.statusCode : err.status ? err.status : (err.error && err.error.code && err.error.code === 'ECONNREFUSED') ? 503 : 500, - message: (err.error && err.error.message) ? err.error.message : err.message ? err.message : errMsg, - error: ( - (err.error && err.error.error && err.error.error.error && typeof err.error.error.error === 'string') ? err.error.error.error : - (err.error && err.error.error && typeof err.error.error === 'string') ? err.error.error : - (err.error && err.error.error && err.error.error.message && typeof err.error.error.message === 'string') ? err.error.error.message : - (err.error && err.error.message && typeof err.error.message === 'string') ? err.error.message : - (err.error && typeof err.error === 'string') ? err.error : - (err.message && typeof err.message === 'string') ? err.message : (typeof err === 'string') ? err : 'Unknown Error' - ) + statusCode: err.statusCode + ? err.statusCode + : err.status + ? err.status + : err.error && err.error.code && err.error.code === 'ECONNREFUSED' + ? 503 + : 500, + message: err.error && err.error.message ? err.error.message : err.message ? err.message : errMsg, + error: + err.error && err.error.error && err.error.error.error && typeof err.error.error.error === 'string' + ? err.error.error.error + : err.error && err.error.error && typeof err.error.error === 'string' + ? err.error.error + : err.error && err.error.error && err.error.error.message && typeof err.error.error.message === 'string' + ? err.error.error.message + : err.error && err.error.message && typeof err.error.message === 'string' + ? err.error.message + : err.error && typeof err.error === 'string' + ? err.error + : err.message && typeof err.message === 'string' + ? err.message + : typeof err === 'string' + ? err + : 'Unknown Error' }; return newErrorObj; }; - public getRequestIP = (req) => ((typeof req.headers['x-forwarded-for'] === 'string' && req.headers['x-forwarded-for'].split(',').shift()) || + public getRequestIP = (req) => + (typeof req.headers['x-forwarded-for'] === 'string' && req.headers['x-forwarded-for'].split(',').shift()) || req.ip || req.connection.remoteAddress || req.socket.remoteAddress || - (req.connection.socket ? req.connection.socket.remoteAddress : null)); + (req.connection.socket ? req.connection.socket.remoteAddress : null); public getDummyData = (dataKey, lnImplementation) => { const dummyDataFile = this.rtl_conf_file_path + sep + 'ECLDummyData.log'; @@ -282,9 +434,19 @@ export class CommonService { fs.readFile(dummyDataFile, 'utf8', (err, data) => { if (err) { if (err.code === 'ENOENT') { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Dummy data file does not exist' }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Dummy data file does not exist' + }); } else { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Getting dummy data failed' }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Getting dummy data failed' + }); } } else { this.dummy_data_array_from_file = data.split('\n'); @@ -303,7 +465,12 @@ export class CommonService { try { this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8'); } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading cookie: \n' + err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while reading cookie: \n' + err + }); throw new Error(err); } } else { @@ -313,21 +480,32 @@ export class CommonService { fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex')); this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8'); } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading the cookie: \n' + err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while reading the cookie: \n' + err + }); throw new Error(err); } } - } + }; public refreshCookie = () => { try { fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex')); this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8'); } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while refreshing cookie', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Something went wrong while refreshing cookie', + error: err + }); throw new Error(err); } - } + }; public createDirectory = (directoryName) => { const initDir = isAbsolute(directoryName) ? sep : ''; @@ -340,7 +518,9 @@ export class CommonService { } catch (err) { if (err.code !== 'EEXIST') { if (err.code === 'ENOENT') { - throw new Error(`ENOENT: No such file or directory, mkdir '${directoryName}'. Ensure that the path separator is '${sep}'`); + throw new Error( + `ENOENT: No such file or directory, mkdir '${directoryName}'. Ensure that the path separator is '${sep}'` + ); } else { throw err; } @@ -351,19 +531,32 @@ export class CommonService { }; public replacePasswordWithHash = (multiPassHashed) => { - this.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(dirname(fileURLToPath(import.meta.url)), '../..'); + this.rtl_conf_file_path = process.env.RTL_CONFIG_PATH + ? process.env.RTL_CONFIG_PATH + : join(dirname(fileURLToPath(import.meta.url)), '../..'); try { const RTLConfFile = this.rtl_conf_file_path + sep + 'RTL-Config.json'; const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8')); config.multiPassHashed = multiPassHashed; delete config.multiPass; fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8'); - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Please note that, RTL has encrypted the plaintext password into its corresponding hash' }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Please note that, RTL has encrypted the plaintext password into its corresponding hash' + }); return config.multiPassHashed; } catch (err) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Password hashing failed', error: err }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Password hashing failed', + error: err + }); } - } + }; public getAllNodeAllChannelBackup = (node: CommonSelectedNode) => { const channel_backup_file = node.channel_backup_path + sep + 'channel-all.bak'; @@ -373,94 +566,235 @@ export class CommonService { json: true, headers: { 'Grpc-Metadata-macaroon': fs.readFileSync(node.macaroon_path + '/admin.macaroon').toString('hex') } }; - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Getting Channel Backup for Node ' + node.ln_node + '..' }); - request(options).then((body) => { - fs.writeFile(channel_backup_file, JSON.stringify(body), (err) => { - if (err) { - if (node.ln_node) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for Node ' + node.ln_node, error: err }); - } else { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for File ' + channel_backup_file, error: err }); - } - } else { - if (node.ln_node) { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Successful in Channel Backup for Node ' + node.ln_node, data: body }); + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Getting Channel Backup for Node ' + node.ln_node + '..' + }); + request(options).then( + (body) => { + fs.writeFile(channel_backup_file, JSON.stringify(body), (err) => { + if (err) { + if (node.ln_node) { + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Error in Channel Backup for Node ' + node.ln_node, + error: err + }); + } else { + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Error in Channel Backup for File ' + channel_backup_file, + error: err + }); + } } else { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Successful in Channel Backup for File ' + channel_backup_file, data: body }); + if (node.ln_node) { + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Successful in Channel Backup for Node ' + node.ln_node, + data: body + }); + } else { + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'INFO', + fileName: 'Common', + msg: 'Successful in Channel Backup for File ' + channel_backup_file, + data: body + }); + } } - } - }); - }, (err) => { - this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for Node ' + node.ln_node, error: err }); - fs.writeFile(channel_backup_file, '', () => { }); - }); + }); + }, + (err) => { + this.logger.log({ + selectedNode: this.initSelectedNode, + level: 'ERROR', + fileName: 'Common', + msg: 'Error in Channel Backup for Node ' + node.ln_node, + error: err + }); + fs.writeFile(channel_backup_file, '', () => {}); + } + ); }; public isVersionCompatible = (currentVersion, checkVersion) => { if (currentVersion) { const versionsArr = currentVersion.trim().replace('v', '').split('-')[0].split('.') || []; const checkVersionsArr = checkVersion.split('.'); - return (+versionsArr[0] > +checkVersionsArr[0]) || + return ( + +versionsArr[0] > +checkVersionsArr[0] || (+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) || - (+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] === +checkVersionsArr[1] && +versionsArr[2] >= +checkVersionsArr[2]); + (+versionsArr[0] === +checkVersionsArr[0] && + +versionsArr[1] === +checkVersionsArr[1] && + +versionsArr[2] >= +checkVersionsArr[2]) + ); } return false; - } + }; - public getMonthDays = (selMonth, selYear) => ((selMonth === 1 && selYear % 4 === 0) ? (this.MONTHS[selMonth].days + 1) : this.MONTHS[selMonth].days); + public getMonthDays = (selMonth, selYear) => + selMonth === 1 && selYear % 4 === 0 ? this.MONTHS[selMonth].days + 1 : this.MONTHS[selMonth].days; public logEnvVariables = (req) => { const selNode = req.session.selectedNode; if (selNode && selNode.index) { - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'PORT: ' + this.port }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'HOST: ' + this.host }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'SSO: ' + this.rtl_sso }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DEFAULT NODE INDEX: ' + selNode.index }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'INDEX: ' + selNode.index }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN NODE: ' + selNode.ln_node }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN IMPLEMENTATION: ' + selNode.ln_implementation }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'FIAT CONVERSION: ' + selNode.fiat_conversion }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'CURRENCY UNIT: ' + selNode.currency_unit }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN SERVER URL: ' + selNode.ln_server_url }); - this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LOGOUT REDIRECT LINK: ' + this.logout_redirect_link + '\r\n' }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'PORT: ' + this.port + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'HOST: ' + this.host + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'SSO: ' + this.rtl_sso + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'DEFAULT NODE INDEX: ' + selNode.index + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'INDEX: ' + selNode.index + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'LN NODE: ' + selNode.ln_node + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'LN IMPLEMENTATION: ' + selNode.ln_implementation + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'FIAT CONVERSION: ' + selNode.fiat_conversion + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'CURRENCY UNIT: ' + selNode.currency_unit + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'LN SERVER URL: ' + selNode.ln_server_url + }); + this.logger.log({ + selectedNode: selNode, + level: 'INFO', + fileName: 'Config Setup Variable', + msg: 'LOGOUT REDIRECT LINK: ' + this.logout_redirect_link + '\r\n' + }); } - } + }; public filterData = (dataKey, lnImplementation) => { let search_string = ''; if (lnImplementation === 'ECL') { switch (dataKey) { - case 'GetInfo': search_string = 'INFO: GetInfo => Get Info Response: '; break; - case 'Fees': search_string = 'INFO: Fees => Fee Response: '; break; - case 'Payments': search_string = 'INFO: Fees => Payments Response: '; break; - case 'Invoices': search_string = 'INFO: Invoice => Invoices List Received: '; break; - case 'OnChainBalance': search_string = 'INFO: Onchain => Balance Received: '; break; - case 'Peers': search_string = 'INFO: Peers => Peers with Alias: '; break; - case 'Channels': search_string = 'INFO: Channels => Simplified Channels with Alias: '; break; - default: search_string = 'Random Line'; break; + case 'GetInfo': + search_string = 'INFO: GetInfo => Get Info Response: '; + break; + case 'Fees': + search_string = 'INFO: Fees => Fee Response: '; + break; + case 'Payments': + search_string = 'INFO: Fees => Payments Response: '; + break; + case 'Invoices': + search_string = 'INFO: Invoice => Invoices List Received: '; + break; + case 'OnChainBalance': + search_string = 'INFO: Onchain => Balance Received: '; + break; + case 'Peers': + search_string = 'INFO: Peers => Peers with Alias: '; + break; + case 'Channels': + search_string = 'INFO: Channels => Simplified Channels with Alias: '; + break; + default: + search_string = 'Random Line'; + break; } } else if (lnImplementation === 'CLN') { switch (dataKey) { - case 'GetInfo': search_string = 'DEBUG: GetInfo => Node Information. '; break; - case 'Fees': search_string = 'DEBUG: Fees => Fee Received. '; break; - case 'Payments': search_string = 'DEBUG: Payments => Payment List Received: '; break; - case 'Invoices': search_string = 'DEBUG: Invoice => Invoices List Received. '; break; - case 'ChannelBalance': search_string = 'DEBUG: Channels => Local Remote Balance. '; break; - case 'Peers': search_string = 'DEBUG: Peers => Peers with Alias: '; break; - case 'Channels': search_string = 'DEBUG: Channels => List Channels: '; break; - case 'Balance': search_string = 'DEBUG: Balance => Balance Received. '; break; - case 'ForwardingHistory': search_string = 'DEBUG: Channels => Forwarding History Received: '; break; - case 'UTXOs': search_string = 'DEBUG: OnChain => List Funds Received. '; break; - case 'FeeRateperkb': search_string = 'DEBUG: Network => Network Fee Rates Received for perkb. '; break; - case 'FeeRateperkw': search_string = 'DEBUG: Network => Network Fee Rates Received for perkw. '; break; - default: search_string = 'Random Line'; break; + case 'GetInfo': + search_string = 'DEBUG: GetInfo => Node Information. '; + break; + case 'Fees': + search_string = 'DEBUG: Fees => Fee Received. '; + break; + case 'Payments': + search_string = 'DEBUG: Payments => Payment List Received: '; + break; + case 'Invoices': + search_string = 'DEBUG: Invoice => Invoices List Received. '; + break; + case 'ChannelBalance': + search_string = 'DEBUG: Channels => Local Remote Balance. '; + break; + case 'Peers': + search_string = 'DEBUG: Peers => Peers with Alias: '; + break; + case 'Channels': + search_string = 'DEBUG: Channels => List Channels: '; + break; + case 'Balance': + search_string = 'DEBUG: Balance => Balance Received. '; + break; + case 'ForwardingHistory': + search_string = 'DEBUG: Channels => Forwarding History Received: '; + break; + case 'UTXOs': + search_string = 'DEBUG: OnChain => List Funds Received. '; + break; + case 'FeeRateperkb': + search_string = 'DEBUG: Network => Network Fee Rates Received for perkb. '; + break; + case 'FeeRateperkw': + search_string = 'DEBUG: Network => Network Fee Rates Received for perkw. '; + break; + default: + search_string = 'Random Line'; + break; } } const foundDataLine = this.dummy_data_array_from_file.find((dataItem) => dataItem.includes(search_string)); - const dataStr = foundDataLine ? foundDataLine.substring((foundDataLine.indexOf(search_string)) + search_string.length) : '{}'; + const dataStr = foundDataLine + ? foundDataLine.substring(foundDataLine.indexOf(search_string) + search_string.length) + : '{}'; return JSON.parse(dataStr); - } - + }; } export const Common = new CommonService(); diff --git a/server/utils/config.ts b/server/utils/config.ts index fe0d3f2b0..8d5fea53a 100644 --- a/server/utils/config.ts +++ b/server/utils/config.ts @@ -9,7 +9,6 @@ import { Common, CommonService } from './common.js'; import { Logger, LoggerService } from './logger.js'; export class ConfigService { - private platform = os.platform(); private hash = crypto.createHash('sha256'); private errMsg = ''; @@ -17,7 +16,7 @@ export class ConfigService { private common: CommonService = Common; private logger: LoggerService = Logger; - constructor() { } + constructor() {} private setDefaultConfig = () => { const homeDir = os.userInfo().homedir; @@ -92,9 +91,56 @@ export class ConfigService { return false; }; + private normalizeSSL = (val) => { + let ssl; + if (typeof val === 'string') { + ssl = val === 'true' ? true : val === 'false' ? false : JSON.parse(val); + } else if (typeof val === 'undefined') { + return false; + } else if (typeof val === 'boolean') { + return val; + } else { + ssl = val; + } + const { + key = undefined, + cert = undefined, + ca = undefined, + altIp = '127.0.0.1', + commonName = 'localhost', + countryName = 'US', + encryptionBits = 2048, + stateName = 'New York', + localityName = 'New York', + organizationName = 'RTL', + organizationalUnit = 'RTL', + validForYears = 10, + requestCert = false, + rejectUnauthorized = false + } = ssl; + return { + key, + cert, + ca, + altIp, + commonName, + countryName, + encryptionBits, + stateName, + localityName, + organizationName, + organizationalUnit, + validForYears, + requestCert, + rejectUnauthorized + }; + }; + private updateLogByLevel = () => { let updateLogFlag = false; - this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..'); + this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH + ? process.env.RTL_CONFIG_PATH + : join(this.directoryName, '../..'); try { const RTLConfFile = this.common.rtl_conf_file_path + sep + 'RTL-Config.json'; const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8')); @@ -114,7 +160,7 @@ export class ConfigService { }; private validateNodeConfig = (config) => { - if ((+process.env.RTL_SSO === 0) || (typeof process.env.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) { + if (+process.env.RTL_SSO === 0 || (typeof process.env.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) { if (process.env.APP_PASSWORD && process.env.APP_PASSWORD.trim() !== '') { this.common.rtl_pass = this.hash.update(process.env.APP_PASSWORD).digest('hex'); this.common.flg_allow_password_update = false; @@ -123,7 +169,8 @@ export class ConfigService { } else if (config.multiPass && config.multiPass !== '') { this.common.rtl_pass = this.common.replacePasswordWithHash(this.hash.update(config.multiPass).digest('hex')); } else { - this.errMsg = this.errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json'; + this.errMsg = + this.errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json'; } this.common.rtl_secret2fa = config.secret2fa; } else { @@ -131,18 +178,42 @@ export class ConfigService { this.errMsg = this.errMsg + '\nRTL Password cannot be set with SSO. Please set SSO as 0 or remove password.'; } } - this.common.port = (process.env.PORT) ? this.normalizePort(process.env.PORT) : (config.port) ? this.normalizePort(config.port) : 3000; - this.common.host = (process.env.HOST) ? process.env.HOST : (config.host) ? config.host : null; + this.common.port = process.env.PORT + ? this.normalizePort(process.env.PORT) + : config.port + ? this.normalizePort(config.port) + : 3000; + this.common.ssl = process.env.SSL + ? this.normalizeSSL(process.env.SSL) + : config.ssl + ? this.normalizeSSL(config.ssl) + : false; + this.common.host = process.env.HOST ? process.env.HOST : config.host ? config.host : null; if (config.nodes && config.nodes.length > 0) { config.nodes.forEach((node, idx) => { this.common.nodes[idx] = {}; this.common.nodes[idx].index = node.index; this.common.nodes[idx].ln_node = node.lnNode; - this.common.nodes[idx].ln_implementation = (process.env.LN_IMPLEMENTATION) ? process.env.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND'; - if (this.common.nodes[idx].ln_implementation === 'CLT') { this.common.nodes[idx].ln_implementation = 'CLN'; } - if (this.common.nodes[idx].ln_implementation !== 'ECL' && process.env.MACAROON_PATH && process.env.MACAROON_PATH.trim() !== '') { + this.common.nodes[idx].ln_implementation = process.env.LN_IMPLEMENTATION + ? process.env.LN_IMPLEMENTATION + : node.lnImplementation + ? node.lnImplementation + : 'LND'; + if (this.common.nodes[idx].ln_implementation === 'CLT') { + this.common.nodes[idx].ln_implementation = 'CLN'; + } + if ( + this.common.nodes[idx].ln_implementation !== 'ECL' && + process.env.MACAROON_PATH && + process.env.MACAROON_PATH.trim() !== '' + ) { this.common.nodes[idx].macaroon_path = process.env.MACAROON_PATH; - } else if (this.common.nodes[idx].ln_implementation !== 'ECL' && node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') { + } else if ( + this.common.nodes[idx].ln_implementation !== 'ECL' && + node.Authentication && + node.Authentication.macaroonPath && + node.Authentication.macaroonPath.trim() !== '' + ) { this.common.nodes[idx].macaroon_path = node.Authentication.macaroonPath; } else if (this.common.nodes[idx].ln_implementation !== 'ECL') { this.errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Config.json!'; @@ -164,14 +235,20 @@ export class ConfigService { } else { this.common.nodes[idx].config_path = ''; } - if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '' && this.common.nodes[idx].config_path !== '') { + if ( + this.common.nodes[idx].ln_implementation === 'ECL' && + this.common.nodes[idx].ln_api_password === '' && + this.common.nodes[idx].config_path !== '' + ) { try { const exists = fs.existsSync(this.common.nodes[idx].config_path); if (exists) { try { const configFile = fs.readFileSync(this.common.nodes[idx].config_path, 'utf-8'); const iniParsed = ini.parse(configFile); - this.common.nodes[idx].ln_api_password = iniParsed['eclair.api.password'] ? iniParsed['eclair.api.password'] : parseHocon(configFile).eclair.api.password; + this.common.nodes[idx].ln_api_password = iniParsed['eclair.api.password'] + ? iniParsed['eclair.api.password'] + : parseHocon(configFile).eclair.api.password; } catch (err) { this.errMsg = this.errMsg + '\nSomething went wrong while reading config file: \n' + err; } @@ -183,19 +260,32 @@ export class ConfigService { } } if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '') { - this.errMsg = this.errMsg + '\nPlease set config path Or api password for node index ' + node.index + ' in RTL-Config.json! It is mandatory for Eclair authentication!'; + this.errMsg = + this.errMsg + + '\nPlease set config path Or api password for node index ' + + node.index + + ' in RTL-Config.json! It is mandatory for Eclair authentication!'; } if (process.env.LN_SERVER_URL && process.env.LN_SERVER_URL.trim() !== '') { - this.common.nodes[idx].ln_server_url = process.env.LN_SERVER_URL.endsWith('/v1') ? process.env.LN_SERVER_URL.slice(0, -3) : process.env.LN_SERVER_URL; + this.common.nodes[idx].ln_server_url = process.env.LN_SERVER_URL.endsWith('/v1') + ? process.env.LN_SERVER_URL.slice(0, -3) + : process.env.LN_SERVER_URL; } else if (process.env.LND_SERVER_URL && process.env.LND_SERVER_URL.trim() !== '') { - this.common.nodes[idx].ln_server_url = process.env.LND_SERVER_URL.endsWith('/v1') ? process.env.LND_SERVER_URL.slice(0, -3) : process.env.LND_SERVER_URL; + this.common.nodes[idx].ln_server_url = process.env.LND_SERVER_URL.endsWith('/v1') + ? process.env.LND_SERVER_URL.slice(0, -3) + : process.env.LND_SERVER_URL; } else if (node.Settings.lnServerUrl && node.Settings.lnServerUrl.trim() !== '') { - this.common.nodes[idx].ln_server_url = node.Settings.lnServerUrl.endsWith('/v1') ? node.Settings.lnServerUrl.slice(0, -3) : node.Settings.lnServerUrl; + this.common.nodes[idx].ln_server_url = node.Settings.lnServerUrl.endsWith('/v1') + ? node.Settings.lnServerUrl.slice(0, -3) + : node.Settings.lnServerUrl; } else if (node.Settings.lndServerUrl && node.Settings.lndServerUrl.trim() !== '') { - this.common.nodes[idx].ln_server_url = node.Settings.lndServerUrl.endsWith('/v1') ? node.Settings.lndServerUrl.slice(0, -3) : node.Settings.lndServerUrl; + this.common.nodes[idx].ln_server_url = node.Settings.lndServerUrl.endsWith('/v1') + ? node.Settings.lndServerUrl.slice(0, -3) + : node.Settings.lndServerUrl; } else { - this.errMsg = this.errMsg + '\nPlease set LN Server URL for node index ' + node.index + ' in RTL-Config.json!'; + this.errMsg = + this.errMsg + '\nPlease set LN Server URL for node index ' + node.index + ' in RTL-Config.json!'; } this.common.nodes[idx].user_persona = node.Settings.userPersona ? node.Settings.userPersona : 'MERCHANT'; this.common.nodes[idx].theme_mode = node.Settings.themeMode ? node.Settings.themeMode : 'DAY'; @@ -206,28 +296,52 @@ export class ConfigService { this.common.nodes[idx].currency_unit = node.Settings.currencyUnit ? node.Settings.currencyUnit : 'USD'; } if (process.env.SWAP_SERVER_URL && process.env.SWAP_SERVER_URL.trim() !== '') { - this.common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') ? process.env.SWAP_SERVER_URL.slice(0, -3) : process.env.SWAP_SERVER_URL; + this.common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') + ? process.env.SWAP_SERVER_URL.slice(0, -3) + : process.env.SWAP_SERVER_URL; this.common.nodes[idx].swap_macaroon_path = process.env.SWAP_MACAROON_PATH; } else if (node.Settings.swapServerUrl && node.Settings.swapServerUrl.trim() !== '') { - this.common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') ? node.Settings.swapServerUrl.slice(0, -3) : node.Settings.swapServerUrl; - this.common.nodes[idx].swap_macaroon_path = node.Authentication.swapMacaroonPath ? node.Authentication.swapMacaroonPath : ''; + this.common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') + ? node.Settings.swapServerUrl.slice(0, -3) + : node.Settings.swapServerUrl; + this.common.nodes[idx].swap_macaroon_path = node.Authentication.swapMacaroonPath + ? node.Authentication.swapMacaroonPath + : ''; } else { this.common.nodes[idx].swap_server_url = ''; this.common.nodes[idx].swap_macaroon_path = ''; } if (process.env.BOLTZ_SERVER_URL && process.env.BOLTZ_SERVER_URL.trim() !== '') { - this.common.nodes[idx].boltz_server_url = process.env.BOLTZ_SERVER_URL.endsWith('/v1') ? process.env.BOLTZ_SERVER_URL.slice(0, -3) : process.env.BOLTZ_SERVER_URL; + this.common.nodes[idx].boltz_server_url = process.env.BOLTZ_SERVER_URL.endsWith('/v1') + ? process.env.BOLTZ_SERVER_URL.slice(0, -3) + : process.env.BOLTZ_SERVER_URL; this.common.nodes[idx].boltz_macaroon_path = process.env.BOLTZ_MACAROON_PATH; } else if (node.Settings.boltzServerUrl && node.Settings.boltzServerUrl.trim() !== '') { - this.common.nodes[idx].boltz_server_url = node.Settings.boltzServerUrl.endsWith('/v1') ? node.Settings.boltzServerUrl.slice(0, -3) : node.Settings.boltzServerUrl; - this.common.nodes[idx].boltz_macaroon_path = node.Authentication.boltzMacaroonPath ? node.Authentication.boltzMacaroonPath : ''; + this.common.nodes[idx].boltz_server_url = node.Settings.boltzServerUrl.endsWith('/v1') + ? node.Settings.boltzServerUrl.slice(0, -3) + : node.Settings.boltzServerUrl; + this.common.nodes[idx].boltz_macaroon_path = node.Authentication.boltzMacaroonPath + ? node.Authentication.boltzMacaroonPath + : ''; } else { this.common.nodes[idx].boltz_server_url = ''; this.common.nodes[idx].boltz_macaroon_path = ''; } - this.common.nodes[idx].enable_offers = process.env.ENABLE_OFFERS ? process.env.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false; - this.common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : ''; - this.common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH ? process.env.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index; + this.common.nodes[idx].enable_offers = process.env.ENABLE_OFFERS + ? process.env.ENABLE_OFFERS + : node.Settings.enableOffers + ? node.Settings.enableOffers + : false; + this.common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH + ? process.env.BITCOIND_CONFIG_PATH + : node.Settings.bitcoindConfigPath + ? node.Settings.bitcoindConfigPath + : ''; + this.common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH + ? process.env.CHANNEL_BACKUP_PATH + : node.Settings.channelBackupPath + ? node.Settings.channelBackupPath + : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index; try { this.common.createDirectory(this.common.nodes[idx].channel_backup_path); const exists = fs.existsSync(this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak'); @@ -236,21 +350,38 @@ export class ConfigService { if (this.common.nodes[idx].ln_implementation === 'LND') { this.common.getAllNodeAllChannelBackup(this.common.nodes[idx]); } else { - const createStream = fs.createWriteStream(this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak'); + const createStream = fs.createWriteStream( + this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak' + ); createStream.end(); } } catch (err) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating backup file: \n' + err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while creating backup file: \n' + err + }); } } } catch (err) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating the backup directory: \n' + err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while creating the backup directory: \n' + err + }); } this.common.nodes[idx].log_file = this.common.rtl_conf_file_path + '/logs/RTL-Node-' + node.index + '.log'; - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'Config', msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'Config', + msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) + }); const log_file = this.common.nodes[idx].log_file; if (fs.existsSync(log_file)) { - fs.writeFile(log_file, '', () => { }); + fs.writeFile(log_file, '', () => {}); } else { try { const directoryName = dirname(log_file); @@ -258,13 +389,20 @@ export class ConfigService { const createStream = fs.createWriteStream(log_file); createStream.end(); } catch (err) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating log file ' + log_file + ': \n' + err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while creating log file ' + log_file + ': \n' + err + }); } } }); } this.setSSOParams(config); - if (this.errMsg && this.errMsg.trim() !== '') { throw new Error(this.errMsg); } + if (this.errMsg && this.errMsg.trim() !== '') { + throw new Error(this.errMsg); + } }; private setSSOParams = (config) => { @@ -307,7 +445,9 @@ export class ConfigService { public setServerConfiguration = () => { try { - this.common.rtl_conf_file_path = (process.env.RTL_CONFIG_PATH) ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..'); + this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH + ? process.env.RTL_CONFIG_PATH + : join(this.directoryName, '../..'); const confFileFullPath = this.common.rtl_conf_file_path + sep + 'RTL-Config.json'; if (!fs.existsSync(confFileFullPath)) { fs.writeFileSync(confFileFullPath, JSON.stringify(this.setDefaultConfig())); @@ -317,11 +457,15 @@ export class ConfigService { this.validateNodeConfig(config); this.setSelectedNode(config); } catch (err) { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while configuring the node server: \n' + err }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'ERROR', + fileName: 'Config', + msg: 'Something went wrong while configuring the node server: \n' + err + }); throw new Error(err); } }; - } export const Config = new ConfigService(); diff --git a/server/utils/cors.ts b/server/utils/cors.ts index 57bec2d7c..81f981316 100644 --- a/server/utils/cors.ts +++ b/server/utils/cors.ts @@ -3,26 +3,35 @@ import { Logger, LoggerService } from './logger.js'; import { Common, CommonService } from './common.js'; class CORS { - public logger: LoggerService = Logger; public common: CommonService = Common; public mount(app: Application): Application { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CORS', msg: 'Setting up CORS..' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'CORS', + msg: 'Setting up CORS..' + }); app.use((req, res, next) => { res.setHeader('Cache-Control', 'no-cache'); - res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, filePath'); + res.setHeader( + 'Access-Control-Allow-Headers', + 'Origin, X-Requested-With, Content-Type, Accept, Authorization, filePath' + ); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS'); if (process.env.NODE_ENV === 'development') { res.setHeader('Access-Control-Allow-Credentials', 'true'); - res.setHeader('Access-Control-Allow-Origin', req.headers.origin ? req.headers.origin : req.headers.host ? req.headers.host : ''); + res.setHeader( + 'Access-Control-Allow-Origin', + req.headers.origin ? req.headers.origin : req.headers.host ? req.headers.host : '' + ); } next(); }); this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CORS', msg: 'CORS Set' }); return app; - }; - + } } -export default new CORS; +export default new CORS(); diff --git a/server/utils/csrf.ts b/server/utils/csrf.ts index 7b12a82f6..b6026f054 100644 --- a/server/utils/csrf.ts +++ b/server/utils/csrf.ts @@ -4,20 +4,23 @@ import { Logger, LoggerService } from './logger.js'; import { Common, CommonService } from './common.js'; class CSRF { - - public csrfProtection = csurf({ cookie: true }); + public csrfProtection = csurf({ cookie: true, secure: Common.ssl ? true : false }); public logger: LoggerService = Logger; public common: CommonService = Common; public mount(app: Application): Application { - this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CSRF', msg: 'Setting up CSRF..' }); + this.logger.log({ + selectedNode: this.common.initSelectedNode, + level: 'INFO', + fileName: 'CSRF', + msg: 'Setting up CSRF..' + }); if (process.env.NODE_ENV !== 'development') { app.use((req, res, next) => this.csrfProtection(req, res, next)); } this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CSRF', msg: 'CSRF Set' }); return app; - }; - + } } -export default new CSRF; +export default new CSRF(); diff --git a/server/utils/ssl.ts b/server/utils/ssl.ts new file mode 100644 index 000000000..b26341547 --- /dev/null +++ b/server/utils/ssl.ts @@ -0,0 +1,108 @@ +import * as fs from 'fs'; +import CertificateFactory from './certificateFactory.js'; +import { Logger, LoggerService } from './logger.js'; + +const { assign } = Object; + +class SSL { + private logger: LoggerService = Logger; + private key: Buffer = undefined; + private cert: Buffer = undefined; + private ca: Buffer = undefined; + private requestCert: Boolean = false; + private rejectUnauthorized: Boolean = false; + + constructor(options: any = true) { + const factoryOptions: any = {}; + const filePaths: any = {}; + if (typeof options === 'object') { + const { + altIp, + commonName, + countryName, + encryptionBits, + stateName, + localityName, + organizationName, + organizationalUnit, + validForYears, + requestCert, + rejectUnauthorized + } = options; + assign(factoryOptions, { + altIp, + commonName, + countryName, + encryptionBits, + stateName, + localityName, + organizationName, + organizationalUnit, + validForYears + }); + filePaths.key = options.key; + filePaths.cert = options.cert; + filePaths.ca = options.ca; + if (typeof requestCert === 'boolean') { + this.requestCert = requestCert; + } + if (typeof rejectUnauthorized === 'boolean') { + this.rejectUnauthorized = rejectUnauthorized; + } + } + try { + if (filePaths.key) { + this.key = fs.readFileSync(filePaths.key); + } + if (filePaths.cert) { + this.cert = fs.readFileSync(filePaths.cert); + } + if (filePaths.ca) { + this.ca = fs.readFileSync(filePaths.ca); + } + } catch (e) { + this.logger.log({ + level: 'ERROR', + fileName: 'SSL', + msg: `Error reading certificate files - ${e.toString()}` + }); + throw e; + } + + if (!this.key && !this.cert && !this.ca) { + try { + const selfSignedCertificateFactory = new CertificateFactory(factoryOptions); + assign(this, selfSignedCertificateFactory.getStaticBundle('buffer')); + } catch (e) { + this.logger.log({ + level: 'ERROR', + fileName: 'SSL', + msg: `Error generating self-signed certificates - ${e.toString()}` + }); + throw e; + } + } + } + + //--------------------------------------------------------------------------------------- + //Method: toObject + // + //Input: none + //Output: ssl server configuration object + // + //This method returns https server config options generated by this class + //--------------------------------------------------------------------------------------- + public toObject() { + const { key, cert, ca, rejectUnauthorized, requestCert } = this; + + return { + key, + cert, + ca, + rejectUnauthorized, + requestCert + }; + } +} + +export default SSL;