Merge branch 'ConfigUpgrade'

pull/260/head
Shahana Farooqui 4 years ago
commit a865b361de

@ -75,19 +75,19 @@ workflows:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*/
only: /v(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?:([A-Za-z1-9])*))?)$/
- publish_docker_linuxarm32v7:
filters:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*/
only: /v(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?:([A-Za-z1-9])*))?)$/
- publish_docker_linuxarm64v8:
filters:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*/
only: /v(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?:([A-Za-z1-9])*))?)$/
- publish_docker_multiarch:
requires:
- publish_docker_linuxamd64
@ -97,4 +97,4 @@ workflows:
branches:
ignore: /.*/
tags:
only: /v[0-9]+(\.[0-9]+)*/
only: /v(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?:([A-Za-z1-9])*))?)$/

10
.gitignore vendored

@ -37,14 +37,14 @@ testem.log
.DS_Store
Thumbs.db
RTL.conf
/logs/*
/cookies/*
RTL-Multi-Node-Conf.json
RTL-Multi-Node-Conf-1.json
/backup/*
cookies
sample-RTL-SSO.conf
.env
RTL-1.conf
RTL-Config.json
RTL-Config-1.json
RTL-Multi-Node-Conf.json
RTL.conf
RTL-1.conf
RTL-Multi-Node-Conf-1.json

@ -71,46 +71,53 @@ $ git pull
$ npm install --only=prod
```
### <a name="prep"></a>Prep for Execution
RTL requires its own config file `RTL.conf`, to start the server and provide user authentication on the app.
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
*Advanced users can refer to [this page](docs/Multi-Node-setup.md), for config settings required to manage multiple nodes*
* Rename `sample-RTL.conf` file to `RTL.conf`.
* Rename `sample-RTL-Config.json` file to `RTL-Config.json`.
* Locate the complete path of the readable macroon file (admin.macroon) on your node and the lnd.conf file.
* Modify the `RTL.conf` file per the example file below
* Modify the `RTL-Config.json` file per the example file below
Example RTL.conf:
Example RTL-Config.json:
```
[Authentication]
macaroonPath=C:\Users\<user>\AppData\Local\Lnd\data\chain\bitcoin\testnet
nodeAuthType=CUSTOM
lndConfigPath=C:\Users\<user>\AppData\Local\Lnd\lnd.conf
rtlPass=***
[SSO]
rtlSSO=0
rtlCookiePath=C:\RTL\cookies\auth.cookie
logoutRedirectLink=/login
[Settings]
userPersona=OPERATOR
themeMode=DAY
themeColor=PURPLE
channelBackupPath=C:\Users\shaha\backup\node-0
bitcoindConfigPath=C:/Bitcoin/bitcoin.conf
enableLogging=true
port=3000
lndServerUrl=https://192.168.1.16:8080/v1
fiatConversion=false
{
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
"logoutRedirectLink": ""
},
"nodes": [
{
"index": 1,
"lnNode": "LND Testnet",
"lnImplementation": "LND",
"Authentication": {
"macaroonPath": "<Complete path of the folder containing admin.macaroon for the node # 1>",
"configPath": "<Optional:Path of the lnd.conf if present locally or empty>"
},
"Settings": {
"userPersona": "OPERATOR",
"themeMode": "DAY",
"themeColor": "PURPLE",
"channelBackupPath": "C:\\RTL\\backup\\node-1",
"bitcoindConfigPath": "<Optional: path of bitcoind.conf path if available locally>",
"enableLogging": true,
"fiatConversion": false,
"lnServerUrl": "<Service url for LND REST APIs for node # 1 e.g. https://192.168.0.1:8080/v1"
}
}
]
}
```
For details on all the configuration options refer to [this page](./docs/Application_configurations).
#### User Authentication on RTL
RTL requires the user to be authenticated by the application first, before allowing access to LND functions.
There are two options to configure authentication on RTL, depending on the `nodeAuthtype` value provided in RTL.conf.
* Option 1: `nodeAuthType=DEFAULT`; Password provided in lnd.conf for the rpc setting for bitcoind will be used for authentication.
* Option 2: `nodeAuthType=CUSTOM`; Specific password must be provided in RTL.conf (in plain text) for authentication. Password should be set with `rtlPass=<user defined>` in the [Authentication] section of RTL.conf
Specific password must be provided in RTL-Config.json (in plain text) for authentication. Password should be set with `multiPass:<user defined>` in the `Authentication` section of RTL-Config.json. Default initial password is `password`.
### <a name="start"></a>Start the Server
Run the following command:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -9,8 +9,8 @@
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicon/favicon-16x16.png">
<link rel="manifest" href="assets/images/favicon/site.webmanifest">
<link rel="stylesheet" href="styles.90ee7bcb73e8367b2a29.css"></head>
<link rel="stylesheet" href="styles.76f25502eb3f17a51952.css"></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.381542d227df565e3542.js" defer></script><script src="polyfills-es5.37b2eeccc22c1df73ce7.js" nomodule defer></script><script src="polyfills.f1c3d2a0bcdfc4e93ca8.js" defer></script><script src="main.2483cf79fa62d6d527e8.js" defer></script></body>
<script src="runtime.bd15680a9b84bab2ef08.js" defer></script><script src="polyfills-es5.37b2eeccc22c1df73ce7.js" nomodule defer></script><script src="polyfills.f1c3d2a0bcdfc4e93ca8.js" defer></script><script src="main.df6ee6d1e5bed4756848.js" defer></script></body>
</html>

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"b78c4146e439be098e08",6:"17ce0762d67b997b139c",7:"731a74a56a4c6565c2cc"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);

@ -0,0 +1 @@
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],l=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,i),t.l=!0,t.exports}i.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"."+{1:"b78c4146e439be098e08",6:"3745fb0b662c2ed95634",7:"96c25bc9ca5994f524b4"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var l=0;l<a.length;l++)r(a[l]);var f=c;t()}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -3,9 +3,7 @@ var crypto = require('crypto');
var path = require('path');
var common = {};
common.multi_node_setup = false;
common.rtl_conf_file_path = '';
common.node_auth_type = 'DEFAULT';
common.rtl_pass = '';
common.rtl_sso = 0;
common.port = 3000;
@ -87,6 +85,11 @@ common.findNode = (selNodeIndex) => {
return common.nodes.find(node => node.index == selNodeIndex);
}
common.replaceNode = (selNodeIndex, newNode) => {
common.nodes.splice(common.nodes.findIndex((node) => {node.index == selNodeIndex}), 1, newNode);
common.selectedNode = common.findNode(selNodeIndex);
}
common.convertToBTC = (num) => {
return (num / 100000000).toFixed(6);
};

@ -3,21 +3,20 @@ var fs = require('fs');
var platform = require('os').platform();
var crypto = require('crypto');
var hash = crypto.createHash('sha256');
var clArgs = require('optimist').argv;
var ini = require('ini');
var common = require('./common');
var path = require('path');
var upperCase = require('upper-case');
var logger = require('./controllers/logger');
var connect = {};
var errMsg = '';
var request = require('request');
var ini = require('ini');
common.path_separator = (platform === 'win32') ? '\\' : '/';
connect.setDefaultConfig = () => {
var homeDir = os.userInfo().homedir;
var macaroonPath = '';
var configPath = '';
var channelBackupPath = '';
switch (platform) {
case 'win32':
macaroonPath = homeDir + '\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet';
@ -41,28 +40,35 @@ connect.setDefaultConfig = () => {
break;
}
return {
multiPass: "password",
port: "3000",
defaultNodeIndex: 1,
SSO: {
rtlSSO: 0,
rtlCookiePath: "",
logoutRedirectLink: ""
},
Authentication: {
macaroonPath: macaroonPath,
configPath: configPath,
nodeAuthType:"CUSTOM",
rtlPass:"password"
},
Settings: {
port: "3000",
lnImplementation: "LND",
userPersona: 'MERCHANT',
themeMode: "DAY",
themeColor: "PURPLE",
enableLogging: false,
lnServerUrl: "https://localhost:8080/v1",
fiatConversion: false
}
};
nodes: [
{
index: 1,
lnNode: "Node 1",
lnImplementation: "LND",
Authentication: {
macaroonPath: macaroonPath,
configPath: configPath,
},
Settings: {
userPersona: 'MERCHANT',
themeMode: "DAY",
themeColor: "PURPLE",
channelBackupPath: channelBackupPath,
enableLogging: false,
lnServerUrl: "https://localhost:8080/v1",
fiatConversion: false
}
}
]
}
}
connect.normalizePort = val => {
@ -76,263 +82,79 @@ connect.normalizePort = val => {
return false;
};
connect.setMacaroonPath = (clArgs, config) => {
common.nodes[0] = {};
common.nodes[0].index = 1;
if(clArgs.lndir) {
common.nodes[0].macaroon_path = clArgs.lndir;
} else if (process.env.MACAROON_PATH) {
common.nodes[0].macaroon_path = process.env.MACAROON_PATH;
} else {
if(config.Authentication.macroonPath && config.Authentication.macroonPath !== '') {
common.nodes[0].macaroon_path = config.Authentication.macroonPath;
} else if(config.Authentication.macaroonPath && config.Authentication.macaroonPath !== '') {
common.nodes[0].macaroon_path = config.Authentication.macaroonPath;
}
}
}
connect.convertCustomToHash = (nodeSetupType) => {
common.rtl_conf_file_path = (process.env.RTL_CONFIG_PATH) ? process.env.RTL_CONFIG_PATH.substring(0, process.env.RTL_CONFIG_PATH.length - 9) : path.normalize(__dirname);
if(nodeSetupType === 'SINGLE') {
try {
RTLConfFile = common.rtl_conf_file_path + '/RTL.conf';
var config = ini.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
const authTemp = config.Authentication;
authTemp.rtlPassHashed = hash.update(authTemp.rtlPass).digest('hex');
delete authTemp.rtlPass;
delete config.Authentication;
fs.writeFileSync(RTLConfFile, ini.stringify(config));
fs.appendFileSync(RTLConfFile, ini.stringify(authTemp, { section: 'Authentication' }));
console.log('Please note that RTL has hashed the plaintext password into its corresponding hash.');
return authTemp.rtlPassHashed;
} catch (err) {
errMsg = errMsg + '\nrtlPass hash conversion failed!';
}
}
if(nodeSetupType === 'MULTI') {
try {
RTLConfFile = common.rtl_conf_file_path + '/RTL-Multi-Node-Conf.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.multiPassHashed = hash.update(config.multiPass).digest('hex');
delete config.multiPass;
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
console.log('Please note that RTL has encrypted the plaintext password into its corresponding hash.');
return config.multiPassHashed;
} catch (err) {
errMsg = errMsg + '\nmultiPass hash conversion failed!';
}
}
}
connect.validateSingleNodeConfig = (config) => {
connect.setSSOParams(config);
if(process.env.LN_IMPLEMENTATION) {
common.nodes[0].ln_implementation = process.env.LN_IMPLEMENTATION;
} else if (config.Settings.lnImplementation && config.Settings.lnImplementation !== '') {
common.nodes[0].ln_implementation = config.Settings.lnImplementation;
} else {
common.nodes[0].ln_implementation = 'LND';
}
if(!+common.rtl_sso) {
if(process.env.NODE_AUTH_TYPE) {
common.node_auth_type = process.env.NODE_AUTH_TYPE;
} else {
if(config.Authentication.nodeAuthType === '' || undefined === config.Authentication.nodeAuthType) {
errMsg = errMsg + '\nPlease set Node Auth Type through environment or RTL.conf!';
} else {
common.node_auth_type = config.Authentication.nodeAuthType;
}
}
if (process.env.RTL_PASS) {
common.rtl_pass = hash.update(process.env.RTL_PASS).digest('hex');
} else if (config.Authentication.rtlPassHashed !== '' && config.Authentication.rtlPassHashed) {
common.rtl_pass = config.Authentication.rtlPassHashed;
} else if (config.Authentication.rtlPass !== '' && config.Authentication.rtlPass) {
common.rtl_pass = connect.convertCustomToHash('SINGLE');
}
if (upperCase(common.node_auth_type) === 'CUSTOM' && (common.rtl_pass === '' || undefined === common.rtl_pass)) {
errMsg = errMsg + '\nCustom Node Authentication can be set with RTL password only. Please set RTL Password through environment or RTL.conf';
}
if(process.env.LND_CONFIG_PATH) {
common.nodes[0].config_path = process.env.LND_CONFIG_PATH;
} else if (process.env.CONFIG_PATH) {
common.nodes[0].config_path = process.env.CONFIG_PATH;
} else {
if(config.Authentication.lndConfigPath !== '' && config.Authentication.lndConfigPath) {
common.nodes[0].config_path = config.Authentication.lndConfigPath;
} else if(config.Authentication.configPath && config.Authentication.configPath.trim() !== '') {
common.nodes[0].config_path = config.Authentication.configPath;
} else {
if(upperCase(common.node_auth_type) === 'DEFAULT') {
errMsg = errMsg + '\nDefault Node Authentication can be set with LND Config Path only. Please set LND Config Path through environment or RTL.conf!';
}
}
}
}
if(common.nodes[0].macaroon_path === '' || undefined === common.nodes[0].macaroon_path) {
errMsg = 'Please set macaroon path through environment or RTL.conf!';
}
if(process.env.LND_SERVER_URL) {
common.nodes[0].ln_server_url = process.env.LND_SERVER_URL;
} else if(process.env.LN_SERVER_URL) {
common.nodes[0].ln_server_url = process.env.LN_SERVER_URL;
} else {
if(
(config.Authentication.lndServerUrl === '' || undefined === config.Authentication.lndServerUrl)
&& (config.Settings.lndServerUrl === '' || undefined === config.Settings.lndServerUrl)
&& (config.Settings.lnServerUrl === '' || undefined === config.Settings.lnServerUrl)
) {
errMsg = errMsg + '\nPlease set Server URL through environment or RTL.conf!';
} else {
if (config.Settings.lndServerUrl !== '' && config.Settings.lndServerUrl) {
common.nodes[0].ln_server_url = config.Settings.lndServerUrl;
} else if (config.Authentication.lndServerUrl !== '' && config.Authentication.lndServerUrl) {
common.nodes[0].ln_server_url = config.Authentication.lndServerUrl;
} else if (config.Settings.lnServerUrl !== '' && config.Settings.lnServerUrl) {
common.nodes[0].ln_server_url = config.Settings.lnServerUrl;
}
}
}
if(process.env.BITCOIND_CONFIG_PATH) {
common.nodes[0].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH;
} else {
if(config.Settings.bitcoindConfigPath !== '' && config.Settings.bitcoindConfigPath) {
common.nodes[0].bitcoind_config_path = config.Settings.bitcoindConfigPath;
} else if(config.Authentication.bitcoindConfigPath !== '' && config.Authentication.bitcoindConfigPath) {
common.nodes[0].bitcoind_config_path = config.Authentication.bitcoindConfigPath;
}
}
if(common.ln_implementation === 'LND') {
if(process.env.CHANNEL_BACKUP_PATH) {
common.nodes[0].channel_backup_path = process.env.CHANNEL_BACKUP_PATH;
} else {
if(config.Settings.channelBackupPath !== '' && config.Settings.channelBackupPath) {
common.nodes[0].channel_backup_path = config.Settings.channelBackupPath;
} else {
common.nodes[0].channel_backup_path = common.rtl_conf_file_path + common.path_separator + 'backup';
}
try {
connect.createDirectory(common.nodes[0].channel_backup_path);
let exists = fs.existsSync(common.nodes[0].channel_backup_path + common.path_separator + 'channel-all.bak');
if (!exists) {
try {
var createStream = fs.createWriteStream(common.nodes[0].channel_backup_path + common.path_separator + 'channel-all.bak');
createStream.end();
} catch (err) {
console.error('Something went wrong while creating backup file: \n' + err);
}
}
} catch (err) {
console.error('Something went wrong while creating backup file: \n' + err);
}
}
}
if (config.Settings.enableLogging) {
common.nodes[0].enable_logging = config.Settings.enableLogging;
} else if (config.Authentication.enableLogging) {
common.nodes[0].enable_logging = config.Authentication.enableLogging;
}
if (common.nodes[0].enable_logging) {
common.nodes[0].log_file = common.rtl_conf_file_path + '/logs/RTL.log';
let exists = fs.existsSync(common.nodes[0].log_file);
if (exists) {
fs.writeFile(common.nodes[0].log_file, '', () => { });
} else {
try {
var dirname = path.dirname(common.nodes[0].log_file);
connect.createDirectory(dirname);
var createStream = fs.createWriteStream(common.nodes[0].log_file);
createStream.end();
}
catch (err) {
console.error('Something went wrong while creating log file: \n' + err);
}
}
}
if (config.Settings.fiatConversion) {
common.nodes[0].fiat_conversion = config.Settings.fiatConversion;
} else {
common.nodes[0].fiat_conversion = false;
}
if (config.Settings.fiatConversion && config.Settings.currencyUnit) {
common.nodes[0].currency_unit = config.Settings.currencyUnit;
}
if (process.env.PORT) {
common.port = connect.normalizePort(process.env.PORT);
} else if (config.Settings.port) {
common.port = connect.normalizePort(config.Settings.port);
}
if (errMsg !== '') {
throw new Error(errMsg);
connect.replacePasswordWithHash = (multiPassHashed) => {
common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : path.normalize(__dirname);
try {
RTLConfFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.multiPassHashed = multiPassHashed;
delete config.multiPass;
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
console.log('Please note that, RTL has encrypted the plaintext password into its corresponding hash.');
return config.multiPassHashed;
} catch (err) {
errMsg = errMsg + '\nPassword hashing failed!';
}
}
connect.validateMultiNodeConfig = (config) => {
connect.validateNodeConfig = (config) => {
if(!+config.SSO.rtlSSO) {
common.node_auth_type = 'CUSTOM';
if (process.env.RTL_PASS) {
common.rtl_pass = hash.update(process.env.RTL_PASS).digest('hex');
} else if (config.multiPassHashed !== '' && config.multiPassHashed) {
if (config.multiPassHashed !== '' && config.multiPassHashed) {
common.rtl_pass = config.multiPassHashed;
} else if (config.multiPass !== '' && config.multiPass) {
common.rtl_pass = connect.convertCustomToHash('MULTI');
common.rtl_pass = connect.replacePasswordWithHash(hash.update(config.multiPass).digest('hex'));
} else {
errMsg = errMsg + '\nMulti Node Authentication can be set with multiPass only. Please set MultiPass in RTL-Multi-Node-Conf.json';
errMsg = errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json';
}
}
common.port = (config.port) ? connect.normalizePort(config.port) : 3000;
common.port = (process.env.PORT) ? connect.normalizePort(process.env.PORT) : (config.port) ? connect.normalizePort(config.port) : 3000;
if (config.nodes && config.nodes.length > 0) {
config.nodes.forEach((node, idx) => {
common.nodes[idx] = {};
if(node.Authentication.macaroonPath === '' || undefined === node.Authentication.macaroonPath) {
errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Multi-Node-Conf.json!';
} else {
if (process.env.MACAROON_PATH && process.env.MACAROON_PATH.trim() !== '') {
common.nodes[idx].macaroon_path = process.env.MACAROON_PATH;
} else if(node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') {
common.nodes[idx].macaroon_path = node.Authentication.macaroonPath;
} else {
errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Config.json!';
}
if(
(node.Settings.lndServerUrl === '' || undefined === node.Settings.lndServerUrl)
&& (node.Settings.lnServerUrl === '' || undefined === node.Settings.lnServerUrl)
) {
errMsg = errMsg + '\nPlease set server URL for node index ' + node.index + ' in RTL-Multi-Node-Conf.json!';
if(process.env.LN_SERVER_URL && process.env.LN_SERVER_URL.trim() !== '') {
common.nodes[idx].ln_server_url = process.env.LN_SERVER_URL;
} else if(process.env.LND_SERVER_URL && process.env.LND_SERVER_URL.trim() !== '') {
common.nodes[idx].ln_server_url = process.env.LND_SERVER_URL;
} else if(node.Settings.lnServerUrl && node.Settings.lnServerUrl.trim() !== '') {
common.nodes[idx].ln_server_url = node.Settings.lnServerUrl;
} else if(node.Settings.lndServerUrl && node.Settings.lndServerUrl.trim() !== '') {
common.nodes[idx].ln_server_url = node.Settings.lndServerUrl;
} else {
common.nodes[idx].ln_server_url = node.Settings.lndServerUrl ? node.Settings.lndServerUrl : node.Settings.lnServerUrl;
errMsg = errMsg + '\nPlease set LN Server URL for node index ' + node.index + ' in RTL-Config.json!';
}
common.nodes[idx].index = node.index;
common.nodes[idx].ln_node = node.lnNode;
common.nodes[idx].ln_implementation = node.lnImplementation;
common.nodes[idx].fiat_conversion = node.Settings.fiatConversion ? node.Settings.fiatConversion : false;
common.nodes[idx].ln_implementation = (process.env.LN_IMPLEMENTATION) ? process.env.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
common.nodes[idx].user_persona = node.Settings.userPersona ? node.Settings.userPersona : 'MERCHANT';
common.nodes[idx].theme_mode = node.Settings.themeMode ? node.Settings.themeMode : 'DAY';
common.nodes[idx].theme_color = node.Settings.themeColor ? node.Settings.themeColor : 'PURPLE';
common.nodes[idx].fiat_conversion = node.Settings.fiatConversion ? !!node.Settings.fiatConversion : false;
if(common.nodes[idx].fiat_conversion) {
common.nodes[idx].currency_unit = node.Settings.currencyUnit ? node.Settings.currencyUnit : 'USD';
}
if (node.Authentication && node.Authentication.lndConfigPath) {
if (process.env.CONFIG_PATH) {
common.nodes[idx].config_path = process.env.CONFIG_PATH;
} else if (process.env.LND_CONFIG_PATH) {
common.nodes[idx].config_path = process.env.LND_CONFIG_PATH;
} else if (node.Authentication && node.Authentication.lndConfigPath) {
common.nodes[idx].config_path = node.Authentication.lndConfigPath;
} else if (node.Authentication && node.Authentication.configPath) {
common.nodes[idx].config_path = node.Authentication.configPath;
} else {
common.nodes[idx].config_path = '';
}
common.nodes[idx].bitcoind_config_path = (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
common.nodes[idx].enable_logging = (node.Settings.enableLogging) ? node.Settings.enableLogging : false;
common.nodes[idx].channel_backup_path = (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : common.rtl_conf_file_path + common.path_separator + 'backup' + common.path_separator + 'node-' + node.index;
common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
common.nodes[idx].enable_logging = (node.Settings.enableLogging) ? !!node.Settings.enableLogging : false;
common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH ? process.env.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : common.rtl_conf_file_path + common.path_separator + 'backup' + common.path_separator + 'node-' + node.index;
try {
connect.createDirectory(common.nodes[idx].channel_backup_path);
let exists = fs.existsSync(common.nodes[idx].channel_backup_path + common.path_separator + 'channel-all.bak');
@ -345,7 +167,7 @@ connect.validateMultiNodeConfig = (config) => {
}
}
} catch (err) {
console.error('Something went wrong while creating backup file: \n' + err);
console.error('Something went wrong while creating the backup directory: \n' + err);
}
if (common.nodes[idx].enable_logging) {
@ -403,9 +225,8 @@ connect.setSSOParams = (config) => {
connect.createDirectory = (dirname) => {
try {
const sep = path.sep;
const initDir = path.isAbsolute(dirname) ? sep : '';
dirname.split(sep).reduce((parentDir, childDir) => {
const initDir = path.isAbsolute(dirname) ? path.sep : '';
dirname.split(path.sep).reduce((parentDir, childDir) => {
const curDir = path.resolve(parentDir, childDir);
if (!fs.existsSync(curDir)) {
fs.mkdirSync(curDir);
@ -417,7 +238,7 @@ connect.createDirectory = (dirname) => {
return dirname;
}
if (err.code === 'ENOENT') {
throw new Error(`EACCES: permission denied, mkdir '${dirname}'`);
throw new Error(`ENOENT: No such file or directory, mkdir '${dirname}'. Ensure that channel backup path separator is '${(platform === 'win32') ? '\\\\' : '/'}'`);
}
}
}
@ -457,29 +278,20 @@ connect.refreshCookie = (cookieFile) => {
}
connect.logEnvVariables = () => {
if (common.multi_node_setup && common.nodes && common.nodes.length > 0) {
if (common.nodes && common.nodes.length > 0) {
common.nodes.forEach((node, idx) => {
if (!node.enable_logging) { return; }
logger.info({fileName: 'Config Setup Variable', msg: 'PORT: ' + common.port, node});
logger.info({fileName: 'Config Setup Variable', msg: 'DEFAULT NODE INDEX: ' + common.selectedNode.index});
logger.info({fileName: 'Config Setup Variable', msg: 'NODE_SETUP: MULTI', node});
logger.info({fileName: 'Config Setup Variable', msg: 'SSO: ' + common.rtl_sso, node});
logger.info({fileName: 'Config Setup Variable', msg: 'LOGOUT REDIRECT LINK: ' + common.logout_redirect_link + '\r\n', node});
logger.info({fileName: 'Config Setup Variable', msg: 'INDEX: ' + node.index, node});
logger.info({fileName: 'Config Setup Variable', msg: 'LN NODE: ' + node.ln_node, node});
logger.info({fileName: 'Config Setup Variable', msg: 'LN IMPLEMENTATION: ' + node.ln_implementation, node});
logger.info({fileName: 'Config Setup Variable', msg: 'PORT: ' + common.port, node});
logger.info({fileName: 'Config Setup Variable', msg: 'FIAT CONVERSION: ' + node.fiatConversion, node});
logger.info({fileName: 'Config Setup Variable', msg: 'FIAT CONVERSION: ' + node.fiat_conversion, node});
logger.info({fileName: 'Config Setup Variable', msg: 'CURRENCY UNIT: ' + node.currency_unit, node});
logger.info({fileName: 'Config Setup Variable', msg: 'LND SERVER URL: ' + node.ln_server_url, node});
logger.info({fileName: 'Config Setup Variable', msg: 'LN SERVER URL: ' + node.ln_server_url, node});
});
} else {
if (!common.nodes[0].enable_logging) { return; }
logger.info({fileName: 'Config Setup Variable', msg: 'NODE_SETUP: SINGLE'});
logger.info({fileName: 'Config Setup Variable', msg: 'PORT: ' + common.port});
logger.info({fileName: 'Config Setup Variable', msg: 'LN IMPLEMENTATION: ' + common.nodes[0].ln_implementation});
logger.info({fileName: 'Config Setup Variable', msg: 'LN SERVER URL: ' + common.nodes[0].ln_server_url});
logger.info({fileName: 'Config Setup Variable', msg: 'SSO: ' + common.rtl_sso});
logger.info({fileName: 'Config Setup Variable', msg: 'LOGOUT REDIRECT LINK: ' + common.logout_redirect_link});
}
}
@ -514,63 +326,187 @@ connect.getAllNodeAllChannelBackup = (node) => {
})
};
connect.setSingleNodeConfiguration = (singleNodeFilePath) => {
const exists = fs.existsSync(singleNodeFilePath);
if (exists) {
var config = ini.parse(fs.readFileSync(singleNodeFilePath, 'utf-8'));
connect.setMacaroonPath(clArgs, config);
connect.validateSingleNodeConfig(config);
connect.setSelectedNode(config);
connect.logEnvVariables();
connect.setSelectedNode = (config) => {
if(config.defaultNodeIndex) {
common.selectedNode = common.findNode(config.defaultNodeIndex);
} else {
try {
fs.writeFileSync(singleNodeFilePath, ini.stringify(connect.setDefaultConfig()));
var config = ini.parse(fs.readFileSync(singleNodeFilePath, 'utf-8'));
connect.setMacaroonPath(clArgs, config);
connect.validateSingleNodeConfig(config);
connect.setSelectedNode(config);
connect.logEnvVariables();
}
catch(err) {
console.error('Something went wrong while configuring the single node server: \n' + err);
throw new Error(err);
}
common.selectedNode = common.findNode(common.nodes[0].index);
}
}
connect.setMultiNodeConfiguration = (multiNodeFilePath) => {
try {
var config = JSON.parse(fs.readFileSync(multiNodeFilePath, 'utf-8'));
connect.validateMultiNodeConfig(config);
connect.setSelectedNode(config);
connect.logEnvVariables();
}
catch(err) {
console.error('Something went wrong while configuring the multi node server: \n' + err);
throw new Error(err);
connect.modifyJsonMultiNodeConfig = (confFileFullPath) => {
RTLConfFile = common.rtl_conf_file_path + '/RTL-Multi-Node-Conf.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
if (!config.SSO) { config.SSO = {}; }
var newConfig = {
port: config.port ? config.port : 3000,
defaultNodeIndex: config.defaultNodeIndex ? config.defaultNodeIndex : 1,
SSO: {
rtlSSO: config.SSO.rtlSSO ? config.SSO.rtlSSO : 0,
rtlCookiePath: config.SSO.rtlCookiePath ? config.SSO.rtlCookiePath : "",
logoutRedirectLink: config.SSO.logoutRedirectLink ? config.SSO.logoutRedirectLink : ""
},
nodes: []
};
if(config.nodes && config.nodes.length > 0) {
let newNode = {};
config.nodes.forEach((node, idx) => {
newNode = {
index: node.index ? node.index : (idx + 1),
lnNode: node.lnNode ? node.lnNode : "Node " + (idx + 1),
lnImplementation: node.lnImplementation ? node.lnImplementation : "LND",
Authentication: {
macaroonPath: node.Authentication.macaroonPath ? node.Authentication.macaroonPath : ''
},
Settings: {
userPersona: node.Settings.userPersona ? node.Settings.userPersona : "MERCHANT",
enableLogging: node.Settings.enableLogging ? !!node.Settings.enableLogging : false,
fiatConversion: node.Settings.fiatConversion ? node.Settings.fiatConversion : false
}
};
if (node.Authentication.configPath) {
newNode.Authentication.configPath = node.Authentication.configPath;
} else if (node.Authentication.lndConfigPath) {
newNode.Authentication.configPath = node.Authentication.lndConfigPath;
}
if (node.Settings.theme) {
var themeArr = node.Settings.theme.split("-");
if (themeArr[2]) { themeArr[1] = themeArr[1] + themeArr[2]; } // For light-blue-gray
newNode.Settings.themeMode = (themeArr[0] === "dark") ? "NIGHT" : "DAY";
newNode.Settings.themeColor = (themeArr[1] === "blue") ? "INDIGO" : (themeArr[1] === "pink") ? "PINK" : (themeArr[1] === "green" || themeArr[1] === "teal") ? "TEAL" : "PURPLE";
} else {
newNode.Settings.themeMode = node.Settings.themeMode ? node.Settings.themeMode : "DAY";
newNode.Settings.themeColor = node.Settings.themeColor ? node.Settings.themeColor : "PURPLE";
}
if (node.Settings.currencyUnit) {
newNode.Settings.currencyUnit = node.Settings.currencyUnit;
}
if (node.Settings.bitcoindConfigPath) {
newNode.Settings.bitcoindConfigPath = node.Settings.bitcoindConfigPath;
}
if (node.Settings.channelBackupPath) {
newNode.Settings.channelBackupPath = node.Settings.channelBackupPath;
}
if (node.Settings.lnServerUrl) {
newNode.Settings.lnServerUrl = node.Settings.lnServerUrl;
} else if (node.Settings.lndServerUrl) {
newNode.Settings.lnServerUrl = node.Settings.lndServerUrl;
}
newConfig.nodes.push(newNode);
});
}
newConfig.multiPassHashed = config.multiPassHashed ? config.multiPassHashed : config.multiPass ? hash.update(config.multiPass).digest('hex') : '';
fs.writeFileSync(confFileFullPath, JSON.stringify(newConfig, null, 2), 'utf-8');
}
connect.setSelectedNode = (config) => {
if(config.defaultNodeIndex) {
common.selectedNode = common.findNode(config.defaultNodeIndex);
connect.modifyIniSingleNodeConfig = (confFileFullPath) => {
RTLConfFile = common.rtl_conf_file_path + '/RTL.conf';
var config = ini.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
if (!config.SSO) { config.SSO = {}; }
if (!config.Authentication) { config.Authentication = {}; }
if (!config.Settings) { config.Settings = {}; }
var newConfig = {
port: config.Settings.port ? config.Settings.port : 3000,
defaultNodeIndex: 1,
SSO: {
rtlSSO: config.SSO.rtlSSO ? config.SSO.rtlSSO : 0,
rtlCookiePath: config.SSO.rtlCookiePath ? config.SSO.rtlCookiePath : "",
logoutRedirectLink: config.SSO.logoutRedirectLink ? config.SSO.logoutRedirectLink : ""
},
nodes: [
{
index: 1,
lnNode: "Node 1",
lnImplementation: config.Settings.lnImplementation ? config.Settings.lnImplementation : "LND",
Authentication: {
macaroonPath: config.Authentication.macaroonPath ? config.Authentication.macaroonPath : (config.Authentication.macroonPath ? config.Authentication.macroonPath : ''),
configPath: config.Authentication.configPath ? config.Authentication.configPath : (config.Authentication.lndConfigPath ? config.Authentication.lndConfigPath : ''),
},
Settings: {
userPersona: config.Settings.userPersona ? config.Settings.userPersona : "MERCHANT",
enableLogging: config.Settings.enableLogging ? !!config.Settings.enableLogging : (config.Authentication.enableLogging ? !!config.Authentication.enableLogging : false),
fiatConversion: config.Settings.fiatConversion ? config.Settings.fiatConversion : false
}
}
]
};
if (config.Settings.theme) {
var themeArr = config.Settings.theme.split("-");
if (themeArr[2]) { themeArr[1] = themeArr[1] + themeArr[2]; } // For light-blue-gray
newConfig.nodes[0].Settings.themeMode = (themeArr[0] === "dark") ? "NIGHT" : "DAY";
newConfig.nodes[0].Settings.themeColor = (themeArr[1] === "blue") ? "INDIGO" : (themeArr[1] === "pink") ? "PINK" : (themeArr[1] === "green" || themeArr[1] === "teal") ? "TEAL" : "PURPLE";
} else {
common.selectedNode = common.findNode(common.nodes[0].index);
newConfig.nodes[0].Settings.themeMode = config.Settings.themeMode ? config.Settings.themeMode : "DAY";
newConfig.nodes[0].Settings.themeColor = config.Settings.themeColor ? config.Settings.themeColor : "PURPLE";
}
if (config.Settings.currencyUnit) {
newConfig.nodes[0].Settings.currencyUnit = config.Settings.currencyUnit;
}
if (config.Settings.bitcoindConfigPath) {
newConfig.nodes[0].Settings.bitcoindConfigPath = config.Settings.bitcoindConfigPath;
} else if(config.Authentication.bitcoindConfigPath) {
newConfig.nodes[0].Settings.bitcoindConfigPath = config.Authentication.bitcoindConfigPath;
}
if (config.Settings.channelBackupPath) {
newConfig.nodes[0].Settings.channelBackupPath = config.Settings.channelBackupPath;
}
if (config.Settings.lnServerUrl) {
newConfig.nodes[0].Settings.lnServerUrl = config.Settings.lnServerUrl;
} else if (config.Settings.lndServerUrl) {
newConfig.nodes[0].Settings.lnServerUrl = config.Settings.lndServerUrl;
} else if (config.Authentication.lndServerUrl) {
newConfig.nodes[0].Settings.lnServerUrl = config.Authentication.lndServerUrl;
}
newConfig.multiPassHashed = config.Authentication.rtlPassHashed ? config.Authentication.rtlPassHashed : config.Authentication.rtlPass ? hash.update(config.Authentication.rtlPass).digest('hex') : '';
fs.writeFileSync(confFileFullPath, JSON.stringify(newConfig, null, 2), 'utf-8');
}
connect.upgradeConfig = (confFileFullPath) => {
try {
singleNodeConfFile = common.rtl_conf_file_path + '/RTL.conf';
multiNodeConfFile = common.rtl_conf_file_path + '/RTL-Multi-Node-Conf.json';
const singleNodeExists = fs.existsSync(singleNodeConfFile);
const multiNodeExists = fs.existsSync(multiNodeConfFile);
if ((singleNodeExists && multiNodeExists) || (!singleNodeExists && multiNodeExists)) {
console.log('Start...config migration for file ' + multiNodeConfFile);
connect.modifyJsonMultiNodeConfig(confFileFullPath);
console.log('End...config migration.');
} else if (singleNodeExists && !multiNodeExists) {
console.log('Start...config migration for file ' + singleNodeConfFile);
connect.modifyIniSingleNodeConfig(confFileFullPath);
console.log('End...config migration.');
} else if (!singleNodeExists && !multiNodeExists) {
if (!fs.existsSync(confFileFullPath)) {
console.log('Start...config creation at: ' + confFileFullPath);
fs.writeFileSync(confFileFullPath, JSON.stringify(connect.setDefaultConfig(), null, 2), 'utf-8');
console.log('End...config creation.');
}
}
} catch(err) {
console.error('Something went wrong while upgrading the RTL config file: \n' + err);
throw new Error(err);
}
}
connect.setServerConfiguration = () => {
common.rtl_conf_file_path = (process.env.RTL_CONFIG_PATH) ? process.env.RTL_CONFIG_PATH.substring(0, process.env.RTL_CONFIG_PATH.length - 9) : path.normalize(__dirname);
singleNodeConfFile = common.rtl_conf_file_path + '/RTL.conf';
multiNodeConfFile = common.rtl_conf_file_path + '/RTL-Multi-Node-Conf.json';
const singleNodeExists = fs.existsSync(singleNodeConfFile);
const multiNodeExists = fs.existsSync(multiNodeConfFile);
if ((!multiNodeExists && singleNodeExists) || (!multiNodeExists && !singleNodeExists)) {
common.multi_node_setup = false;
connect.setSingleNodeConfiguration(singleNodeConfFile);
} else if ((multiNodeExists && singleNodeExists) || (multiNodeExists && !singleNodeExists)) {
common.multi_node_setup = true;
connect.setMultiNodeConfiguration(multiNodeConfFile);
try {
common.rtl_conf_file_path = (process.env.RTL_CONFIG_PATH) ? process.env.RTL_CONFIG_PATH : path.normalize(__dirname);
confFileFullPath = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
if(!fs.existsSync(confFileFullPath)) {
connect.upgradeConfig(confFileFullPath);
}
var config = JSON.parse(fs.readFileSync(confFileFullPath, 'utf-8'));
connect.validateNodeConfig(config);
connect.setSelectedNode(config);
connect.logEnvVariables();
} catch(err) {
console.error('Something went wrong while configuring the node server: \n' + err);
throw new Error(err);
}
}

@ -8,176 +8,104 @@ var options = {};
exports.updateSelectedNode = (req, res, next) => {
const selNodeIndex = req.body.selNodeIndex;
common.selectedNode = common.findNode(selNodeIndex);
const responseVal = !common.selectedNode ? '' : (common.selectedNode.ln_node ? common.selectedNode.ln_node : common.selectedNode.ln_server_url);
const responseVal = common.selectedNode && common.selectedNode.ln_node ? common.selectedNode.ln_node : '';
logger.info({fileName: 'RTLConf', msg: 'Selected Node Updated To: ' + JSON.stringify(responseVal)});
res.status(200).json({status: 'Selected Node Updated To: ' + JSON.stringify(responseVal) + '!'});
};
exports.getRTLConfig = (req, res, next) => {
if(!common.multi_node_setup) {
var RTLConfFile = common.rtl_conf_file_path + '/RTL.conf';
logger.info({fileName: 'RTLConf', msg: 'Getting RTL Config'});
fs.readFile(RTLConfFile, 'utf8', function(err, data) {
if (err) {
logger.error({fileName: 'RTLConf', lineNum: 19, msg: 'Getting RTL Config Failed!'});
var confFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
logger.info({fileName: 'RTLConf', msg: 'Getting Node Config'});
fs.readFile(confFile, 'utf8', function(err, data) {
if (err) {
if (err.code === 'ENOENT') {
logger.error({fileName: 'RTLConf', lineNum: 46, msg: 'Node config does not exist!'});
res.status(200).json({ defaultNodeIndex: 0, selectedNodeIndex: 0, sso: {}, nodes: [] });
} else {
logger.error({fileName: 'RTLConf', lineNum: 49, msg: 'Getting Node Config Failed!'});
res.status(500).json({
message: "Reading RTL Config Failed!",
message: "Reading Node Config Failed!",
error: err
});
} else {
const jsonConfig = ini.parse(data);
const sso = { rtlSSO: common.rtl_sso, logoutRedirectLink: common.logout_redirect_link };
const authentication = {
nodeAuthType: common.node_auth_type,
configPath: common.nodes[0].config_path,
bitcoindConfigPath: common.nodes[0].bitcoind_config_path
};
jsonConfig.Settings.lnImplementation = (common.nodes[0].ln_implementation) ? common.nodes[0].ln_implementation : (jsonConfig.Settings.lnImplementation ? jsonConfig.Settings.lnImplementation : 'LND');
jsonConfig.Settings.channelBackupPath = common.nodes[0].channel_backup_path ? common.nodes[0].channel_backup_path : (jsonConfig.Settings.channelBackupPath) ? jsonConfig.Settings.channelBackupPath : common.nodes[0].channel_backup_path;
jsonConfig.Settings.flgSidenavOpened = (jsonConfig.Settings.flgSidenavOpened) ? jsonConfig.Settings.flgSidenavOpened : true;
jsonConfig.Settings.flgSidenavPinned = (jsonConfig.Settings.flgSidenavPinned) ? jsonConfig.Settings.flgSidenavPinned : true;
jsonConfig.Settings.menu = (jsonConfig.Settings.menu) ? jsonConfig.Settings.menu : 'VERTICAL';
jsonConfig.Settings.menuType = (jsonConfig.Settings.menuType) ? jsonConfig.Settings.menuType : 'REGULAR';
jsonConfig.Settings.fontSize = (jsonConfig.Settings.fontSize) ? jsonConfig.Settings.fontSize : 'MEDIUM';
jsonConfig.Settings.themeMode = (jsonConfig.Settings.themeMode) ? jsonConfig.Settings.themeMode : 'DAY';
jsonConfig.Settings.themeColor = (jsonConfig.Settings.themeColor) ? jsonConfig.Settings.themeColor : 'PURPLE';
jsonConfig.Settings.satsToBTC = (jsonConfig.Settings.satsToBTC) ? jsonConfig.Settings.satsToBTC : false;
res.status(200).json({ defaultNodeIndex: 0, selectedNodeIndex: common.selectedNode.index, sso: sso, nodes: [{
index: common.nodes[0].index,
lnNode: 'SingleNode',
lnImplementation: jsonConfig.Settings.lnImplementation,
settings: jsonConfig.Settings,
authentication: authentication}] });
}
});
} else {
var RTLMultiNodeConfFile = common.rtl_conf_file_path + '/RTL-Multi-Node-Conf.json';
logger.info({fileName: 'RTLConf', msg: 'Getting Multi Node Config'});
fs.readFile(RTLMultiNodeConfFile, 'utf8', function(err, data) {
if (err) {
if (err.code === 'ENOENT') {
logger.error({fileName: 'RTLConf', lineNum: 46, msg: 'Multi Node Config does not exist!'});
res.status(200).json({ defaultNodeIndex: 0, selectedNodeIndex: 0, sso: {}, nodes: [] });
} else {
logger.error({fileName: 'RTLConf', lineNum: 49, msg: 'Getting Multi Node Config Failed!'});
res.status(500).json({
message: "Reading Multi Node Config Failed!",
error: err
});
}
} else {
const multiNodeConfig = JSON.parse(data);
const sso = { rtlSSO: common.rtl_sso, logoutRedirectLink: common.logout_redirect_link };
var nodesArr = [];
if (multiNodeConfig.nodes && multiNodeConfig.nodes.length > 0) {
multiNodeConfig.nodes.forEach((node, i) => {
const authentication = {};
authentication.nodeAuthType = 'CUSTOM';
if(node.Authentication && node.Authentication.lndConfigPath) {
authentication.configPath = node.Authentication.lndConfigPath;
} else if(node.Authentication && node.Authentication.configPath) {
authentication.configPath = node.Authentication.configPath;
} else {
authentication.configPath = '';
}
if(node.Settings.bitcoindConfigPath) {
authentication.bitcoindConfigPath = node.Settings.bitcoindConfigPath;
}
node.Settings.channelBackupPath = (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : common.nodes[i].channel_backup_path;
node.Settings.flgSidenavOpened = (node.Settings.flgSidenavOpened) ? node.Settings.flgSidenavOpened : true;
node.Settings.flgSidenavPinned = (node.Settings.flgSidenavPinned) ? node.Settings.flgSidenavPinned : true;
node.Settings.menu = (node.Settings.menu) ? node.Settings.menu : 'VERTICAL';
node.Settings.menuType = (node.Settings.menuType) ? node.Settings.menuType : 'REGULAR';
node.Settings.fontSize = (node.Settings.fontSize) ? node.Settings.fontSize : 'MEDIUM';
node.Settings.themeMode = (node.Settings.themeMode) ? node.Settings.themeMode : 'DAY';
node.Settings.themeColor = (node.Settings.themeColor) ? node.Settings.themeColor : 'PURPLE';
node.Settings.satsToBTC = (node.Settings.satsToBTC) ? node.Settings.satsToBTC : false;
nodesArr.push({
index: node.index,
lnNode: node.lnNode,
lnImplementation: node.lnImplementation,
settings: node.Settings,
authentication: authentication})
});
}
res.status(200).json({ defaultNodeIndex: multiNodeConfig.defaultNodeIndex, selectedNodeIndex: common.selectedNode.index, sso: sso, nodes: nodesArr });
} else {
const nodeConfData = JSON.parse(data);
const sso = { rtlSSO: common.rtl_sso, logoutRedirectLink: common.logout_redirect_link };
var nodesArr = [];
if (common.nodes && common.nodes.length > 0) {
common.nodes.forEach((node, i) => {
const authentication = {};
if(node.config_path) {
authentication.configPath = node.config_path;
} else {
authentication.configPath = '';
}
const settings = {};
settings.userPersona = node.user_persona ? node.user_persona : 'MERCHANT';
settings.themeMode = (node.theme_mode) ? node.theme_mode : 'DAY';
settings.themeColor = (node.theme_color) ? node.theme_color : 'PURPLE';
settings.fiatConversion = (node.fiat_conversion) ? !!node.fiat_conversion : false;
settings.bitcoindConfigPath = node.bitcoind_config_path;
settings.enableLogging = node.enable_logging ? !!node.enable_logging : false;
settings.lnServerUrl = node.ln_server_url;
settings.channelBackupPath = node.channel_backup_path;
settings.currencyUnit = node.currency_unit;
nodesArr.push({
index: node.index,
lnNode: node.ln_node,
lnImplementation: node.ln_implementation,
settings: settings,
authentication: authentication})
});
}
});
}
res.status(200).json({ defaultNodeIndex: nodeConfData.defaultNodeIndex, selectedNodeIndex: common.selectedNode.index, sso: sso, nodes: nodesArr });
}
});
};
exports.updateUISettings = (req, res, next) => {
var RTLConfFile = '';
if(common.multi_node_setup) {
RTLConfFile = common.rtl_conf_file_path + '/RTL-Multi-Node-Conf.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.nodes.find(node => {
if(node.index == common.selectedNode.index) {
node.Settings.userPersona = req.body.updatedSettings.userPersona;
node.Settings.themeMode = req.body.updatedSettings.themeMode;
node.Settings.themeColor = req.body.updatedSettings.themeColor;
node.Settings.fiatConversion = req.body.updatedSettings.fiatConversion;
if(req.body.updatedSettings.fiatConversion) {
node.Settings.currencyUnit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
} else {
delete node.Settings.currencyUnit;
}
node.Settings.flgSidenavOpened = true; // req.body.updatedSettings.flgSidenavOpened;
node.Settings.flgSidenavPinned = true; // req.body.updatedSettings.flgSidenavPinned;
node.Settings.menu = 'VERTICAL'; // req.body.updatedSettings.menu;
node.Settings.menuType = 'REGULAR'; // req.body.updatedSettings.menuType;
node.Settings.fontSize = 'MEDIUM'; // req.body.updatedSettings.fontSize;
node.Settings.satsToBTC = false; // req.body.updatedSettings.satsToBTC;
var RTLConfFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.nodes.find(node => {
if(node.index == common.selectedNode.index) {
node.Settings.userPersona = req.body.updatedSettings.userPersona;
node.Settings.themeMode = req.body.updatedSettings.themeMode;
node.Settings.themeColor = req.body.updatedSettings.themeColor;
node.Settings.fiatConversion = req.body.updatedSettings.fiatConversion;
if(req.body.updatedSettings.fiatConversion) {
node.Settings.currencyUnit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
} else {
delete node.Settings.currencyUnit;
}
});
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
logger.info({fileName: 'RTLConf', msg: 'Updating Application Node Settings Succesful!'});
res.status(201).json({message: 'Application Node Settings Updated Successfully'});
}
catch (err) {
logger.error({fileName: 'Conf', lineNum: 102, msg: 'Updating Application Node Settings Failed!'});
res.status(500).json({
message: "Updating Application Node Settings Failed!",
error: 'Updating Application Node Settings Failed!'
});
}
} else {
RTLConfFile = common.rtl_conf_file_path + '/RTL.conf';
var config = ini.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
const settingsTemp = config.Settings;
settingsTemp.userPersona = req.body.updatedSettings.userPersona;
settingsTemp.themeMode = req.body.updatedSettings.themeMode;
settingsTemp.themeColor = req.body.updatedSettings.themeColor;
settingsTemp.fiatConversion = req.body.updatedSettings.fiatConversion;
if(req.body.updatedSettings.fiatConversion) {
settingsTemp.currencyUnit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
}
settingsTemp.flgSidenavOpened = true; // req.body.updatedSettings.flgSidenavOpened;
settingsTemp.flgSidenavPinned = true; // req.body.updatedSettings.flgSidenavPinned;
settingsTemp.menu = 'VERTICAL'; // req.body.updatedSettings.menu;
settingsTemp.menuType = 'REGULAR'; // req.body.updatedSettings.menuType;
settingsTemp.fontSize = 'MEDIUM'; // req.body.updatedSettings.fontSize;
settingsTemp.satsToBTC = false; // req.body.updatedSettings.satsToBTC;
delete config.Settings;
fs.writeFileSync(RTLConfFile, ini.stringify(config));
fs.appendFile(RTLConfFile, ini.stringify(settingsTemp, { section: 'Settings' }), function(err) {
if (err) {
logger.error({fileName: 'Conf', lineNum: 122, msg:'Updating Application Node Settings Failed!'});
res.status(500).json({
message: "Updating Application Node Settings Failed!",
error: 'Updating Application Node Settings Failed!'
});
const selectedNode = common.findNode(common.selectedNode.index);
selectedNode.user_persona = req.body.updatedSettings.userPersona;
selectedNode.theme_mode = req.body.updatedSettings.themeMode;
selectedNode.theme_color = req.body.updatedSettings.themeColor;
selectedNode.fiat_conversion = req.body.updatedSettings.fiatConversion;
if(req.body.updatedSettings.fiatConversion) {
selectedNode.currency_unit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
} else {
logger.info({fileName: 'RTLConf', msg: 'Updating Application Node Settings Succesful!'});
res.status(201).json({message: 'Application Node Settings Updated Successfully'});
delete selectedNode.currency_unit;
}
common.replaceNode(common.selectedNode.index, selectedNode);
}
});
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
logger.info({fileName: 'RTLConf', msg: 'Updating Application Node Settings Succesful!'});
res.status(201).json({message: 'Application Node Settings Updated Successfully'});
}
catch (err) {
logger.error({fileName: 'Conf', lineNum: 102, msg: 'Updating Application Node Settings Failed!'});
res.status(500).json({
message: "Updating Application Node Settings Failed!",
error: 'Updating Application Node Settings Failed!'
});
}
};
exports.updateDefaultNode = (req, res, next) => {
RTLConfFile = common.rtl_conf_file_path + '/RTL-Multi-Node-Conf.json';
RTLConfFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.defaultNodeIndex = req.body.defaultNodeIndex;
try {
@ -207,8 +135,8 @@ exports.getConfig = (req, res, next) => {
confFile = common.selectedNode.bitcoind_config_path;
break;
case 'rtl':
JSONFormat = (common.multi_node_setup) ? true : false;
confFile = (common.multi_node_setup) ? common.rtl_conf_file_path + '/RTL-Multi-Node-Conf.json' : common.rtl_conf_file_path + '/RTL.conf';
JSONFormat = true;
confFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
break;
default:
JSONFormat = false;
@ -225,9 +153,6 @@ exports.getConfig = (req, res, next) => {
});
} else {
const jsonConfig = (JSONFormat) ? JSON.parse(data) : ini.parse(data);
if (jsonConfig.Authentication && jsonConfig.Authentication.rtlPass) {
jsonConfig.Authentication.rtlPass = jsonConfig.Authentication.rtlPass.replace(/./g, '*');
}
if (jsonConfig.Bitcoind && jsonConfig.Bitcoind['bitcoind.rpcpass']) {
jsonConfig.Bitcoind['bitcoind.rpcpass'] = jsonConfig.Bitcoind['bitcoind.rpcpass'].replace(/./g, '*');
}

@ -1,13 +1,8 @@
var ini = require('ini');
var fs = require('fs');
var common = require('../common');
var connect = require('../connect');
const jwt = require("jsonwebtoken");
var upperCase = require('upper-case');
var crypto = require('crypto');
var hash = crypto.createHash('sha256');
var logger = require('./logger');
var rpcPass = '';
exports.authenticateUser = (req, res, next) => {
if(+common.rtl_sso) {
@ -16,7 +11,7 @@ exports.authenticateUser = (req, res, next) => {
} else if (req.body.authenticateWith === 'PASSWORD' && crypto.createHash('sha256').update(common.cookie).digest('hex') === req.body.authenticationValue) {
connect.refreshCookie(common.rtl_cookie_path);
const token = jwt.sign(
{ user: 'Custom_User', configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
{ user: 'SSO_USER', configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
@ -29,76 +24,46 @@ exports.authenticateUser = (req, res, next) => {
}
} else {
const password = req.body.authenticationValue;
if (common.multi_node_setup) {
if (common.rtl_pass === password) {
var rpcUser = 'Multi_Node_User';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
} else {
logger.error({fileName: 'Authenticate', lineNum: 38, msg: 'Password Validation Failed!'});
res.status(401).json({
message: "Authentication Failed!",
error: "Password Validation Failed!"
});
}
if (common.rtl_pass === password) {
var rpcUser = 'NODE_USER';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
} else {
if(upperCase(common.node_auth_type) === 'CUSTOM') {
if (common.rtl_pass === password) {
var rpcUser = 'Single_Node_User';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
} else {
logger.error({fileName: 'Authenticate', lineNum: 54, msg: 'Password Validation Failed!'});
res.status(401).json({
message: "Authentication Failed!",
error: "Password Validation Failed!"
});
}
} else {
fs.readFile(common.nodes[0].config_path, 'utf8', function (err, data) {
if (err) {
logger.error({fileName: 'Authenticate', lineNum: 60, msg: 'LND Config Reading Failed!'});
err.description = 'You might be connecting RTL remotely to your LND node OR You might be missing rpcpass in your lnd.conf.';
err.description = err.description + ' If the former modify the RTL.conf for remote setting.';
err.description = err.description + ' If the later modify the lnd.conf to include rpcpass';
res.status(500).json({
message: "LND Config Reading Failed!",
error: err
});
} else {
const jsonLNDConfig = ini.parse(data);
if (rpcPass === '') {
if (undefined !== jsonLNDConfig.Bitcoind && undefined !== jsonLNDConfig.Bitcoind['bitcoind.rpcpass']) {
rpcPass = jsonLNDConfig.Bitcoind['bitcoind.rpcpass'];
} else if (undefined !== jsonLNDConfig['bitcoind.rpcpass']) {
rpcPass = jsonLNDConfig['bitcoind.rpcpass'];
}
rpcPass = hash.update(rpcPass).digest('hex');
}
if (rpcPass === password) {
var rpcUser = (undefined !== jsonLNDConfig.Bitcoind && undefined !== jsonLNDConfig.Bitcoind['bitcoind.rpcuser']) ? jsonLNDConfig.Bitcoind['bitcoind.rpcuser'] : '';
rpcUser = (rpcUser === '' && undefined !== jsonLNDConfig['bitcoind.rpcuser']) ? jsonLNDConfig['bitcoind.rpcuser'] : '';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
} else {
logger.error({fileName: 'Authenticate', lineNum: 89, msg: 'Password Validation Failed!'});
res.status(401).json({
message: "Authentication Failed!",
error: "Password Validation Failed!"
});
}
}
});
}
logger.error({fileName: 'Authenticate', lineNum: 38, msg: 'Password Validation Failed!'});
res.status(401).json({
message: "Authentication Failed!",
error: "Password Validation Failed!"
});
}
}
};
exports.resetPassword = (req, res, next) => {
if(+common.rtl_sso) {
logger.error({fileName: 'Authenticate', lineNum: 46, msg: 'Password Reset Failed!'});
res.status(402).json({
message: "Password Reset Failure!",
error: "Password cannot be reset for SSO authentication!"
});
} else {
const oldPassword = req.body.oldPassword;
if (common.rtl_pass === oldPassword) {
common.rtl_pass = connect.replacePasswordWithHash(req.body.newPassword);
var rpcUser = 'NODE_USER';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
} else {
logger.error({fileName: 'Authenticate', lineNum: 62, msg: 'Password Reset Failed!'});
res.status(402).json({
message: "Password Reset Failed!",
error: "Old password is not correct!"
});
}
}
};

@ -1,18 +1,13 @@
var request = require('request-promise');
var common = require('../../common');
var logger = require('../logger');
var connect = require('../../connect');
var options = {};
exports.getInfo = (req, res, next) => {
common.setOptions();
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/getinfo';
if(common.multi_node_setup) {
logger.info({fileName:'GetInfo', msg: 'Selected Node: ' + JSON.stringify(common.selectedNode.ln_node)});
} else {
logger.info({fileName:'GetInfo', msg: 'Single Node Setup!'});
}
logger.info({fileName:'GetInfo', msg: 'Selected Node: ' + JSON.stringify(common.selectedNode.ln_node)});
logger.info({fileName: 'GetInfo', msg: 'Calling getinfo from c-lightning server url: ' + options.url});
request(options).then((body) => {
logger.info({fileName: 'GetInfo', msg: JSON.stringify(body)});

@ -8,13 +8,9 @@ exports.getInfo = (req, res, next) => {
common.setOptions();
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/getinfo';
if(common.multi_node_setup) {
logger.info({fileName:'GetInfo', msg: 'Selected Node: ' + JSON.stringify(common.selectedNode.ln_node)});
} else {
logger.info({fileName:'GetInfo', msg: 'Single Node Setup!'});
}
logger.info({fileName:'GetInfo', msg: 'Selected Node: ' + JSON.stringify(common.selectedNode.ln_node)});
if (!options.headers || !options.headers['Grpc-Metadata-macaroon']) {
logger.error({fileName: 'GetInfo', lineNum: 17, msg: 'Get info failed due to bad or missing macaroon!'});
logger.error({fileName: 'GetInfo', lineNum: 17, msg: 'LND Get info failed due to bad or missing macaroon!'});
res.status(502).json({
message: "Fetching Info Failed!",
error: "Bad Macaroon"

@ -69,7 +69,7 @@ services:
rtl:
container_name: ${COMPOSE_PROJECT_NAME}_rtl
image: shahanafarooqui/rtl:0.2.16
image: shahanafarooqui/rtl:0.6.4
restart: unless-stopped
depends_on:
- lnd
@ -80,7 +80,6 @@ services:
environment:
PORT: ${RTL_PORT}
MACAROON_PATH: /shared
LND_SERVER_URL: https://${LIGHTNING_HOST}:${LIGHTNING_REST_PORT}/v1
LND_CONFIG_PATH: ''
NODE_AUTH_TYPE: ${RTL_NODE_AUTH_TYPE}
LN_SERVER_URL: https://${LIGHTNING_HOST}:${LIGHTNING_REST_PORT}/v1
CONFIG_PATH: ''
RTL_PASS: ${RTL_PASS}

@ -1,68 +1,52 @@
RTL allows the user to configure and control specific application parameters for app customization and integration.
The parameters can be configured via RTL.conf file or through environment variables defined at the OS level.
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.
#### RTL.conf
[Authentication]
;Path for the folder containing 'admin.macaroon' file
macaroonPath=<>
;For stand alone RTL authentication. Allowed values - CUSTOM, DEFAULT
nodeAuthType=<>
;Full path of the lnd.conf file including the file name
lndConfigPath=<>
;For 'nodeAuthType=CUSTOM', the password in plain text
rtlPass=<>
[Settings]
;Set by RTL
userPersona=OPERATOR
;Set by RTL
themeMode=DAY
;Set by RTL
themeColor=PURPLE
;Full path of the bitcoin.conf file including the file name
bitcoindConfigPath=<>
;parameter to turn RTL logging off/on. Allowed values - true, false
enableLogging=<>
;port number for the rtl node server, default 3000
port=3000
;<LND server URL for REST APIs.
;Default is 'https://localhost:8080/v1'
lndServerUrl=https://localhost:8080/v1
;Channel backup folder
channelBackupPath=<>
;Set by RTL
fiatConversion=false
;Set by RTL, dafault 'USD' If fiatConversion is true
currencyUnit=USD
[SSO]
;Single Sign On control
;Allowed values - 1,0
;1-single sign on via an external cookie
;0-stand alone RTL authentication
rtlSSO=0
;Required if 'rtlSSO=1'
;Full path of the cookie file including the file name
;The application url needs to pass the value from this cookie file as query param 'access-key'
;for the SSO authentication to work
rtlCookiePath=<>
;Required if 'rtlSSO=1'
;URL to re-direct to after logout/timeout from RTL
logoutRedirectLink=/login
#### RTL-Config.json
{
"multiPass": "<The password in plain text, default 'password', Required>",
"port": "<port number for the rtl node server, default '3000', Required>",
"defaultNodeIndex": <Default index to load when rtl server starts, default 1, Optional>,
"SSO": {
"rtlSSO": <parameter to turn SSO off/on. Allowed values - 1 (single sign on via an external cookie), 0 (stand alone RTL authentication), default 0, Required>,
"rtlCookiePath": "<Full path of the cookie file including the file name. The application url needs to pass the value from this cookie file as query param 'access-key' for the SSO authentication to work, Required if SSO=1 else empty (Optional)>",
"logoutRedirectLink": "<URL to re-direct to after logout/timeout from RTL, Required if SSO=1 else empty (Optional)>"
},
"nodes": [
{
"index": <Incremental node indices starting from 1, Required>,
"lnNode": "<Node name to uniquely identify the node in the UI, Default 'Node 1', Required>",
"lnImplementation": "<LNP implementation, Allowed values LND/CLT. Default 'LND', Required>",
"Authentication": {
"macaroonPath": "<Path for the folder containing 'admin.macaroon' file, Required>",
"configPath": "<Optional:Full path of the lnd.conf file including the file name, if present locally or empty, Optional>"
},
"Settings": {
"userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT, OPERATOR. Default MERCHANT, Required>",
"themeMode": "<Theme modes, Allowed values DAY, NIGHT. Default DAY, Required>",
"themeColor": "<Theme colors, Allowed values PURPLE, TEAL, INDIGO, PINK. Default PURPLE, Required>",
"channelBackupPath": "<Path to save channel backup file. Only for LND implementation, Default <RTL root>\backup\node-1, Optional>",
"bitcoindConfigPath": "<Path of bitcoind.conf path if available locally>",
"enableLogging": <Parameter to turn RTL logging off/on. Allowed values - true, false, default false, Required>,
"fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Required>,
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD' If fiatConversion is true, Required if fiatConversion is true>",
"lnServerUrl": "<Service url for LND/CLightning REST APIs for the node, e.g. https://192.168.0.1:8080/v1 OR https://192.168.0.1:3001/v1. Default 'https://localhost:8080/v1', Required"
}
}
]
}
#### Environment variables
;The environment variable can also be used for all of the above configurations except the UI settings.
;If the environment variables are set, it will take precedence over the parameters in the RTL.conf file.
PORT (port number for the rtl node server, default 3000)
NODE_AUTH_TYPE (For stand alone RTL authentication allowed values - CUSTOM, DEFAULT)
RTL_PASS (Password for RTL custom authentication)
LN_IMPLEMENTATION (LND, CLT. Default 'LND')
LND_SERVER_URL (LND server URL for REST APIs, default https://localhost:8080/v1) OR LN_SERVER_URL (LN server URL for LNP REST APIs)
LND_CONFIG_PATH (Full path of the lnd.conf file including the file name) OR CONFIG_PATH (Full path of the LNP .conf file including the file name)
MACAROON_PATH (Path for the folder containing 'admin.macaroon' file)
RTL_SSO (1 - single sign on via an external cookie, 0 - stand alone RTL authentication)
RTL_COOKIE_PATH (Full path of the cookie file including the file name)
LOGOUT_REDIRECT_LINK (URL to re-direct to after logout/timeout from RTL)
RTL_CONFIG_PATH (Full path of the RTL.conf file including the file name)
BITCOIND_CONFIG_PATH (Full path of the bitcoind.conf file including the file name)
CHANNEL_BACKUP_PATH (folder location for saving the channel backup files, valid for LND implementation only)
;If the environment variables are set, it will take precedence over the parameters in the RTL-Config.json file.
PORT (port number for the rtl node server, default 3000, Required)
LN_IMPLEMENTATION (LND, CLT. Default 'LND', Required)
LN_SERVER_URL (LND server URL for REST APIs, default https://localhost:8080/v1) OR LN_SERVER_URL (LN server URL for LNP REST APIs) (Required)
CONFIG_PATH (Full path of the lnd.conf file including the file name) OR CONFIG_PATH (Full path of the LNP .conf file including the file name) (Optional)
MACAROON_PATH (Path for the folder containing 'admin.macaroon' file, Required)
RTL_SSO (1 - single sign on via an external cookie, 0 - stand alone RTL authentication, Optional)
RTL_COOKIE_PATH (Full path of the cookie file including the file name, Required if RTL_SSO=1 else Optional)
LOGOUT_REDIRECT_LINK (URL to re-direct to after logout/timeout from RTL, Required if RTL_SSO=1 else Optional)
RTL_CONFIG_PATH (Path for the folder containing 'RTL-Config.json' file, Required)
BITCOIND_CONFIG_PATH (Full path of the bitcoind.conf file including the file name, Optional)
CHANNEL_BACKUP_PATH (folder location for saving the channel backup files, valid for LND implementation only, Required if lnImplementation=LND else Optional)

@ -45,8 +45,8 @@ $ git pull
$ npm install --only=prod
```
### <a name="prep"></a>Prep for Execution
RTL requires its own config file `RTL-Multi-Node-Conf.json`, to start the server and provide user authentication on the app.
* Rename `sample-RTL-Multi-Node-Conf.json` file to `RTL-Multi-Node-Conf.json`.
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
* Rename `sample-RTL-Config.json` file to `RTL-Config.json`.
* Locate the complete path of the readable `access.macaroon` from `cl-rest` on your node.
* Modify the RTL conf file per the example file below
@ -84,7 +84,7 @@ Ensure that the follow values are correct per your config:
"lnServerUrl": "https://<cl-rest api server ip address>:3001/v1"
}
}
],
],
"multiPass": <password required for accessing RTL>
}
```

@ -4,7 +4,7 @@ Static Channel Backup APIs of LND has been leveraged to provide channel backup f
#### Backup folder location
Default location: If no folder location is specified in the RTL conf files (single or mult-node setup), RTL will create a folder `backup` on the RTL root. If multiple nodes are being managed via RTL, multiple node sub-folders will be created in the `backup` folder.
User defined: User can specify the folder where channel backups should be created, by setting a config variable `channelBackupPath` in the `RTL.conf` or `RTL-Multi-Node-Conf.json` files. Please ensure that RTL has the permission to write in the specified folder location.
User defined: User can specify the folder where channel backups should be created, by setting a config variable `channelBackupPath` in the `RTL-Config.json` file. Please ensure that RTL has the permission to write in the specified folder location.
Environment variable: Channel backup folder location can also be controlled via an environment variable `CHANNEL_BACKUP_PATH`

@ -4,7 +4,7 @@ Caution: This feature is for advanced users, running multiple nodes.
A single server instance of RTL can now be used to connect with multiple nodes on the same network. Multi-Node configuration requires the following:
1. In case of LND node implementation, update lnd.conf for the node to enable remote connections and restart LND
2. Configure 'RTL-Multi-Node-Conf.json' with individual entries for each node
2. Configure 'RTL-Config.json' with individual entries for each node
3. Restart RTL
4. Run RTL and switch nodes live via dropdown on the menubar
@ -15,18 +15,16 @@ This step is only required to configure the nodes, which will be remotely connec
3. Add this setting your lnd.conf file under the [Application Options] section: `restlisten=<ip address of the device running LND>:8080`
4. Restart LND
#### 2. Configure 'RTL-Multi-Node-Conf.json'
1. Rename the `sample-RTL-Multi-Node-Conf.json` on the root RTL location to `RTL-Multi-Node-Conf.json`
#### 2. Configure 'RTL-Config.json'
1. Rename the `sample-RTL-Config.json` on the root RTL location to `RTL-Config.json`
2. Set `multiPass` to the preferred password. This password will be used to authenticate the user for RTL. Once authenticated, the user will be able to access all the nodes configured in the json file
3. Set the `port` to the preferred port number over which to run RTL
4. Set the `defaultNodeIndex` to configure the default start up node at server restart
5. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
6. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lndServerUrl`/`lnServerUrl`).
6. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`).
7. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server.
8. `lndServerUrl` must be set to the service url for LND REST APIs for each node, with the unique ip address of the node hosting lnd e.g. https://192.168.0.1:8080/v1. In this case the ip address of the node hosting lnd is '192.168.0.1'
OR
`lnServerUrl` must be set to the service url for C Lightining REST APIs for each node, with the unique ip address of the node hosting clightning e.g. https://192.168.0.2:3001/v1. In this case the ip address of the node hosting clightning is '192.168.0.2'
9. `lndConfigPath`(for LND)/`configPath`(for CLT) and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
8. `lnServerUrl` must be set to the service url for LND/C Lightining REST APIs for each node, with the unique ip address of the node hosting lnd/clightning e.g. https://192.168.0.1:8080/v1 OR https://192.168.0.1:3001/v1. In this case the ip address of the node hosting lnd/clightning is '192.168.0.1'
9. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
#### 3. Restart RTL

@ -9,14 +9,39 @@ If your running RTL and LND on different devices on your local LAN, certain conf
2. `admin.macaroon` file must be transferred to the device on which you need to run RTL
3. Add to your lnd.conf file under the [Application Options] section: `restlisten=<ip address of the device running LND>:8080`
4. Restart LND
5. Make the following changes to the RTL.conf file
5. Make the following changes to the RTL-Config.json file
```
[Authentication]
macaroonPath=<Path of the folder containing 'admin.macaroon' on the device running RTL>
nodeAuthType=CUSTOM
rtlPass=<password in plain text>
[Settings]
lndServerUrl=https://<ip-address-of-device-running-lnd>:8080/v1
{
"multiPass": "<password in plain text, Default 'password'>",
"port": "3000",
"defaultNodeIndex": 1,
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
"logoutRedirectLink": ""
},
"nodes": [
{
"index": 1,
"lnNode": "LND Testnet",
"lnImplementation": "LND",
"Authentication": {
"macaroonPath": "<Path of the folder containing 'admin.macaroon' on the device running RTL>",
"configPath": "<Optional:Path of the lnd.conf if present locally or empty>"
},
"Settings": {
"userPersona": "OPERATOR",
"themeMode": "DAY",
"themeColor": "PURPLE",
"channelBackupPath": "<RTL Root path + \backup\node-1>",
"bitcoindConfigPath": "<Optional: path of bitcoind.conf path if available locally>",
"enableLogging": false,
"fiatConversion": false,
"lnServerUrl": "<https://<ip-address-of-device-running-lnd>:8080/v1; e.g. https://192.168.0.1:8080/v1>"
}
}
]
}
```
6. Restart RTL
7. Access RTL by opening your browser at the following address: http://localhost:3000

9
package-lock.json generated

@ -1,6 +1,6 @@
{
"name": "rtl",
"version": "0.6.3-beta",
"version": "0.6.4-beta",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -9750,7 +9750,8 @@
"minimist": {
"version": "0.0.10",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
"integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=",
"dev": true
},
"minipass": {
"version": "2.9.0",
@ -10489,6 +10490,7 @@
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
"dev": true,
"requires": {
"minimist": "~0.0.1",
"wordwrap": "~0.0.2"
@ -15826,7 +15828,8 @@
"wordwrap": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
"dev": true
},
"worker-farm": {
"version": "1.7.0",

@ -1,6 +1,6 @@
{
"name": "rtl",
"version": "0.6.3-beta",
"version": "0.6.4-beta",
"license": "MIT",
"scripts": {
"ng": "ng",
@ -43,7 +43,6 @@
"jsonwebtoken": "^8.5.1",
"material-design-icons": "^3.0.1",
"ngx-perfect-scrollbar": "^8.0.0",
"optimist": "^0.6.1",
"request": "^2.88.0",
"request-promise": "^4.2.5",
"roboto-fontface": "^0.10.0",

@ -3,5 +3,6 @@ const express = require("express");
const router = express.Router();
router.post("/", AuthenticateController.authenticateUser);
router.post("/reset", AuthenticateController.resetPassword);
module.exports = router;

@ -0,0 +1,30 @@
{
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
"logoutRedirectLink": ""
},
"nodes": [
{
"index": 1,
"lnNode": "Node 1",
"lnImplementation": "LND",
"Authentication": {
"macaroonPath": "C:\\Users\\shaha\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet",
"configPath": "C:\\Users\\shaha\\AppData\\Local\\Lnd\\lnd.conf"
},
"Settings": {
"userPersona": "MERCHANT",
"themeMode": "DAY",
"themeColor": "PURPLE",
"channelBackupPath": "C:\\Users\\shaha\\backup\\node-1",
"enableLogging": false,
"lnServerUrl": "https://localhost:8080/v1",
"fiatConversion": false
}
}
]
}

@ -1,50 +0,0 @@
{
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
"logoutRedirectLink": ""
},
"nodes": [
{
"index": 1,
"lnNode": "LND Testnet",
"lnImplementation": "LND",
"Authentication": {
"macaroonPath": "<Complete path of the folder containing admin.macaroon for the node # 1>",
"lndConfigPath": "<Optional:Path of the lnd.conf if present locally or empty>"
},
"Settings": {
"userPersona": "OPERATOR",
"themeMode": "DAY",
"themeColor": "PURPLE",
"channelBackupPath": "C:\\RTL\\backup\\node-1",
"bitcoindConfigPath": "<Optional: path of bitcoind.conf path if available locally>",
"enableLogging": true,
"currencyUnit": "USD",
"fiatConversion": true,
"lndServerUrl": "<Service url for LND REST APIs for node # 1 e.g. https://192.168.0.1:8080/v1"
}
},
{
"index": 2,
"lnNode": "C Lighting Testnet",
"lnImplementation": "CLT",
"Authentication": {
"macaroonPath": "<Complete path of the folder containing access.macaroon of CL REST for the node # 2>"
},
"Settings": {
"userPersona": "MERCHANT",
"themeMode": "NIGHT",
"themeColor": "TEAL",
"channelBackupPath": "C:\\RTL\\backup\\node-2",
"bitcoindConfigPath": "",
"enableLogging": true,
"fiatConversion": false,
"lnServerUrl": "<Service url for C Lightning REST APIs for node # 2 e.g. https://192.168.0.2:3001/v1"
}
}
]
}

@ -1,22 +0,0 @@
[Authentication]
macaroonPath=
nodeAuthType=DEFAULT
lndConfigPath=
rtlPass=
[Settings]
userPersona=OPERATOR
themeMode=DAY
themeColor=PURPLE
channelBackupPath=C:\RTL\backup
bitcoindConfigPath=
enableLogging=true
port=3000
lndServerUrl=https://localhost:8080/v1
currencyUnit=GBP
fiatConversion=true
[SSO]
rtlSSO=0
rtlCookiePath=
logoutRedirectLink=/login

@ -1,26 +1,13 @@
<!-- <div fxLayout="column" id="rtl-container" class="rtl-container" [ngClass]="[settings.themeColor, settings.themeMode]" [class.horizontal]="settings.menu === 'HORIZONTAL'" [class.compact]="settings.menuType === 'COMPACT'" [class.mini]="settings.menuType === 'MINI'" [style.fontSize.px]="getFontSize()"> -->
<div fxLayout="column" id="rtl-container" class="rtl-container" [ngClass]="[settings.themeColor | lowercase, settings.themeMode | lowercase, settings.fontSize | lowercase]" [class.horizontal]="settings.menu === 'HORIZONTAL'" [class.compact]="settings.menuType === 'COMPACT'" [class.mini]="settings.menuType === 'MINI'">
<mat-toolbar fxLayout="row" fxLayoutAlign="space-between center" class="padding-gap-x bg-primary rtl-top-toolbar" *ngIf="settings.menu === 'VERTICAL'">
<div fxLayout="column" id="rtl-container" class="rtl-container medium" [ngClass]="[settings.themeColor | lowercase, settings.themeMode | lowercase]">
<mat-toolbar fxLayout="row" fxLayoutAlign="space-between center" class="padding-gap-x bg-primary rtl-top-toolbar">
<div>
<button *ngIf="settings.menu === 'VERTICAL'" class="top-toolbar-icon" mat-icon-button (click)="sideNavToggle()">
<button class="top-toolbar-icon" mat-icon-button (click)="sideNavToggle()">
<mat-icon class="mr-5px">menu</mat-icon>
</button>
<button *ngIf="settings.fontSize === 'SMALL' && settings.menu === 'VERTICAL' && !smallScreen" mat-icon-button (click)="settings.flgSidenavPinned = !settings.flgSidenavPinned">
<svg class="top-toolbar-icon icon-pinned" viewBox="0 0 42 42">
<path fill="currentColor" *ngIf="!settings.flgSidenavPinned" d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
<path fill="currentColor" *ngIf="settings.flgSidenavPinned" d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z" />
</svg>
</button>
<button *ngIf="settings.fontSize === 'MEDIUM' && settings.menu === 'VERTICAL' && !smallScreen" mat-icon-button (click)="settings.flgSidenavPinned = !settings.flgSidenavPinned">
<button *ngIf="!smallScreen" mat-icon-button (click)="flgSidenavPinned = !flgSidenavPinned">
<svg class="top-toolbar-icon icon-pinned" viewBox="0 0 32 32">
<path fill="currentColor" *ngIf="!settings.flgSidenavPinned" d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
<path fill="currentColor" *ngIf="settings.flgSidenavPinned" d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z" />
</svg>
</button>
<button *ngIf="settings.fontSize === 'LARGE' && settings.menu === 'VERTICAL' && !smallScreen" mat-icon-button (click)="settings.flgSidenavPinned = !settings.flgSidenavPinned">
<svg class="top-toolbar-icon icon-pinned" viewBox="0 0 24 24">
<path fill="currentColor" *ngIf="!settings.flgSidenavPinned" d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
<path fill="currentColor" *ngIf="settings.flgSidenavPinned" d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z" />
<path fill="currentColor" *ngIf="!flgSidenavPinned" d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
<path fill="currentColor" *ngIf="flgSidenavPinned" d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z" />
</svg>
</button>
</div>
@ -32,11 +19,8 @@
<rtl-top-menu></rtl-top-menu>
</div>
</mat-toolbar>
<mat-toolbar color="primary" *ngIf="settings.menu === 'HORIZONTAL'" class="padding-gap-x horizontal-nav">
<rtl-horizontal-navigation fxLayout="row" fxFlex="100" fxLayoutAlign="start center" class="h-100"></rtl-horizontal-navigation>
</mat-toolbar>
<mat-sidenav-container>
<mat-sidenav perfectScrollbar *ngIf="settings.menu === 'VERTICAL'" [opened]="settings.flgSidenavOpened" [mode]="(settings.flgSidenavPinned && !smallScreen) ? 'side' : 'over'" #sideNavigation class="sidenav mat-elevation-z6">
<mat-sidenav perfectScrollbar [opened]="true" [mode]="(flgSidenavPinned && !smallScreen) ? 'side' : 'over'" #sideNavigation class="sidenav mat-elevation-z6">
<rtl-side-navigation (ChildNavClicked)="onNavigationClicked($event)" fxFlex="100"></rtl-side-navigation>
</mat-sidenav>
<mat-sidenav-content perfectScrollbar>

@ -1,5 +1,5 @@
import { Component, OnInit, AfterViewInit, OnDestroy, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Router, NavigationEnd } from '@angular/router';
import { Breakpoints, BreakpointObserver } from '@angular/cdk/layout';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
@ -12,7 +12,7 @@ import * as sha256 from 'sha256';
import { LoggerService } from './shared/services/logger.service';
import { CommonService } from './shared/services/common.service';
import { SessionService } from './shared/services/session.service';
import { AlertTypeEnum, ScreenSizeEnum, NODE_SETTINGS } from './shared/services/consts-enums-functions';
import { AlertTypeEnum, ScreenSizeEnum } from './shared/services/consts-enums-functions';
import { RTLConfiguration, Settings, ConfigSettingsNode, GetInfoRoot } from './shared/models/RTLconfig';
import * as RTLActions from './store/rtl.actions';
@ -34,12 +34,17 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
public accessKey = '';
public xSmallScreen = false;
public smallScreen = false;
public flgSidenavPinned = true;
unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private commonService: CommonService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions,
private userIdle: UserIdleService, private router: Router, private sessionService: SessionService, private breakpointObserver: BreakpointObserver) {}
ngOnInit() {
this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) { return; }
document.getElementsByTagName('mat-sidenav-content')[0].scrollTo(0, 0);
});
this.breakpointObserver.observe([Breakpoints.XSmall, Breakpoints.TabletPortrait, Breakpoints.Small, Breakpoints.Medium])
.pipe(takeUntil(this.unSubs[5]))
.subscribe((matches) => {
@ -83,7 +88,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
if (action.type === RTLActions.SET_RTL_CONFIG) {
if (!this.sessionService.getItem('token')) {
if (+action.payload.sso.rtlSSO) {
this.store.dispatch(new RTLActions.Signin(sha256(this.accessKey)));
this.store.dispatch(new RTLActions.Login({password: sha256(this.accessKey), initialPass: false}));
} else {
this.router.navigate([this.appConfig.sso.logoutRedirectLink]);
}
@ -91,11 +96,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
// START: Workaround to add adjust container width initially
this.sideNavigation.toggle();
setTimeout(() => { this.sideNavigation.toggle(); }, 500);
if (this.settings.menuType === 'COMPACT' || this.settings.menuType === 'MINI') {
this.sideNavigation.toggle(); // To dynamically update the width to 100% after side nav is closed
setTimeout(() => { this.sideNavigation.toggle(); }, 100);
}
// END: Workaround to add left margin to container initially
// END: Workaround to add left margin to container initially
}
});
this.userIdle.startWatching();
@ -108,7 +109,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
alertTitle: 'Logging out',
titleMessage: 'Time limit exceeded for session inactivity.'
}}));
this.store.dispatch(new RTLActions.Signout());
this.store.dispatch(new RTLActions.Logout());
this.userIdle.resetTimer();
}
});
@ -129,13 +130,12 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
}
ngAfterViewInit() {
if ((this.settings.menuType !== 'REGULAR' || !this.settings.flgSidenavPinned) || (this.smallScreen)) {
if (this.smallScreen) {
this.sideNavigation.close();
}
}
sideNavToggle() {
this.settings.flgSidenavOpened = !this.settings.flgSidenavOpened;
this.sideNavigation.toggle();
}
@ -151,11 +151,6 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
this.logger.info('Copied Text: ' + payload);
}
getFontSize() {
return (this.settings.fontSize === NODE_SETTINGS.fontSize[0].class) ? 14 :
(this.settings.fontSize === NODE_SETTINGS.fontSize[2].class) ? 18 : 16;
}
ngOnDestroy() {
this.unSubs.forEach(unsub => {
unsub.next();

@ -4,7 +4,7 @@ import { ModuleWithProviders } from '@angular/core';
import { SettingsComponent } from './shared/components/settings/settings.component';
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
import { HelpComponent } from './shared/components/help/help.component';
import { SigninComponent } from './shared/components/signin/signin.component';
import { LoginComponent } from './shared/components/login/login.component';
import { ErrorComponent } from './shared/components/error/error.component';
import { AuthGuard } from './shared/services/auth.guard';
@ -13,7 +13,7 @@ export const routes: Routes = [
{ path: 'cl', loadChildren: () => import('./clightning/cl.module').then(childModule => childModule.CLModule), canActivate: [AuthGuard] },
{ path: 'settings', component: SettingsComponent, canActivate: [AuthGuard] },
{ path: 'help', component: HelpComponent },
{ path: 'login', component: SigninComponent },
{ path: 'login', component: LoginComponent },
{ path: 'error', component: ErrorComponent },
{ path: '**', component: NotFoundComponent }
];

@ -736,8 +736,8 @@ export class CLEffects implements OnDestroy {
handleErrorWithoutAlert(actionName: string, err: { status: number, error: any }) {
this.logger.error('ERROR IN: ' + actionName + '\n' + JSON.stringify(err));
if (err.status === 401) {
this.logger.info('Redirecting to Signin');
this.store.dispatch(new RTLActions.Signout());
this.logger.info('Redirecting to Login');
this.store.dispatch(new RTLActions.Logout());
} else {
this.store.dispatch(new RTLActions.EffectErrorCl({ action: actionName, code: err.status.toString(), message: err.error.error }));
}
@ -746,8 +746,8 @@ export class CLEffects implements OnDestroy {
handleErrorWithAlert(alerType: string, alertTitle: string, errURL: string, err: { status: number, error: any }) {
this.logger.error(err);
if (err.status === 401) {
this.logger.info('Redirecting to Signin');
this.store.dispatch(new RTLActions.Signout());
this.logger.info('Redirecting to Login');
this.store.dispatch(new RTLActions.Logout());
} else {
this.store.dispatch(new RTLActions.CloseSpinner());
this.store.dispatch(new RTLActions.OpenAlert({

@ -23,7 +23,7 @@ export interface CLState {
export const initCLState: CLState = {
effectErrorsCl: [],
nodeSettings: { userPersona: UserPersonaEnum.OPERATOR, selCurrencyUnit: 'USD', fiatConversion: false, channelBackupPath: '', satsToBTC: false, currencyUnits: [] },
nodeSettings: { userPersona: UserPersonaEnum.OPERATOR, selCurrencyUnit: 'USD', fiatConversion: false, channelBackupPath: '', currencyUnits: [] },
information: {},
fees: {},
feeRatesPerKB: {},

@ -1115,8 +1115,8 @@ export class LNDEffects implements OnDestroy {
handleErrorWithoutAlert(actionName: string, err: { status: number, error: any }) {
this.logger.error('ERROR IN: ' + actionName + '\n' + JSON.stringify(err));
if (err.status === 401) {
this.logger.info('Redirecting to Signin');
this.store.dispatch(new RTLActions.Signout());
this.logger.info('Redirecting to Login');
this.store.dispatch(new RTLActions.Logout());
} else {
this.store.dispatch(new RTLActions.EffectErrorLnd({ action: actionName, code: err.status.toString(), message: err.error.error }));
}
@ -1125,8 +1125,8 @@ export class LNDEffects implements OnDestroy {
handleErrorWithAlert(alertType: string, alertTitle: string, errURL: string, err: { status: number, error: any }) {
this.logger.error(err);
if (err.status === 401) {
this.logger.info('Redirecting to Signin');
this.store.dispatch(new RTLActions.Signout());
this.logger.info('Redirecting to Login');
this.store.dispatch(new RTLActions.Logout());
} else {
this.store.dispatch(new RTLActions.CloseSpinner());
this.store.dispatch(new RTLActions.OpenAlert({

@ -34,7 +34,7 @@ export interface LNDState {
export const initLNDState: LNDState = {
effectErrorsLnd: [],
nodeSettings: { userPersona: UserPersonaEnum.OPERATOR, fiatConversion: false, channelBackupPath: '', satsToBTC: false, currencyUnits: [] },
nodeSettings: { userPersona: UserPersonaEnum.OPERATOR, fiatConversion: false, channelBackupPath: '', currencyUnits: [] },
information: {},
peers: [],
fees: {},

@ -1,6 +1,6 @@
<div perfectScrollbar fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="space-between stretch">
<div fxFlex="30" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap-large" [ngClass]="{'display-none': showQRField === '' || screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<qrcode [qrdata]="showQRField" [size]="210" [level]="'L'" [allowEmptyString]="true" class="qr-border"></qrcode>
<qrcode [qrdata]="showQRField" [margin]="2" [width]="210" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div [fxFlex]="showQRField === '' || screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '100' : '70'" class="padding-gap-large pl-3">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
@ -12,7 +12,7 @@
<mat-card-content class="mt-5px mb-0 pr-2">
<div fxLayout="column">
<div fxFlex="50" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap-large mb-1" [ngClass]="{'display-none': showQRField === '' || (screenSize !== screenSizeEnum.XS && screenSize !== screenSizeEnum.SM)}">
<qrcode [qrdata]="showQRField" [size]="210" [level]="'L'" [allowEmptyString]="true" class="qr-border"></qrcode>
<qrcode [qrdata]="showQRField" [margin]="2" [width]="210" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxLayout="row" fxFlex="100">
<p *ngIf="data.titleMessage" fxLayoutAlign="start center" class="pb-1">{{data.titleMessage}}</p>

@ -1,6 +1,6 @@
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="space-between stretch">
<div fxFlex="35" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap-large" [ngClass]="{'display-none': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<qrcode [qrdata]="invoice.bolt11" [size]="qrWidth" [level]="'L'" [allowEmptyString]="true" [ngClass]="{'qr-border': screenSize !== screenSizeEnum.XS, 'qr-thin-border': screenSize === screenSizeEnum.XS}"></qrcode>
<qrcode [qrdata]="invoice.bolt11" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxFlex="65" class="padding-gap-large">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header mb-2">
@ -13,7 +13,7 @@
<mat-card-content [ngClass]="{'xs-scroll-y': screenSize === screenSizeEnum.XS}">
<div fxLayout="column">
<div fxFlex="30" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap" [ngClass]="{'display-none': screenSize !== screenSizeEnum.XS && screenSize !== screenSizeEnum.SM}">
<qrcode [qrdata]="invoice.bolt11" [size]="qrWidth" [level]="'L'" [allowEmptyString]="true" [ngClass]="{'qr-border': screenSize !== screenSizeEnum.XS, 'qr-thin-border': screenSize === screenSizeEnum.XS}"></qrcode>
<qrcode [qrdata]="invoice.bolt11" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxLayout="row" *ngIf="invoice.warning_capacity">
<div fxFlex="100" class="alert alert-warn">

@ -1,6 +1,6 @@
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="space-between stretch">
<div fxFlex="35" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap-large" [ngClass]="{'display-none': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<qrcode [qrdata]="invoice.payment_request" [size]="qrWidth" [level]="'L'" [allowEmptyString]="true" [ngClass]="{'qr-border': screenSize !== screenSizeEnum.XS, 'qr-thin-border': screenSize === screenSizeEnum.XS}"></qrcode>
<qrcode [qrdata]="invoice.payment_request" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxFlex="65" class="padding-gap-large">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header mb-2">
@ -13,7 +13,7 @@
<mat-card-content [ngClass]="{'xs-scroll-y': screenSize === screenSizeEnum.XS}">
<div fxLayout="column">
<div fxFlex="30" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap" [ngClass]="{'display-none': screenSize !== screenSizeEnum.XS && screenSize !== screenSizeEnum.SM}">
<qrcode [qrdata]="invoice.payment_request" [size]="qrWidth" [level]="'L'" [allowEmptyString]="true" [ngClass]="{'qr-border': screenSize !== screenSizeEnum.XS, 'qr-thin-border': screenSize === screenSizeEnum.XS}"></qrcode>
<qrcode [qrdata]="invoice.payment_request" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxLayout="row">
<div fxFlex="50">

@ -1,6 +1,6 @@
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="space-between stretch">
<div fxFlex="35" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap-large" [ngClass]="{'display-none': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<qrcode [qrdata]="address" [size]="qrWidth" [level]="'L'" [allowEmptyString]="true" class="qr-border"></qrcode>
<qrcode [qrdata]="address" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxFlex="65" class="padding-gap-large">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header mb-2">
@ -13,7 +13,7 @@
<mat-card-content>
<div fxLayout="column">
<div fxFlex="50" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap-large" [ngClass]="{'display-none': screenSize !== screenSizeEnum.XS && screenSize !== screenSizeEnum.SM}">
<qrcode [qrdata]="address" [size]="qrWidth" [level]="'L'" [allowEmptyString]="true" class="qr-border"></qrcode>
<qrcode [qrdata]="address" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxLayout="row">
<div fxFlex="100">

@ -1,6 +1,6 @@
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="space-between stretch">
<div fxFlex="30" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap-large" [ngClass]="{'display-none': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<qrcode qrdata="{{selInfoType.infoID === 1 ? information.uris[0] : information.identity_pubkey}}" [size]="qrWidth" [level]="'L'" [allowEmptyString]="true" class="qr-border"></qrcode>
<qrcode qrdata="{{selInfoType.infoID === 1 ? information.uris[0] : information.identity_pubkey}}" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxFlex="100" fxFlex.gt-sm="70" class="padding-gap-large pl-3">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header mb-2">
@ -13,7 +13,7 @@
<mat-card-content class="pr-2">
<div fxLayout="column">
<div fxFlex="50" fxLayoutAlign="center start" class="modal-qr-code-container padding-gap-large" [ngClass]="{'display-none': screenSize !== screenSizeEnum.XS && screenSize !== screenSizeEnum.SM}">
<qrcode qrdata="{{selInfoType.infoID === 1 ? information.uris[0] : information.identity_pubkey}}" [size]="qrWidth" [level]="'L'" [allowEmptyString]="true" class="qr-border"></qrcode>
<qrcode qrdata="{{selInfoType.infoID === 1 ? information.uris[0] : information.identity_pubkey}}" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>
</div>
<div fxLayout="row" *ngIf="information.uris && information.uris.length > 0">
<mat-form-field fxFlex="100" fxFlex.gt-sm="40" fxLayoutAlign="start end">

@ -1,15 +1,14 @@
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
<fa-icon [icon]="faUnlockAlt" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Signin to RTL</span>
<span class="page-title">Login to RTL</span>
</div>
<div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-content class="card-content-gap">
<form (ngSubmit)="onSignin()" #signinForm="ngForm" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign="start" fxLayoutAlign.gt-sm="space-between">
<form (ngSubmit)="onLogin()" #loginForm="ngForm" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign="start" fxLayoutAlign.gt-sm="space-between">
<mat-form-field fxFlex="100" fxLayoutAlign="start">
<input matInput placeholder="Password" type="password" id="password" name="password" [(ngModel)]="password" tabindex="1" required>
<mat-hint>{{hintStr}}</mat-hint>
<mat-error *ngIf="!password">Password is required.</mat-error>
</mat-form-field>
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch" class="mt-2">

@ -12,18 +12,16 @@ import * as fromRTLReducer from '../../../store/rtl.reducers';
import * as RTLActions from '../../../store/rtl.actions';
@Component({
selector: 'rtl-signin',
templateUrl: './signin.component.html',
styleUrls: ['./signin.component.scss']
selector: 'rtl-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
})
export class SigninComponent implements OnInit, OnDestroy {
export class LoginComponent implements OnInit, OnDestroy {
public faUnlockAlt = faUnlockAlt;
public selNode: ConfigSettingsNode;
public password = '';
public nodeAuthType = '';
public rtlSSO = 0;
public rtlCookiePath = '';
public hintStr = '';
public accessKey = '';
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
@ -38,19 +36,13 @@ export class SigninComponent implements OnInit, OnDestroy {
this.logger.error(effectsErr);
});
this.selNode = rtlStore.selNode;
this.nodeAuthType = this.selNode.authentication.nodeAuthType;
this.logger.info(rtlStore);
if (this.nodeAuthType.toUpperCase() === 'DEFAULT') {
this.hintStr = 'Enter RPC password';
} else {
this.hintStr = ''; // Do not remove, initial passowrd 'DEFAULT' is initilizing its value
}
});
}
onSignin() {
onLogin() {
if(!this.password) { return true; }
this.store.dispatch(new RTLActions.Signin(sha256(this.password)));
this.store.dispatch(new RTLActions.Login({password: sha256(this.password), initialPass: this.password === 'password'}));
}
resetData() {

@ -1,30 +0,0 @@
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<div fxFlex="70" fxLayoutAlign="start start">
<div *ngFor="let menuNode of menuNodes">
<button mat-button *ngIf="undefined === menuNode.children" fxLayoutAlign="center center" class="horizontal-button" routerLinkActive="h-active-link" [routerLinkActiveOptions]="{exact: true}" routerLink="{{menuNode.link}}" (click)="onClick(menuNode)">
<fa-icon *ngIf="menuNode.iconType === 'FA'" matTooltip="{{menuNode.name}}" [icon]="menuNode.icon" class="fa-icon-small"></fa-icon>
</button>
<div *ngIf="undefined !== menuNode.children" fxLayoutAlign="start start" [matMenuTriggerFor]="childMenu">
<button mat-button class="horizontal-button" fxLayoutAlign="center center">
<fa-icon *ngIf="menuNode.iconType === 'FA'" matTooltip="{{menuNode.name}}" [icon]="menuNode.icon" class="fa-icon-small"></fa-icon>
</button>
<mat-menu #childMenu="matMenu" xPosition="after" overlapTrigger="false" class="child-menu">
<div *ngFor="let childNode of menuNode.children">
<button mat-button class="horizontal-button bg-primary px-2" fxFlex="100" fxLayoutAlign="center center" [routerLinkActive]="'h-active-link'" routerLink="{{childNode.link}}" [routerLinkActiveOptions]="{exact: true}">
<fa-icon *ngIf="childNode.iconType === 'FA'" matTooltip="{{childNode.name}}" [icon]="childNode.icon" class="fa-icon-small"></fa-icon>
</button>
</div>
</mat-menu>
</div>
</div>
</div>
<div fxFlex="30" fxLayoutAlign="end center">
<mat-select fxFlex="40" *ngIf="appConfig.nodes.length > 1" [value]="selNode" (selectionChange)="onNodeSelectionChange($event.value)" class="m-2 multi-node-select">
<mat-option *ngFor="let node of appConfig.nodes" [value]="node" tabindex="19">
{{node.lnNode}} ({{node.lnImplementation}})
</mat-option>
</mat-select>
<button fxLayoutAlign="center center" mat-stroked-button color="primary" class="horizontal-button-show" tabindex="20" (click)="onShowPubkey()">Show Public Key</button>
<rtl-top-menu></rtl-top-menu>
</div>
</div>

@ -1,19 +0,0 @@
.mat-menu-panel.child-menu {
min-width: 88px;
width:88px;
border-radius: 0;
margin-left: 30%;
margin-top: 6%;
.mat-menu-content {
.mat-menu-item {
padding: 0;
margin-top: -3px;
.mat-icon {
margin-right: 0;
}
button {
border-radius: 0;
}
}
}
}

@ -1,25 +0,0 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { HorizontalNavigationComponent } from './horizontal-navigation.component';
describe('HorizontalNavigationComponent', () => {
let component: HorizontalNavigationComponent;
let fixture: ComponentFixture<HorizontalNavigationComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ HorizontalNavigationComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HorizontalNavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -1,102 +0,0 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { faEject } from '@fortawesome/free-solid-svg-icons';
import { SessionService } from '../../../services/session.service';
import { MENU_DATA } from '../../../models/navMenu';
import { RTLEffects } from '../../../../store/rtl.effects';
import * as RTLActions from '../../../../store/rtl.actions';
import * as fromRTLReducer from '../../../../store/rtl.reducers';
import { GetInfoRoot, ConfigSettingsNode, RTLConfiguration } from '../../../models/RTLconfig';
import { AlertTypeEnum } from '../../../services/consts-enums-functions';
@Component({
selector: 'rtl-horizontal-navigation',
templateUrl: './horizontal-navigation.component.html',
styleUrls: ['./horizontal-navigation.component.scss']
})
export class HorizontalNavigationComponent implements OnInit, OnDestroy {
public menuNodes = [];
public logoutNode = [];
public showLogout = false;
public numPendingChannels = 0;
public appConfig: RTLConfiguration;
public selNode: ConfigSettingsNode;
public information: GetInfoRoot = {};
private unSubs = [new Subject(), new Subject(), new Subject()];
constructor(private sessionService: SessionService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects) {}
ngOnInit() {
this.store.select('root')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
this.information = rtlStore.nodeData;
this.appConfig = rtlStore.appConfig;
this.selNode = rtlStore.selNode;
if(this.selNode.lnImplementation.toUpperCase() === 'CLT') {
this.menuNodes = MENU_DATA.CLChildren;
} else {
this.menuNodes = MENU_DATA.LNDChildren;
}
if(this.sessionService.getItem('token')) {
if (this.menuNodes[this.menuNodes.length - 1].id !== 200) {
this.menuNodes.push({id: 200, parentId: 0, name: 'Logout', iconType: 'FA', icon: faEject});
}
} else {
if(this.menuNodes[this.menuNodes.length - 1].id === 200) {
this.menuNodes.pop();
}
}
});
this.sessionService.watchSession()
.pipe(takeUntil(this.unSubs[1]))
.subscribe(session => {
if(session.token) {
if (this.menuNodes[this.menuNodes.length - 1].id !== 200) {
this.menuNodes.push({id: 200, parentId: 0, name: 'Logout', iconType: 'FA', icon: faEject});
}
} else {
if(this.menuNodes[this.menuNodes.length - 1].id === 200) {
this.menuNodes.pop();
}
}
});
}
onClick(node) {
if (node.name === 'Logout') {
this.store.dispatch(new RTLActions.OpenConfirmation({
data: { type: AlertTypeEnum.CONFIRM, alertTitle: 'Logout', titleMessage: 'Logout from this device?', noBtnText: 'Cancel', yesBtnText: 'Logout'
}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[2]))
.subscribe(confirmRes => {
if (confirmRes) {
this.showLogout = false;
this.store.dispatch(new RTLActions.Signout());
}
});
}
}
onShowPubkey() {
this.store.dispatch(new RTLActions.ShowPubkey());
}
onNodeSelectionChange(selNodeValue: ConfigSettingsNode) {
this.selNode = selNodeValue;
this.store.dispatch(new RTLActions.OpenSpinner('Updating Selected Node...'));
this.store.dispatch(new RTLActions.SetSelelectedNode({ lnNode: selNodeValue, isInitialSetup: false }));
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -6,13 +6,13 @@
</mat-option>
</mat-select>
<mat-divider class="w-100"></mat-divider>
<mat-tree #tree [dataSource]="navMenus" [treeControl]="treeControlNested" *ngIf="settings.menuType === 'REGULAR'">
<mat-tree #tree [dataSource]="navMenus" [treeControl]="treeControlNested">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle routerLinkActive="active-link" routerLink="{{node.link}}">
<div (click)="onChildNavClicked(node)">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-small mr-2"></fa-icon>
<mat-icon *ngIf="!node.iconType" class="mat-icon-36">{{node.icon}}</mat-icon>
<span *ngIf="settings.menuType === 'REGULAR'">{{node.name}}</span>
<span>{{node.name}}</span>
</div>
</div>
</mat-tree-node>
@ -34,100 +34,21 @@
</div>
</mat-nested-tree-node>
</mat-tree>
<mat-tree [dataSource]="navMenus" [treeControl]="treeControlNested" *ngIf="settings.menuType === 'COMPACT'">
<mat-tree-node fxLayout="row" matTreeNodeToggle fxLayoutAlign="start center" *matTreeNodeDef="let node"
(click)="onChildNavClicked(node)" routerLinkActive="active-link" routerLink="{{node.link}}">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-small mr-1"></fa-icon>
<mat-icon *ngIf="!node.iconType" class="mat-icon-36">{{node.icon}}</mat-icon>
<span>{{node.name}}</span>
</mat-tree-node>
<mat-nested-tree-node fxLayout="column" *matTreeNodeDef="let node; when: hasChild" matTreeNodeToggle>
<div fxLayout="row" fxLayoutAlign="start center" class="mat-nested-tree-node-parent">
<div fxFlex="89" fxLayoutAlign="start center">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-small mr-1"></fa-icon>
<mat-icon *ngIf="!node.iconType" class="mat-icon-36">{{node.icon}}</mat-icon>
<span>{{node.name}}</span>
</div>
<button fxFlex="11" mat-icon-button [attr.aria-label]="'toggle ' + node.name" fxLayoutAlign="end center">
<mat-icon class="mat-icon-rtl-mirror">
{{treeControlNested.isExpanded(node) ? 'arrow_drop_up' : 'arrow_drop_down'}}</mat-icon>
</button>
</div>
<div [class.tree-children-invisible]="!treeControlNested.isExpanded(node)" class="mat-nested-tree-node-child">
<ng-container matTreeNodeOutlet></ng-container>
</div>
</mat-nested-tree-node>
</mat-tree>
<mat-tree [dataSource]="navMenus" [treeControl]="treeControlNested" *ngIf="settings.menuType === 'MINI'">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle routerLinkActive="active-link" routerLink="{{node.link}}">
<div (click)="onChildNavClicked(node)">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-regular" matTooltip="{{node.name}}" matTooltipPosition="right"></fa-icon>
<mat-icon *ngIf="!node.iconType" class="mat-icon-36" matTooltip="{{node.name}}" matTooltipPosition="right">{{node.icon}}</mat-icon>
</div>
</div>
</mat-tree-node>
<mat-nested-tree-node fxLayout="column" *matTreeNodeDef="let node; when: hasChild" matTreeNodeToggle>
<div fxLayout="row" fxLayoutAlign="start center" class="mat-nested-tree-node-parent">
<div fxFlex="89" fxLayoutAlign="start center">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-regular" matTooltip="{{node.name}}" matTooltipPosition="right"></fa-icon>
<mat-icon *ngIf="!node.iconType" class="mat-icon-36" matTooltip="{{node.name}}" matTooltipPosition="right">{{node.icon}}</mat-icon>
</div>
<button fxFlex="11" fxLayoutAlign="end center" mat-icon-button [attr.aria-label]="'toggle ' + node.name"
fxLayoutAlign="end center">
<mat-icon class="mat-icon-rtl-mirror">
{{treeControlNested.isExpanded(node) ? 'arrow_drop_up' : 'arrow_drop_down'}}</mat-icon>
</button>
</div>
<div [class.tree-children-invisible]="!treeControlNested.isExpanded(node)" class="mat-nested-tree-node-child">
<ng-container matTreeNodeOutlet></ng-container>
</div>
</mat-nested-tree-node>
</mat-tree>
<mat-divider class="w-100"></mat-divider>
<mat-tree [dataSource]="navMenusShowData" [treeControl]="treeControlShowData" *ngIf="settings.menuType === 'REGULAR'">
<mat-tree [dataSource]="navMenusShowData" [treeControl]="treeControlShowData">
<mat-tree-node *matTreeNodeDef="let node" (click)="onShowData(node)">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-small mr-2" matTooltip="{{node.name}}" matTooltipPosition="right"></fa-icon>
<mat-icon *ngIf="!node.iconType" class="mat-icon-36" matTooltip="{{node.name}}" matTooltipPosition="right">{{node.icon}}</mat-icon>
<span>{{node.name}}</span>
</mat-tree-node>
</mat-tree>
<mat-tree [dataSource]="navMenusShowData" [treeControl]="treeControlShowData" *ngIf="settings.menuType === 'COMPACT'">
<mat-tree-node fxLayout="row" fxLayoutAlign="start center" *matTreeNodeDef="let node" (click)="onShowData(node)">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-small mr-1"></fa-icon>
<span>{{node.name}}</span>
</mat-tree-node>
</mat-tree>
<mat-tree [dataSource]="navMenusShowData" [treeControl]="treeControlShowData" *ngIf="settings.menuType === 'MINI'">
<mat-tree-node *matTreeNodeDef="let node" (click)="onShowData(node)" fxLayoutAlign="start center">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-regular" matTooltip="{{node.name}}" matTooltipPosition="right"></fa-icon>
</mat-tree-node>
</mat-tree>
</div>
<div fxLayout="column" fxFlex="10" fxLayoutAlign="end stretch" class="w-100">
<mat-tree [dataSource]="navMenusLogout" [treeControl]="treeControlLogout" *ngIf="settings.menuType === 'REGULAR' && showLogout">
<mat-tree [dataSource]="navMenusLogout" [treeControl]="treeControlLogout" *ngIf="showLogout">
<mat-tree-node *matTreeNodeDef="let node" (click)="onClick(node)">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-small mr-2" matTooltip="{{node.name}}" matTooltipPosition="right"></fa-icon>
<span>{{node.name}}</span>
</mat-tree-node>
</mat-tree>
<mat-tree [dataSource]="navMenusLogout" [treeControl]="treeControlLogout" *ngIf="settings.menuType === 'COMPACT' && showLogout">
<mat-tree-node fxLayout="row" fxLayoutAlign="start center" *matTreeNodeDef="let node" (click)="onClick(node)">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-small mr-1"></fa-icon>
<span>{{node.name}}</span>
</mat-tree-node>
</mat-tree>
<mat-tree [dataSource]="navMenusLogout" [treeControl]="treeControlLogout" *ngIf="settings.menuType === 'MINI' && showLogout">
<mat-tree-node *matTreeNodeDef="let node" (click)="onClick(node)" fxLayoutAlign="start center">
<fa-icon *ngIf="node.iconType === 'FA'" [icon]="node.icon" class="fa-icon-regular" matTooltip="{{node.name}}" matTooltipPosition="right"></fa-icon>
</mat-tree-node>
</mat-tree>
</div>
</div>

@ -101,8 +101,8 @@ export class SideNavigationComponent implements OnInit, OnDestroy {
this.flgLoading = session.token ? true : false;
});
this.actions$.pipe(takeUntil(this.unSubs[2]),
filter((action) => action.type === RTLActions.SIGNOUT))
.subscribe((action: RTLActions.Signout) => {
filter((action) => action.type === RTLActions.LOGOUT))
.subscribe((action: RTLActions.Logout) => {
this.showLogout = false;
});
}
@ -119,7 +119,7 @@ export class SideNavigationComponent implements OnInit, OnDestroy {
.subscribe(confirmRes => {
if (confirmRes) {
this.showLogout = false;
this.store.dispatch(new RTLActions.Signout());
this.store.dispatch(new RTLActions.Logout());
}
});
}

@ -71,7 +71,7 @@ export class TopMenuComponent implements OnInit, OnDestroy {
this.actions$
.pipe(
takeUntil(this.unSubs[2]),
filter((action) => action.type === RTLActions.SIGNOUT)
filter((action) => action.type === RTLActions.LOGOUT)
).subscribe(() => {
this.showLogout = false;
});
@ -86,7 +86,7 @@ export class TopMenuComponent implements OnInit, OnDestroy {
.subscribe(confirmRes => {
if (confirmRes) {
this.showLogout = false;
this.store.dispatch(new RTLActions.Signout());
this.store.dispatch(new RTLActions.Logout());
}
});
}

@ -26,14 +26,8 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
public information: GetInfoRoot = {};
public userPersonas = [UserPersonaEnum.OPERATOR, UserPersonaEnum.MERCHANT];
public currencyUnits = FIAT_CURRENCY_UNITS;
public menus = NODE_SETTINGS.menus;
public selectedMenu = NODE_SETTINGS.menus[0];
public menuTypes = NODE_SETTINGS.menuTypes;
public themeModes = NODE_SETTINGS.modes;
public themeColors = NODE_SETTINGS.themes;
public fontSizes = NODE_SETTINGS.fontSize;
public selectedMenuType = NODE_SETTINGS.menuTypes[0];
public selectedFontSize = NODE_SETTINGS.fontSize[1];
public selectedThemeMode = NODE_SETTINGS.modes[0];
public selectedThemeColor = NODE_SETTINGS.themes[0].id;
public currencyUnit = 'BTC';
@ -57,17 +51,8 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
.subscribe((rtlStore) => {
this.appConfig = rtlStore.appConfig;
this.selNode = rtlStore.selNode;
this.selectedMenu = this.menus.find(menu => menu.id === this.selNode.settings.menu);
this.selectedMenuType = this.menuTypes.find(menuType => this.selNode.settings.menuType === menuType.id);
this.selectedThemeMode = this.themeModes.find(themeMode => this.selNode.settings.themeMode === themeMode.id);
this.selectedThemeColor = this.selNode.settings.themeColor;
this.selectedFontSize = this.fontSizes.find(fontSize => fontSize.class === this.selNode.settings.fontSize);
if (window.innerWidth <= 768) {
this.selNode.settings.menu = 'VERTICAL';
this.selNode.settings.flgSidenavOpened = false;
this.selNode.settings.flgSidenavPinned = false;
this.showSettingOption = false;
}
this.information = rtlStore.nodeData;
this.smallerCurrencyUnit = (undefined !== this.information && undefined !== this.information.smaller_currency_unit) ? this.information.smaller_currency_unit : 'Sats';
this.currencyUnit = (undefined !== this.information && undefined !== this.information.currency_unit) ? this.information.currency_unit : 'BTC';
@ -82,31 +67,12 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
onCurrencyChange(event: any) {
this.selNode.settings.currencyUnits = [...CURRENCY_UNITS, event.value];
this.store.dispatch(new RTLActions.SetChildNodeSettings({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, satsToBTC: this.selNode.settings.satsToBTC, selCurrencyUnit: event.value, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion}));
this.store.dispatch(new RTLActions.SetChildNodeSettingsCL({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, satsToBTC: this.selNode.settings.satsToBTC, selCurrencyUnit: event.value, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion}));
}
chooseMenuType() {
this.selNode.settings.menuType = this.selectedMenuType.id;
this.commonService.changeContainerWidth('menuType');
}
chooseFontSize() {
this.selNode.settings.fontSize = (this.fontSizes.filter(fontSize => fontSize.id === this.selectedFontSize.id)[0]).class;
this.store.dispatch(new RTLActions.SetChildNodeSettings({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: event.value, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion}));
this.store.dispatch(new RTLActions.SetChildNodeSettingsCL({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: event.value, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion}));
}
toggleSettings(toggleField: string, event?: any) {
if(toggleField === 'menu') {
this.selNode.settings.flgSidenavOpened = (!event.checked) ? false : true;
setTimeout(() => {
this.selNode.settings.menu = (!event.checked) ? 'HORIZONTAL' : 'VERTICAL';
}, 10);
} else {
this.selNode.settings[toggleField] = !this.selNode.settings[toggleField];
if(toggleField === 'flgSidenavOpened' || toggleField === 'flgSidenavPinned') {
this.commonService.changeContainerWidth(toggleField);
}
}
this.selNode.settings[toggleField] = !this.selNode.settings[toggleField];
}
changeThemeColor(newThemeColor: string) {
@ -124,16 +90,13 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
this.logger.info(this.selNode.settings);
this.store.dispatch(new RTLActions.OpenSpinner('Updating Settings...'));
this.store.dispatch(new RTLActions.SaveSettings({settings: this.selNode.settings, defaultNodeIndex: defaultNodeIndex}));
this.store.dispatch(new RTLActions.SetChildNodeSettings({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, satsToBTC: this.selNode.settings.satsToBTC, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion}));
this.store.dispatch(new RTLActions.SetChildNodeSettingsCL({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, satsToBTC: this.selNode.settings.satsToBTC, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion}));
this.store.dispatch(new RTLActions.SetChildNodeSettings({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion}));
this.store.dispatch(new RTLActions.SetChildNodeSettingsCL({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion}));
this.done.emit();
}
onResetSettings() {
this.selNode.settings = this.previousSettings;
this.selectedMenu = this.menus.find(menu => menu.id === this.previousSettings.menu);
this.selectedMenuType = this.menuTypes.find(menuType => menuType.id === this.previousSettings.menuType);
this.selectedFontSize = this.fontSizes.find(fontSize => fontSize.class === this.previousSettings.fontSize);
this.selectedThemeMode = this.themeModes.find(themeMode => themeMode.id === this.previousSettings.themeMode);
this.selectedThemeColor = this.previousSettings.themeColor;
this.store.dispatch(new RTLActions.SetSelelectedNode({ lnNode: this.selNode, isInitialSetup: true }));

@ -0,0 +1,30 @@
<div fxLayout="column" fxFlex="100" class="overflow-x-hidden">
<form (ngSubmit)="onResetPassword()" fxLayout="column" fxLayoutAlign="start stretch" class="settings-container page-sub-title-container mt-1" #authForm="ngForm">
<div fxFlex="100" class="mb-1">
<fa-icon [icon]="faKey" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Reset Password</span>
</div>
<div fxLayout="row">
<div fxLayout="column" fxFlex="100" fxFlex.gt-sm="100" fxLayoutAlign="space-between stretch" class="mt-2">
<mat-form-field>
<input matInput placeholder="Old Password" type="password" id="oldpassword" name="oldpassword" [(ngModel)]="oldPassword" tabindex="1" required>
<mat-error *ngIf="!oldPassword">Old password is required.</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="New Password" type="password" id="newpassword" name="newpassword" [(ngModel)]="newPassword" tabindex="2" required>
<mat-error *ngIf="matchOldAndNewPasswords()">{{errorMsg}}</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Confirm New Password" type="password" id="confirmpassword" name="confirmpassword" [(ngModel)]="confirmPassword" tabindex="3" required>
<mat-error *ngIf="matchNewPasswords()">{{errorConfirmMsg}}</mat-error>
</mat-form-field>
</div>
</div>
<div fxLayout="row">
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch" class="mt-2">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="4" type="reset" (click)="resetData()">Clear</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-flat-button color="primary" tabindex="5" type="submit">Reset Password</button>
</div>
</div>
</form>
</div>

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AuthSettingsComponent } from './auth-settings.component';
describe('AuthSettingsComponent', () => {
let component: AuthSettingsComponent;
let fixture: ComponentFixture<AuthSettingsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AuthSettingsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(AuthSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,78 @@
import { Component, OnInit, ViewChild } from '@angular/core';
import { Store } from '@ngrx/store';
import { faKey } from '@fortawesome/free-solid-svg-icons';
import * as sha256 from 'sha256';
import * as fromRTLReducer from '../../../../store/rtl.reducers';
import * as RTLActions from '../../../../store/rtl.actions';
@Component({
selector: 'rtl-auth-settings',
templateUrl: './auth-settings.component.html',
styleUrls: ['./auth-settings.component.scss']
})
export class AuthSettingsComponent implements OnInit {
@ViewChild('authForm', { static: true }) form: any;
public faKey = faKey;
public oldPassword = '';
public newPassword = '';
public confirmPassword = '';
public errorMsg = '';
public errorConfirmMsg = '';
constructor(private store: Store<fromRTLReducer.RTLState>) {}
ngOnInit() {}
onResetPassword() {
if(!this.oldPassword || !this.newPassword || !this.confirmPassword || this.oldPassword === this.newPassword || this.newPassword !== this.confirmPassword) { return true; }
this.store.dispatch(new RTLActions.ResetPassword({oldPassword: sha256(this.oldPassword), newPassword: sha256(this.newPassword)}));
}
matchOldAndNewPasswords(): boolean {
let invalid = false;
if(this.form.controls.newpassword) {
if (!this.newPassword) {
this.form.controls.newpassword.setErrors({invalid: true});
this.errorMsg = 'New password is required.';
invalid = true;
} else if (this.oldPassword !== '' && this.newPassword !== '' && this.oldPassword === this.newPassword) {
this.form.controls.newpassword.setErrors({invalid: true});
this.errorMsg = 'Old and New password cannot be same.';
invalid = true;
} else {
this.form.controls.newpassword.setErrors(null);
this.errorMsg = '';
invalid = false;
}
}
return invalid;
}
matchNewPasswords(): boolean {
let invalid = false;
if (this.form.controls.confirmpassword) {
if (!this.confirmPassword) {
this.form.controls.confirmpassword.setErrors({invalid: true});
this.errorConfirmMsg = 'Confirm password is required.';
invalid = true;
} else if (this.newPassword !== '' && this.confirmPassword !== '' && this.newPassword !== this.confirmPassword) {
this.form.controls.confirmpassword.setErrors({invalid: true});
this.errorConfirmMsg = 'New and confirm passwords do not match.';
invalid = true;
} else {
this.form.controls.confirmpassword.setErrors(null);
this.errorConfirmMsg = '';
invalid = false;
}
}
return invalid;
}
resetData() {
this.oldPassword = '';
this.newPassword = '';
this.confirmPassword = '';
}
}

@ -5,8 +5,9 @@
<div fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column">
<mat-tab-group>
<mat-tab label="Settings"><rtl-app-settings></rtl-app-settings></mat-tab>
<mat-tab-group selectedIndex="{{loadTab === 'authSettings' ? 1 : 0}}">
<mat-tab id="appSettings" label="Settings"><rtl-app-settings></rtl-app-settings></mat-tab>
<mat-tab *ngIf="!appConfig.sso.rtlSSO" label="Password Reset"><rtl-auth-settings></rtl-auth-settings></mat-tab>
<mat-tab *ngIf="showLnConfig" [label]="lnImplementationStr">
<ng-template matTabContent>
<rtl-server-config [selectedNodeType]="'ln'"></rtl-server-config>

@ -1,11 +1,13 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { faTools } from '@fortawesome/free-solid-svg-icons';
import { ConfigSettingsNode } from '../../models/RTLconfig';
import { ConfigSettingsNode, RTLConfiguration } from '../../models/RTLconfig';
import * as fromRTLReducer from '../../../store/rtl.reducers';
import * as RTLActions from '../../../store/rtl.actions';
@Component({
selector: 'rtl-settings',
@ -17,29 +19,42 @@ export class SettingsComponent implements OnInit, OnDestroy{
public showLnConfig = false;
public showBitcoind = false;
public selNode: ConfigSettingsNode;
public appConfig: RTLConfiguration;
public lnImplementationStr = '';
public loadTab = 'appSettings';
public initializeNodeData = false;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>) {}
constructor(private store: Store<fromRTLReducer.RTLState>, private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.store.select('root')
this.activatedRoute.paramMap
.pipe(takeUntil(this.unSubs[0]))
.subscribe(data => {
this.loadTab = window.history.state.loadTab ? window.history.state.loadTab : 'appSettings';
this.initializeNodeData = window.history.state.initializeNodeData ? window.history.state.initializeNodeData : false;
});
this.store.select('root')
.pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => {
this.showLnConfig = false;
this.showBitcoind = false;
this.appConfig = rtlStore.appConfig;
this.selNode = rtlStore.selNode;
this.lnImplementationStr = this.selNode.lnImplementation.toUpperCase() === 'CLT' ? 'C-Lightning Config' : 'LND Config';
if (undefined !== this.selNode.authentication && undefined !== this.selNode.authentication.configPath && this.selNode.authentication.configPath !== '') {
if (this.selNode.authentication && this.selNode.authentication.configPath && this.selNode.authentication.configPath.trim() !== '') {
this.showLnConfig = true;
}
if (undefined !== this.selNode.authentication && undefined !== this.selNode.authentication.bitcoindConfigPath && this.selNode.authentication.bitcoindConfigPath !== '') {
if (this.selNode.settings && this.selNode.settings.bitcoindConfigPath && this.selNode.settings.bitcoindConfigPath.trim() !== '') {
this.showBitcoind = true;
}
});
}
ngOnDestroy() {
if(this.initializeNodeData) {
this.store.dispatch(new RTLActions.SetSelelectedNode({lnNode: this.selNode, isInitialSetup: true}));
}
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();

@ -10,14 +10,8 @@ export class SSO {
export class Settings {
constructor(
public userPersona: string,
public flgSidenavOpened: boolean,
public flgSidenavPinned: boolean,
public menu: string,
public menuType: string,
public fontSize: string,
public themeMode: string,
public themeColor: string,
public satsToBTC: boolean,
public currencyUnits: Array<string>,
public fiatConversion: boolean,
public bitcoindConfigPath?: string,
@ -30,9 +24,7 @@ export class Settings {
export class Authentication {
constructor(
public nodeAuthType?: string,
public configPath?: string,
public bitcoindConfigPath?: string
public configPath?: string
) { }
}
@ -69,7 +61,6 @@ export interface GetInfoRoot {
export interface SelNodeChild {
userPersona?: string;
channelBackupPath?: string;
satsToBTC?: boolean;
selCurrencyUnit?: string;
currencyUnits?: string[];
fiatConversion?: boolean;

@ -8,9 +8,9 @@ export const MENU_DATA: MenuRootNode = {
{id: 3, parentId: 0, name: 'Lightning', iconType: 'FA', icon: faBolt, link: '/lnd/peerschannels', userPersona: UserPersonaEnum.ALL, children: [
{id: 31, parentId: 3, name: 'Peers/Channels', iconType: 'FA', icon: faUsers, link: '/lnd/peerschannels', userPersona: UserPersonaEnum.ALL},
{id: 32, parentId: 3, name: 'Transactions', iconType: 'FA', icon: faExchangeAlt, link: '/lnd/transactions', userPersona: UserPersonaEnum.ALL},
{id: 33, parentId: 3, name: 'Backup', iconType: 'FA', icon: faDownload, link: '/lnd/backup', userPersona: UserPersonaEnum.ALL},
{id: 34, parentId: 3, name: 'Routing', iconType: 'FA', icon: faMapSigns, link: '/lnd/routing', userPersona: UserPersonaEnum.ALL},
{id: 35, parentId: 3, name: 'Graph Lookup', iconType: 'FA', icon: faSearch, link: '/lnd/lookups', userPersona: UserPersonaEnum.ALL}
{id: 33, parentId: 3, name: 'Routing', iconType: 'FA', icon: faMapSigns, link: '/lnd/routing', userPersona: UserPersonaEnum.ALL},
{id: 34, parentId: 3, name: 'Graph Lookup', iconType: 'FA', icon: faSearch, link: '/lnd/lookups', userPersona: UserPersonaEnum.ALL},
{id: 35, parentId: 3, name: 'Backup', iconType: 'FA', icon: faDownload, link: '/lnd/backup', userPersona: UserPersonaEnum.ALL}
]},
{id: 5, parentId: 0, name: 'Network', iconType: 'FA', icon: faProjectDiagram, link: '/lnd/network', userPersona: UserPersonaEnum.OPERATOR},
{id: 6, parentId: 0, name: 'Node/Network', iconType: 'FA', icon: faServer, link: '/lnd/network', userPersona: UserPersonaEnum.MERCHANT},

@ -56,10 +56,7 @@ export const NODE_SETTINGS = {
{id: 'INDIGO', name: 'RTL'},
{id: 'PINK', name: 'BK'}
],
modes: [{id: 'DAY', name: 'Day'}, {id: 'NIGHT', name: 'Night'}],
fontSize: [{id: 1, name: 'Small', class: 'SMALL'}, {id: 2, name: 'Medium', class: 'MEDIUM'}, {id: 3, name: 'Large', class: 'LARGE'}],
menuTypes: [{id: 'REGULAR', name: 'Regular'}, {id: 'COMPACT', name: 'Compact'}, {id: 'MINI', name: 'Mini'}],
menus: [{id: 'VERTICAL', name: 'Vertical'}, {id: 'HORIZONTAL', name: 'Horizontal'}]
modes: [{id: 'DAY', name: 'Day'}, {id: 'NIGHT', name: 'Night'}]
};
export enum UserPersonaEnum {

@ -33,11 +33,10 @@ import { ConfirmationMessageComponent } from './components/data-modal/confirmati
import { ErrorMessageComponent } from './components/data-modal/error-message/error-message.component';
import { SpinnerDialogComponent } from './components/data-modal/spinner-dialog/spinner-dialog.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { SigninComponent } from './components/signin/signin.component';
import { LoginComponent } from './components/login/login.component';
import { HelpComponent } from './components/help/help.component';
import { SideNavigationComponent } from './components/navigation/side-navigation/side-navigation.component';
import { TopMenuComponent } from './components/navigation/top-menu/top-menu.component';
import { HorizontalNavigationComponent } from './components/navigation/horizontal-navigation/horizontal-navigation.component';
import { SettingsComponent } from './components/settings/settings.component';
import { ServerConfigComponent } from './components/settings/server-config/server-config.component';
import { ErrorComponent } from './components/error/error.component';
@ -51,6 +50,7 @@ import { NonNegativeAmountValidator } from './directive/non-negative-amount.dire
import { RemoveLeadingZerosPipe } from './pipes/app.pipe';
import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.service';
import { AuthSettingsComponent } from './components/settings/auth-settings/auth-settings.component';
@NgModule({
imports: [
@ -145,8 +145,7 @@ import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.s
NotFoundComponent,
SideNavigationComponent,
TopMenuComponent,
HorizontalNavigationComponent,
SigninComponent,
LoginComponent,
HelpComponent,
ServerConfigComponent,
CurrencyUnitConverterComponent,
@ -170,8 +169,7 @@ import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.s
NotFoundComponent,
SideNavigationComponent,
TopMenuComponent,
HorizontalNavigationComponent,
SigninComponent,
LoginComponent,
HelpComponent,
ServerConfigComponent,
CurrencyUnitConverterComponent,
@ -182,7 +180,8 @@ import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.s
RemoveLeadingZerosPipe,
CLOpenChannelComponent,
OpenChannelComponent,
ShowPubkeyComponent
ShowPubkeyComponent,
AuthSettingsComponent
],
entryComponents: [
CLInvoiceInformationComponent,

@ -798,14 +798,6 @@ a {
font-weight: 700 !important;
}
.qr-border {
border: 1.2rem solid white;
}
.qr-thin-border {
border: .5rem solid white;
}
.pubkey-info-top {
flex-wrap: wrap;
margin-top: 1px;

@ -92,8 +92,9 @@ export const FETCH_CONFIG = 'FETCH_CONFIG';
export const SHOW_CONFIG = 'SHOW_CONFIG';
export const IS_AUTHORIZED = 'IS_AUTHORIZED';
export const IS_AUTHORIZED_RES = 'IS_AUTHORIZED_RES';
export const SIGNIN = 'SIGNIN';
export const SIGNOUT = 'SIGNOUT';
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
export const RESET_PASSWORD = 'RESET_PASSWORD';
export const PEER_LOOKUP = 'PEER_LOOKUP';
export const CHANNEL_LOOKUP = 'CHANNEL_LOOKUP';
export const INVOICE_LOOKUP = 'INVOICE_LOOKUP';
@ -593,16 +594,21 @@ export class IsAuthorizedRes implements Action {
constructor(public payload: any) {} // payload = token/error
}
export class Signin implements Action {
readonly type = SIGNIN;
constructor(public payload: string) {} // payload = password
export class Login implements Action {
readonly type = LOGIN;
constructor(public payload: {password: string, initialPass: boolean}) {}
}
export class Signout implements Action {
readonly type = SIGNOUT;
export class Logout implements Action {
readonly type = LOGOUT;
constructor() {}
}
export class ResetPassword implements Action {
readonly type = RESET_PASSWORD;
constructor(public payload: {oldPassword: string, newPassword: string}) {}
}
export class SetChildNodeSettingsCL implements Action {
readonly type = SET_CHILD_NODE_SETTINGS_CL;
constructor(public payload: SelNodeChild) {}
@ -845,7 +851,7 @@ export type RTLActions =
GetNewAddress | SetNewAddress | SetChannelTransaction |
GenSeed | GenSeedResponse | InitWallet | InitWalletResponse | UnlockWallet |
FetchConfig | ShowConfig | PeerLookup | ChannelLookup | InvoiceLookup | SetLookup |
IsAuthorized | IsAuthorizedRes | Signin | Signout |
IsAuthorized | IsAuthorizedRes | Login | Logout | ResetPassword |
SetChildNodeSettingsCL | FetchInfoCL | SetInfoCL | FetchFeesCL | SetFeesCL | FetchFeeRatesCL | SetFeeRatesCL |
FetchBalanceCL | SetBalanceCL | FetchLocalRemoteBalanceCL | SetLocalRemoteBalanceCL |
GetNewAddressCL | SetNewAddressCL |

@ -269,16 +269,16 @@ export class RTLEffects implements OnDestroy {
);
@Effect({ dispatch: false })
authSignin = this.actions$.pipe(
ofType(RTLActions.SIGNIN),
authLogin = this.actions$.pipe(
ofType(RTLActions.LOGIN),
withLatestFrom(this.store.select('root')),
mergeMap(([action, rootStore]: [RTLActions.Signin, fromRTLReducer.RootState]) => {
mergeMap(([action, rootStore]: [RTLActions.Login, fromRTLReducer.RootState]) => {
this.store.dispatch(new RTLActions.ClearEffectErrorLnd('FetchInfo'));
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchInfoCL'));
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('Signin'));
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('Login'));
return this.httpClient.post(environment.AUTHENTICATE_API, {
authenticateWith: (undefined === action.payload || action.payload == null || action.payload === '') ? AuthenticateWith.TOKEN : AuthenticateWith.PASSWORD,
authenticationValue: (undefined === action.payload || action.payload == null || action.payload === '') ? (this.sessionService.getItem('token') ? this.sessionService.getItem('token') : '') : action.payload
authenticateWith: (!action.payload.password) ? AuthenticateWith.TOKEN : AuthenticateWith.PASSWORD,
authenticationValue: (!action.payload.password) ? (this.sessionService.getItem('token') ? this.sessionService.getItem('token') : '') : action.payload.password
})
.pipe(
map((postRes: any) => {
@ -286,12 +286,17 @@ export class RTLEffects implements OnDestroy {
this.logger.info('Successfully Authorized!');
this.SetToken(postRes.token);
rootStore.selNode.settings.currencyUnits = [...CURRENCY_UNITS, rootStore.selNode.settings.currencyUnit];
this.store.dispatch(new RTLActions.SetSelelectedNode({lnNode: rootStore.selNode, isInitialSetup: true}))
if(action.payload.initialPass) {
this.store.dispatch(new RTLActions.OpenSnackBar('Reset your password.'));
this.router.navigate(['/settings'], { state: { loadTab: 'authSettings', initializeNodeData: true }});
} else {
this.store.dispatch(new RTLActions.SetSelelectedNode({lnNode: rootStore.selNode, isInitialSetup: true}));
}
}),
catchError((err) => {
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: 'Signin', code: err.status, message: err.error.message }));
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: 'Login', code: err.status, message: err.error.message }));
this.handleErrorWithAlert('ERROR', 'Authorization Failed!', environment.AUTHENTICATE_API, err.error);
this.logger.info('Redirecting to Signin Error Page');
this.logger.info('Redirecting to Login Error Page');
if (+rootStore.appConfig.sso.rtlSSO) {
this.router.navigate(['/error'], { state: { errorCode: '401', errorMessage: 'Single Sign On Failed!' }});
} else {
@ -303,8 +308,8 @@ export class RTLEffects implements OnDestroy {
}));
@Effect({ dispatch: false })
signOut = this.actions$.pipe(
ofType(RTLActions.SIGNOUT),
logOut = this.actions$.pipe(
ofType(RTLActions.LOGOUT),
withLatestFrom(this.store.select('root')),
mergeMap(([action, store]) => {
if (+store.appConfig.sso.rtlSSO) {
@ -320,6 +325,31 @@ export class RTLEffects implements OnDestroy {
}));
@Effect({ dispatch: false })
resetPassword = this.actions$.pipe(
ofType(RTLActions.RESET_PASSWORD),
withLatestFrom(this.store.select('root')),
mergeMap(([action, rootStore]: [RTLActions.ResetPassword, fromRTLReducer.RootState]) => {
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('ResetPassword'));
return this.httpClient.post(environment.AUTHENTICATE_API + '/reset', {
oldPassword: action.payload.oldPassword,
newPassword: action.payload.newPassword
})
.pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.logger.info('Password Reset Successful!');
this.store.dispatch(new RTLActions.OpenSnackBar('Password Reset Successful!'));
this.SetToken(postRes.token);
}),
catchError((err) => {
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: 'ResetPassword', code: err.status, message: err.error.message }));
this.handleErrorWithAlert('ERROR', 'Password Reset Failed!', environment.AUTHENTICATE_API + '/reset', err.error);
return of({type: RTLActions.VOID});
})
);
}));
@Effect()
setSelectedNode = this.actions$.pipe(
ofType(RTLActions.SET_SELECTED_NODE),
@ -346,9 +376,9 @@ export class RTLEffects implements OnDestroy {
const landingPage = isInitialSetup ? '' : 'HOME';
let selNode = {};
if(node.settings.fiatConversion && node.settings.currencyUnit) {
selNode = { userPersona: node.settings.userPersona, channelBackupPath: node.settings.channelBackupPath, satsToBTC: node.settings.satsToBTC, selCurrencyUnit: node.settings.currencyUnit, currencyUnits: [...CURRENCY_UNITS, node.settings.currencyUnit], fiatConversion: node.settings.fiatConversion };
selNode = { userPersona: node.settings.userPersona, channelBackupPath: node.settings.channelBackupPath, selCurrencyUnit: node.settings.currencyUnit, currencyUnits: [...CURRENCY_UNITS, node.settings.currencyUnit], fiatConversion: node.settings.fiatConversion };
} else {
selNode = { userPersona: node.settings.userPersona, channelBackupPath: node.settings.channelBackupPath, satsToBTC: node.settings.satsToBTC, selCurrencyUnit: node.settings.currencyUnit, currencyUnits: CURRENCY_UNITS, fiatConversion: node.settings.fiatConversion };
selNode = { userPersona: node.settings.userPersona, channelBackupPath: node.settings.channelBackupPath, selCurrencyUnit: node.settings.currencyUnit, currencyUnits: CURRENCY_UNITS, fiatConversion: node.settings.fiatConversion };
}
this.store.dispatch(new RTLActions.ResetRootStore(node));
this.store.dispatch(new RTLActions.ResetLNDStore(selNode));
@ -377,8 +407,8 @@ export class RTLEffects implements OnDestroy {
handleErrorWithoutAlert(actionName: string, err: { status: number, error: any }) {
this.logger.error('ERROR IN: ' + actionName + '\n' + JSON.stringify(err));
if (err.status === 401) {
this.logger.info('Redirecting to Signin');
this.store.dispatch(new RTLActions.Signout());
this.logger.info('Redirecting to Login');
this.store.dispatch(new RTLActions.Logout());
} else {
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: actionName, code: err.status.toString(), message: err.error.error }));
}
@ -387,8 +417,8 @@ export class RTLEffects implements OnDestroy {
handleErrorWithAlert(alertType: string, alertTitle: string, errURL: string, err: { status: number, error: any }) {
this.logger.error(err);
if (err.status === 401) {
this.logger.info('Redirecting to Signin');
this.store.dispatch(new RTLActions.Signout());
this.logger.info('Redirecting to Login');
this.store.dispatch(new RTLActions.Logout());
} else {
this.store.dispatch(new RTLActions.CloseSpinner());
this.store.dispatch(new RTLActions.OpenAlert({

@ -13,8 +13,8 @@ export interface RootState {
nodeData: GetInfoRoot;
}
const initNodeSettings = { userPersona: 'OPERATOR', flgSidenavOpened: true, flgSidenavPinned: true, menu: 'VERTICAL', menuType: 'REGULAR', fontSize: 'MEDIUM', themeMode: 'DAY', themeColor: 'PURPLE', satsToBTC: false, channelBackupPath: '', selCurrencyUnit: 'USD', fiatConversion: false, currencyUnits: ['Sats', 'BTC', 'USD'] };
const initNodeAuthentication = { nodeAuthType: 'CUSTOM', configPath: '', bitcoindConfigPath: '' };
const initNodeSettings = { userPersona: 'OPERATOR', themeMode: 'DAY', themeColor: 'PURPLE', channelBackupPath: '', selCurrencyUnit: 'USD', fiatConversion: false, currencyUnits: ['Sats', 'BTC', 'USD'] };
const initNodeAuthentication = { configPath: '', bitcoindConfigPath: '' };
const initRootState: RootState = {
effectErrorsRoot: [],

@ -1 +1 @@
export const VERSION = '0.6.3-beta';
export const VERSION = '0.6.4-beta';

Loading…
Cancel
Save