Google authenticator integration PR-266

Google authenticator integration PR-266
pull/294/head^2 v0.7.0
Shahana Farooqui 4 years ago
parent cf71a4d897
commit 3a40bd9748

@ -426,6 +426,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE. THE SOFTWARE.
otplib
MIT
The MIT License (MIT)
Copyright (c) 2014 Gerald Yeo <contact@fusedthought.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
perfect-scrollbar perfect-scrollbar
MIT MIT
The MIT License (MIT) Copyright (c) 2012-2017 Hyunje Jun and other contributors The MIT License (MIT) Copyright (c) 2012-2017 Hyunje Jun and other contributors

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -12,8 +12,8 @@
<link rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5"> <link rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c"> <meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="styles.f04e677f00c07a00a61f.css"></head> <link rel="stylesheet" href="styles.807ae95794a05be97fba.css"></head>
<body> <body>
<rtl-app></rtl-app> <rtl-app></rtl-app>
<script src="runtime.f077d9a5a55b365fad3b.js" defer></script><script src="polyfills-es5.2ae7ace69949ec0a3f00.js" nomodule defer></script><script src="polyfills.3302e98effc5e50a54c2.js" defer></script><script src="main.0b4165691223413d29d4.js" defer></script></body> <script src="runtime.2cfa778f51a601dbdb7e.js" defer></script><script src="polyfills-es5.2ae7ace69949ec0a3f00.js" nomodule defer></script><script src="polyfills.3302e98effc5e50a54c2.js" defer></script><script src="main.4b92e283188428242458.js" defer></script></body>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
!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:"55621ac84bdf6c786e25",6:"83056549d4d88d5a89c1",7:"75677e99073742b9f2a4"}[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()}([]); !function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=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(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),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:"55621ac84bdf6c786e25",6:"3228fcaf0890e1d596e3",7:"6fecb498f36cf3efcace"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);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 f=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 f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -5,6 +5,7 @@ var common = {};
common.rtl_conf_file_path = ''; common.rtl_conf_file_path = '';
common.rtl_pass = ''; common.rtl_pass = '';
common.rtl_secret2fa = '';
common.rtl_sso = 0; common.rtl_sso = 0;
common.port = 3000; common.port = 3000;
common.rtl_cookie_path = ''; common.rtl_cookie_path = '';

@ -107,6 +107,7 @@ connect.validateNodeConfig = (config) => {
} else { } else {
errMsg = errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json'; errMsg = errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json';
} }
common.rtl_secret2fa = config.secret2fa;
} }
common.port = (process.env.PORT) ? connect.normalizePort(process.env.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) { if (config.nodes && config.nodes.length > 0) {

@ -31,6 +31,7 @@ exports.getRTLConfig = (req, res, next) => {
} else { } else {
const nodeConfData = JSON.parse(data); const nodeConfData = JSON.parse(data);
const sso = { rtlSSO: common.rtl_sso, logoutRedirectLink: common.logout_redirect_link }; const sso = { rtlSSO: common.rtl_sso, logoutRedirectLink: common.logout_redirect_link };
const enable2FA = !common.rtl_secret2fa ? false : true;
var nodesArr = []; var nodesArr = [];
if (common.nodes && common.nodes.length > 0) { if (common.nodes && common.nodes.length > 0) {
common.nodes.forEach((node, i) => { common.nodes.forEach((node, i) => {
@ -59,7 +60,7 @@ exports.getRTLConfig = (req, res, next) => {
authentication: authentication}) authentication: authentication})
}); });
} }
res.status(200).json({ defaultNodeIndex: nodeConfData.defaultNodeIndex, selectedNodeIndex: common.selectedNode.index, sso: sso, nodes: nodesArr }); res.status(200).json({ defaultNodeIndex: nodeConfData.defaultNodeIndex, selectedNodeIndex: common.selectedNode.index, sso: sso, enable2FA: enable2FA, nodes: nodesArr });
} }
}); });
}; };
@ -105,6 +106,26 @@ exports.updateUISettings = (req, res, next) => {
} }
}; };
exports.update2FASettings = (req, res, next) => {
var RTLConfFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.secret2fa = req.body.secret2fa;
let message = req.body.secret2fa.trim() === '' ? 'Two factor authentication disabled sucessfully.' : 'Two factor authentication enabled sucessfully.';
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
common.rtl_secret2fa = config.secret2fa;
logger.info({fileName: 'RTLConf', msg: message});
res.status(201).json({message: message});
}
catch (err) {
logger.error({fileName: 'Conf', lineNum: 102, msg: 'Updating 2FA Settings Failed!'});
res.status(500).json({
message: "Updating 2FA Settings Failed!",
error: 'Updating 2FA Settings Failed!'
});
}
};
exports.updateDefaultNode = (req, res, next) => { exports.updateDefaultNode = (req, res, next) => {
RTLConfFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json'; RTLConfFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8')); var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));

@ -3,6 +3,7 @@ var connect = require('../connect');
const jwt = require("jsonwebtoken"); const jwt = require("jsonwebtoken");
var crypto = require('crypto'); var crypto = require('crypto');
var logger = require('./logger'); var logger = require('./logger');
const otplib = require("otplib");
exports.authenticateUser = (req, res, next) => { exports.authenticateUser = (req, res, next) => {
if(+common.rtl_sso) { if(+common.rtl_sso) {
@ -49,8 +50,8 @@ exports.resetPassword = (req, res, next) => {
error: "Password cannot be reset for SSO authentication!" error: "Password cannot be reset for SSO authentication!"
}); });
} else { } else {
const oldPassword = req.body.oldPassword; const currPassword = req.body.currPassword;
if (common.rtl_pass === oldPassword) { if (common.rtl_pass === currPassword) {
common.rtl_pass = connect.replacePasswordWithHash(req.body.newPassword); common.rtl_pass = connect.replacePasswordWithHash(req.body.newPassword);
var rpcUser = 'NODE_USER'; var rpcUser = 'NODE_USER';
const token = jwt.sign( const token = jwt.sign(
@ -66,4 +67,17 @@ exports.resetPassword = (req, res, next) => {
}); });
} }
} }
};
exports.verifyToken = (req, res, next) => {
const token2fa = req.body.authentication2FA;
if (!common.rtl_secret2fa || otplib.authenticator.check(token2fa, common.rtl_secret2fa)) {
res.status(200).json({ isValidToken: true });
} else {
logger.error({fileName: 'Authenticate', lineNum: 77, msg: 'Token Verification Failed!'});
res.status(401).json({
message: "Authentication Failed!",
error: "Token Verification Failed!"
});
}
}; };

13
package-lock.json generated

@ -10700,6 +10700,14 @@
"os-tmpdir": "^1.0.0" "os-tmpdir": "^1.0.0"
} }
}, },
"otplib": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/otplib/-/otplib-11.0.1.tgz",
"integrity": "sha512-oi57teljNyWTC/JqJztHOtSGeFNDiDh5C1myd+faocUtFAX27Sm1mbx69kpEJ8/JqrblI3kAm4Pqd6tZJoOIBQ==",
"requires": {
"thirty-two": "1.0.2"
}
},
"p-defer": { "p-defer": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
@ -13388,6 +13396,11 @@
} }
} }
}, },
"thirty-two": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz",
"integrity": "sha1-TKL//AKlEpDSdEueP1V2k8prYno="
},
"through": { "through": {
"version": "2.3.8", "version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",

@ -43,6 +43,7 @@
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"ngx-perfect-scrollbar": "^8.0.0", "ngx-perfect-scrollbar": "^8.0.0",
"otplib": "^11.0.1",
"request": "^2.88.0", "request": "^2.88.0",
"request-promise": "^4.2.5", "request-promise": "^4.2.5",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",

@ -5,6 +5,7 @@ const authCheck = require("./authCheck");
router.get("/rtlconf", RTLConfController.getRTLConfig); router.get("/rtlconf", RTLConfController.getRTLConfig);
router.post("/", authCheck, RTLConfController.updateUISettings); router.post("/", authCheck, RTLConfController.updateUISettings);
router.post("/update2FA", authCheck, RTLConfController.update2FASettings);
router.get("/config/:nodeType", authCheck, RTLConfController.getConfig); router.get("/config/:nodeType", authCheck, RTLConfController.getConfig);
router.post("/updateSelNode", RTLConfController.updateSelectedNode); router.post("/updateSelNode", RTLConfController.updateSelectedNode);
router.post("/updateDefaultNode", RTLConfController.updateDefaultNode); router.post("/updateDefaultNode", RTLConfController.updateDefaultNode);

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

@ -42,6 +42,8 @@ import { AlertMessageComponent } from './shared/components/data-modal/alert-mess
import { ConfirmationMessageComponent } from './shared/components/data-modal/confirmation-message/confirmation-message.component'; import { ConfirmationMessageComponent } from './shared/components/data-modal/confirmation-message/confirmation-message.component';
import { ErrorMessageComponent } from './shared/components/data-modal/error-message/error-message.component'; import { ErrorMessageComponent } from './shared/components/data-modal/error-message/error-message.component';
import { LoopModalComponent } from './lnd/loop/loop-modal/loop-modal.component'; import { LoopModalComponent } from './lnd/loop/loop-modal/loop-modal.component';
import { TwoFactorAuthComponent } from './shared/components/data-modal/two-factor-auth/two-factor-auth.component';
import { LoginTokenComponent } from './shared/components/data-modal/login-2fa-token/login-2fa-token.component';
@NgModule({ @NgModule({
imports: [ imports: [
@ -70,7 +72,9 @@ import { LoopModalComponent } from './lnd/loop/loop-modal/loop-modal.component';
ConfirmationMessageComponent, ConfirmationMessageComponent,
ErrorMessageComponent, ErrorMessageComponent,
CloseChannelComponent, CloseChannelComponent,
LoopModalComponent LoopModalComponent,
TwoFactorAuthComponent,
LoginTokenComponent
], ],
entryComponents: [ entryComponents: [
CLInvoiceInformationComponent, CLInvoiceInformationComponent,
@ -85,7 +89,9 @@ import { LoopModalComponent } from './lnd/loop/loop-modal/loop-modal.component';
ConfirmationMessageComponent, ConfirmationMessageComponent,
ErrorMessageComponent, ErrorMessageComponent,
CloseChannelComponent, CloseChannelComponent,
LoopModalComponent LoopModalComponent,
TwoFactorAuthComponent,
LoginTokenComponent
], ],
providers: [ providers: [
{ provide: LoggerService, useClass: ConsoleLoggerService }, { provide: LoggerService, useClass: ConsoleLoggerService },

@ -7,7 +7,6 @@
<svg viewBox="0 0 108 118" version="1.1" xmlns="http://www.w3.org/2000/svg" <svg viewBox="0 0 108 118" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step01</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<g id="Loopv0.2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Loopv0.2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="LoopIn_Step01" transform="translate(-594.000000, -215.000000)" fill-rule="nonzero"> <g id="LoopIn_Step01" transform="translate(-594.000000, -215.000000)" fill-rule="nonzero">
@ -68,10 +67,10 @@
<div class="mt-6" fxLayout="column" <div class="mt-6" fxLayout="column"
[fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'" [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'"
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Loop in, Explained.</mat-card-title> <mat-card-title>Loop In explained.</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">Lightning loop is non custodial service offered by lightning labs to bridge <p class="font-size-120 h-10" fxFlex="80">Lightning Loop is a non custodial service offered by Lightning Labs to bridge
on-chain and off-chain bitcoin using submarine swaps.</p> on-chain and off-chain Bitcoin using Submarine swaps.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>
@ -82,7 +81,6 @@
<svg viewBox="0 0 200 120" version="1.1" xmlns="http://www.w3.org/2000/svg" <svg viewBox="0 0 200 120" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step02</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<defs> <defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
@ -219,9 +217,9 @@
<div class="mt-6" fxLayout="column" <div class="mt-6" fxLayout="column"
[fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'" [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'"
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Step 1: Deciding to loop in</mat-card-title> <mat-card-title>Step 1: Deciding to Loop In</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">Your outgoing capacity is depleted and you want to regain it without opening new channels.</p> <p class="font-size-120 h-10" fxFlex="80">Your outgoing capacity is depleted and you want to regain it without opening new channels.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>
@ -232,7 +230,6 @@
<svg viewBox="0 0 364 120" version="1.1" xmlns="http://www.w3.org/2000/svg" <svg viewBox="0 0 364 120" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step03</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<defs> <defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="8.86848147e-15%" id="linearGradient-1"> <linearGradient x1="50%" y1="100%" x2="50%" y2="8.86848147e-15%" id="linearGradient-1">
@ -428,7 +425,7 @@
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Step 2: Send on-chain payment out</mat-card-title> <mat-card-title>Step 2: Send on-chain payment out</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">Your node sends funds on-chain to loop server to be swapped with off-chain liquidity.</p> <p class="font-size-120 h-10" fxFlex="80">Your node sends funds on-chain to loop server to be swapped with off-chain liquidity.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>
@ -439,7 +436,6 @@
<svg viewBox="0 0 278 118" version="1.1" xmlns="http://www.w3.org/2000/svg" <svg viewBox="0 0 278 118" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step04</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<g id="Loopv0.3" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Loopv0.3" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="LoopIn_Step04" transform="translate(-1799.000000, -756.000000)"> <g id="LoopIn_Step04" transform="translate(-1799.000000, -756.000000)">
@ -546,7 +542,7 @@
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Step 3: Recieve Funds Off-chain</mat-card-title> <mat-card-title>Step 3: Recieve Funds Off-chain</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">Loop server sends equivalent funds off-chain to your node by making a lightning payment to you.</p> <p class="font-size-120 h-10" fxFlex="80">Loop server sends equivalent funds off-chain to your node by making a lightning payment to you.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>
@ -557,7 +553,6 @@
<svg viewBox="0 0 205 121" version="1.1" xmlns="http://www.w3.org/2000/svg" <svg viewBox="0 0 205 121" version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step05</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<defs> <defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
@ -659,8 +654,8 @@
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Done!</mat-card-title> <mat-card-title>Done!</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">You send the payment on-chain from your wallet and also moved remote <p class="font-size-120 h-10" fxFlex="80">You send the payment on-chain from your wallet and also move remote
balance to the local side of the node.</p> balance to the local side of the node, gaining outgoing capacity.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>

@ -42,7 +42,7 @@
<mat-error *ngIf="inputFormGroup.controls.routingFeePercent.errors?.min">Percentage must be a positive number.</mat-error> <mat-error *ngIf="inputFormGroup.controls.routingFeePercent.errors?.min">Percentage must be a positive number.</mat-error>
<mat-error *ngIf="inputFormGroup.controls.routingFeePercent.errors?.max">Percentage must be less than or equal to {{maxRoutingFeePercentage}}.</mat-error> <mat-error *ngIf="inputFormGroup.controls.routingFeePercent.errors?.max">Percentage must be less than or equal to {{maxRoutingFeePercentage}}.</mat-error>
</mat-form-field> </mat-form-field>
<mat-slide-toggle *ngIf="direction === swapTypeEnum.LOOP_OUT" fxFlex="15" tabindex="4" color="primary" formControlName="fast">Fast</mat-slide-toggle> <mat-slide-toggle matTooltip="Swap immediately (Might end up paying a higher on-chain fee)" *ngIf="direction === swapTypeEnum.LOOP_OUT" fxFlex="15" tabindex="4" color="primary" formControlName="fast">Fast</mat-slide-toggle>
</div> </div>
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100"> <div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
<button mat-stroked-button color="primary" tabindex="5" type="button" (click)="onEstimateQuote()">Estimate Quote</button> <button mat-stroked-button color="primary" tabindex="5" type="button" (click)="onEstimateQuote()">Estimate Quote</button>
@ -121,16 +121,18 @@
<rtl-loop-out-info-graphics *ngIf="direction === swapTypeEnum.LOOP_OUT" [(stepNumber)]="stepNumber" [animationDirection]="animationDirection"></rtl-loop-out-info-graphics> <rtl-loop-out-info-graphics *ngIf="direction === swapTypeEnum.LOOP_OUT" [(stepNumber)]="stepNumber" [animationDirection]="animationDirection"></rtl-loop-out-info-graphics>
<rtl-loop-in-info-graphics *ngIf="direction === swapTypeEnum.LOOP_IN" [(stepNumber)]="stepNumber" [animationDirection]="animationDirection"></rtl-loop-in-info-graphics> <rtl-loop-in-info-graphics *ngIf="direction === swapTypeEnum.LOOP_IN" [(stepNumber)]="stepNumber" [animationDirection]="animationDirection"></rtl-loop-in-info-graphics>
</mat-card-content> </mat-card-content>
<div class="my-4" fxLayout="row" fxFlex="10" fxLayoutAlign="center end"> <div class="my-3" fxLayout="row" fxFlex="10" fxLayoutAlign="center end">
<span *ngFor="let i of [1, 2, 3, 4, 5];" (click) = "onStepChanged(i)" fxLayoutAlign="center center" class="dots-stepper-block"> <span *ngFor="let i of [1, 2, 3, 4, 5];" (click) = "onStepChanged(i)" fxLayoutAlign="center center" class="dots-stepper-block">
<p class="dot tiny-dot mr-0" [ngClass]="{'dot-primary': stepNumber === i, 'dot-primary-lighter': stepNumber !== i}"></p> <p class="dot tiny-dot mr-0" [ngClass]="{'dot-primary': stepNumber === i, 'dot-primary-lighter': stepNumber !== i}"></p>
</span> </span>
</div> </div>
<div fxLayout="row" fxFlex="15" fxLayoutAlign="end end" class="mt-2"> <div fxLayout="row" fxFlex="15" fxLayoutAlign="end end" class="mt-2">
<button *ngIf="stepNumber === 5" mat-stroked-button class="mr-1" color="primary" tabindex="15" type="button" (click)="onReadMore()">Read More</button> <button *ngIf="stepNumber === 5" mat-stroked-button class="mr-1" color="primary" tabindex="15" type="button" (click)="onReadMore()">Read More</button>
<button *ngIf="stepNumber === 5" mat-flat-button color="primary" tabindex="16" type="button" (click)="flgShowInfo=false;stepNumber=1;">Close</button> <button *ngIf="stepNumber === 5" mat-flat-button class="mr-1" color="primary" tabindex="16" type="button" (click)="onStepChanged(4)">Back</button>
<button *ngIf="stepNumber < 5" mat-stroked-button class="mr-1" color="primary" tabindex="17" type="button" (click)="flgShowInfo=false;stepNumber=1;">Close</button> <button *ngIf="stepNumber === 5" mat-flat-button color="primary" tabindex="17" type="button" (click)="flgShowInfo=false;stepNumber=1;">Close</button>
<button *ngIf="stepNumber < 5" mat-flat-button color="primary" tabindex="18" type="button" (click)="onStepChanged(stepNumber + 1)">Next</button> <button *ngIf="stepNumber < 5" mat-stroked-button class="mr-1" color="primary" tabindex="18" type="button" (click)="flgShowInfo=false;stepNumber=1;">Close</button>
<button *ngIf="stepNumber > 1 && stepNumber < 5" mat-flat-button class="mr-1" color="primary" tabindex="19" type="button" (click)="onStepChanged(stepNumber - 1)">Back</button>
<button *ngIf="stepNumber < 5" mat-flat-button color="primary" tabindex="20" type="button" (click)="onStepChanged(stepNumber + 1)">Next</button>
</div> </div>
</div> </div>
</div> </div>

@ -5,7 +5,6 @@
<div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection"> <div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection">
<svg viewBox="0 0 108 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg viewBox="0 0 108 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step01</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<g id="Loopv0.2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Loopv0.2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="LoopOut_Step01" transform="translate(-594.000000, -215.000000)" fill-rule="nonzero"> <g id="LoopOut_Step01" transform="translate(-594.000000, -215.000000)" fill-rule="nonzero">
@ -53,10 +52,10 @@
<div class="mt-6" fxLayout="column" <div class="mt-6" fxLayout="column"
[fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'" [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'"
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Loop out, Explained.</mat-card-title> <mat-card-title>Loop Out explained.</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">Lightning loop is non custodial service offered by lightning labs to bridge <p class="font-size-120 h-10" fxFlex="80">Lightning Loop is a non custodial service offered by Lightning Labs to bridge
on-chain and off-chain bitcoin using submarine swaps.</p> on-chain and off-chain Bitcoin using Submarine swaps.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>
@ -65,7 +64,6 @@
<div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection"> <div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection">
<svg viewBox="0 0 205 121" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg viewBox="0 0 205 121" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step02</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<defs> <defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
@ -152,9 +150,9 @@
<div class="mt-6" fxLayout="column" <div class="mt-6" fxLayout="column"
[fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'" [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'"
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Step 1: Deciding to loop out</mat-card-title> <mat-card-title>Step 1: Deciding to Loop Out</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">You have a channel with a local balance amount you want to loop out.</p> <p class="font-size-120 h-10" fxFlex="80">You have a channel with a local balance amount and you want to gain inbound liquidity.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>
@ -163,7 +161,6 @@
<div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection"> <div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection">
<svg viewBox="0 0 373 121" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg viewBox="0 0 373 121" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step03</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<defs> <defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="8.86848147e-15%" id="linearGradient-1"> <linearGradient x1="50%" y1="100%" x2="50%" y2="8.86848147e-15%" id="linearGradient-1">
@ -308,8 +305,8 @@
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Step 2: Send lightning payment</mat-card-title> <mat-card-title>Step 2: Send lightning payment</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">Your node pays a lightning invoice for that amount generated by the loop <p class="font-size-120 h-10" fxFlex="80">Your node pays a lightning invoice for the amount requested via the loop
service. Moving that local balance, to the remote side of the channel.</p> service. This moves the local balance, for the amount paid, to the remote side of the channel.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>
@ -318,7 +315,6 @@
<div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection"> <div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection">
<svg viewBox="0 0 278 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg viewBox="0 0 278 118" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step04</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<g id="Loopv0.2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="Loopv0.2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="LoopOut_Step04" transform="translate(-503.000000, -212.000000)"> <g id="LoopOut_Step04" transform="translate(-503.000000, -212.000000)">
@ -420,7 +416,7 @@
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Step 3: Receive funds back on-chain</mat-card-title> <mat-card-title>Step 3: Receive funds back on-chain</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">Loop service then sends you a payment on-chain for that same amount.</p> <p class="font-size-120 h-10" fxFlex="80">Loop service then sends you a payment on-chain for the amount same as the lightning payment minus the fee.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>
@ -429,7 +425,6 @@
<div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection"> <div fxLayout="column" fxFlex="100" class="step-container" (swipe)="onSwipe($event)" [@sliderAnimation]="animationDirection">
<svg viewBox="0 0 200 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg viewBox="0 0 200 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com --> <!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step05</title>
<desc>Created with Sketch.</desc> <desc>Created with Sketch.</desc>
<defs> <defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1"> <linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
@ -557,8 +552,8 @@
fxLayoutAlign="start center"> fxLayoutAlign="start center">
<mat-card-title>Done!</mat-card-title> <mat-card-title>Done!</mat-card-title>
<mat-card-subtitle fxLayout="row" fxLayoutAlign="center start"> <mat-card-subtitle fxLayout="row" fxLayoutAlign="center start">
<p class="font-size-120" fxFlex="80">You receive the payment on-chain in your wallet and also moved local <p class="font-size-120 h-10" fxFlex="80">Final settlement occurs when your node sweeps the on-chain payment and the loop server settles the lightning invoice. You receive the payment on-chain in your wallet and also move local
balance to the remote side of the channel.</p> balance to the remote side of the channel, gaining inbound capacity.</p>
</mat-card-subtitle> </mat-card-subtitle>
</div> </div>
</div> </div>

@ -15,7 +15,7 @@
<ng-template #quoteDetailsBlock> <ng-template #quoteDetailsBlock>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch"> <div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
<div fxLayout="row"> <div fxLayout="row">
<div fxFlex="30" matTooltip="The fee that the swap server is charging for the swap"> <div fxFlex="30" matTooltip="Estimated fee charged by the loop server for the swap">
<h4 fxLayoutAlign="start" class="font-bold-500">Swap Fee (Sats)</h4> <h4 fxLayoutAlign="start" class="font-bold-500">Swap Fee (Sats)</h4>
<span class="foreground-secondary-text">{{quote?.swap_fee | number}}</span> <span class="foreground-secondary-text">{{quote?.swap_fee | number}}</span>
</div> </div>
@ -41,7 +41,7 @@
</div> </div>
<mat-divider class="w-100 my-1" *ngIf="quote?.swap_payment_dest !== ''"></mat-divider> <mat-divider class="w-100 my-1" *ngIf="quote?.swap_payment_dest !== ''"></mat-divider>
<div fxLayout="row" *ngIf="quote?.swap_payment_dest !== ''"> <div fxLayout="row" *ngIf="quote?.swap_payment_dest !== ''">
<div fxFlex="100" matTooltip="The node pubkey where the swap payment needs to be paid to"> <div fxFlex="100" matTooltip="The node pubkey, where the swap payments will be sent">
<h4 fxLayoutAlign="start" class="font-bold-500">Swap Server Node Pubkey</h4> <h4 fxLayoutAlign="start" class="font-bold-500">Swap Server Node Pubkey</h4>
<span class="foreground-secondary-text">{{quote?.swap_payment_dest}}</span> <span class="foreground-secondary-text">{{quote?.swap_payment_dest}}</span>
</div> </div>

@ -0,0 +1,22 @@
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="space-between stretch">
<div fxFlex="100" class="padding-gap-large pl-3">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header mb-2">
<div fxFlex="95" fxLayoutAlign="start start">
<span class="page-title">Two Factor Token</span>
</div>
<button tabindex="3" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" (click)="onClose()" mat-button>X</button>
</mat-card-header>
<mat-card-content fxLayout="row" class="pr-1">
<form (ngSubmit)="onVerifyToken()" #tokenForm="ngForm" fxLayout="column" fxFlex="100">
<mat-form-field>
<input autoFocus matInput placeholder="Token" type="text" id="token" name="token" [(ngModel)]="token" tabindex="2" required>
<mat-error *ngIf="!token">Token is required.</mat-error>
</mat-form-field>
<p *ngIf="tokenErrorMessage !== ''" fxFlex="100" class="color-warn" fxLayoutAlign="start center"><mat-icon class="mr-1 icon-small">close</mat-icon>{{tokenErrorMessage}}</p>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="end center" class="mt-2">
<button mat-flat-button color="primary" tabindex="4" type="submit">Verify Token</button>
</div>
</form>
</mat-card-content>
</div>
</div>

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

@ -0,0 +1,59 @@
import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
import { faUserClock } from '@fortawesome/free-solid-svg-icons';
import { LoginTokenData } from '../../../models/alertData';
import * as fromRTLReducer from '../../../../store/rtl.reducers';
import * as RTLActions from '../../../../store/rtl.actions';
@Component({
selector: 'rtl-login-token',
templateUrl: './login-2fa-token.component.html',
styleUrls: ['./login-2fa-token.component.scss']
})
export class LoginTokenComponent implements OnInit, OnDestroy {
public faUserClock = faUserClock;
public token = '';
public tokenErrorMessage = '';
public authRes = { token: '' };
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<LoginTokenComponent>, @Inject(MAT_DIALOG_DATA) public data: LoginTokenData, private store: Store<fromRTLReducer.RTLState>) { }
ngOnInit() {
this.authRes = this.data.authRes;
this.tokenErrorMessage = '';
this.store.select('root')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsRoot.forEach(effectsErr => {
if (effectsErr.action === 'VerifyToken') {
this.tokenErrorMessage = this.tokenErrorMessage + effectsErr.message + ' ';
}
});
});
}
onClose() {
this.dialogRef.close(false);
}
onVerifyToken() {
if (!this.token) { return true; }
this.tokenErrorMessage = '';
this.store.dispatch(new RTLActions.VerifyTwoFA({token: this.token, authResponse: this.authRes}));
}
ngOnDestroy() {
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('VerifyToken'));
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -10,7 +10,7 @@
</div> </div>
<button tabindex="3" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" (click)="onClose()" mat-button>X</button> <button tabindex="3" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" (click)="onClose()" mat-button>X</button>
</mat-card-header> </mat-card-header>
<mat-card-content class="pr-2"> <mat-card-content class="pr-1">
<div fxLayout="column"> <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}"> <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}}" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode> <qrcode qrdata="{{selInfoType.infoID === 1 ? information.uris[0] : information.identity_pubkey}}" [margin]="2" [width]="qrWidth" [errorCorrectionLevel]="'L'" [allowEmptyString]="true"></qrcode>

@ -0,0 +1,90 @@
<div fxLayout="row">
<div fxFlex="100" class="padding-gap-large">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
<div fxFlex="95" fxLayoutAlign="start start" class="padding-gap-x-large"><span class="page-title">Setup Two Factor Authentication</span></div>
<button tabindex="15" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" [mat-dialog-close]="false" mat-button>X</button>
</mat-card-header>
<mat-card-content class="mt-5px">
<div fxLayout="column">
<mat-vertical-stepper [linear]="true" #stepper (selectionChange)="stepSelectionChanged($event)">
<mat-step [stepControl]="passwordFormGroup" [editable]="flgEditable">
<form [formGroup]="passwordFormGroup" fxLayout="column" fxLayoutAlign="space-between" class="my-1 pr-1">
<ng-template matStepLabel>{{passwordFormLabel}}</ng-template>
<div fxLayout="row">
<mat-form-field fxFlex="100">
<input autoFocus matInput placeholder="Password" type="password" tabindex="1" formControlName="password" required>
<mat-error *ngIf="passwordFormGroup.controls.password.errors?.required">Password is required.</mat-error>
</mat-form-field>
</div>
<div class="mt-2" fxLayout="row">
<button mat-stroked-button color="primary" tabindex="3" type="button" (click)="onAuthenticate()">Confirm</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="secretFormGroup" [editable]="flgEditable" *ngIf="!showDisableStepper">
<form [formGroup]="secretFormGroup" fxLayout="column" class="my-1 pr-1">
<ng-template matStepLabel disabled="true">{{secretFormLabel}}</ng-template>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start">
<qrcode [qrdata]="otpauth" [margin]="1" [width]="180" [errorCorrectionLevel]="'L'"></qrcode>
</div>
<div fxFlex="100" class="w-100 alert alert-info">
<fa-icon [icon]="faInfoCircle" class="mt-1 mr-1 alert-icon"></fa-icon>
<span>You can use a compatible authentication app to get an authentication code when you log in to RTL. e.g.: Google Authenticator.</span>
</div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between stretch">
<mat-form-field fxFlex="100">
<input autoFocus matInput placeholder="Secret Code" type="text" tabindex="4" formControlName="secret" required>
<fa-icon [icon]="faCopy" matSuffix rtlClipboard [payload]="secretFormGroup.controls.secret.value" (copied)="onCopySecret($event)"></fa-icon>
<mat-error *ngIf="secretFormGroup.controls.secret.errors?.required">Secret Code is required.</mat-error>
</mat-form-field>
</div>
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
<button mat-stroked-button color="primary" tabindex="6" type="button" matStepperNext>Next</button>
</div>
</form>
</mat-step>
<mat-step [stepControl]="tokenFormGroup" *ngIf="!showDisableStepper">
<form [formGroup]="tokenFormGroup" fxLayout="column" fxLayoutAlign="start" class="my-1 pr-1">
<ng-template matStepLabel>{{tokenFormLabel}}</ng-template>
<div fxLayout="column" *ngIf="!flgValidated || !isTokenValid">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between stretch">
<mat-form-field fxFlex="100">
<input autoFocus matInput placeholder="Token" type="text" tabindex="7" formControlName="token" required>
<mat-error *ngIf="tokenFormGroup.controls.token.errors?.required">Token is required.</mat-error>
<mat-error *ngIf="tokenFormGroup.controls.token.errors?.notValid">Token is invalid.</mat-error>
</mat-form-field>
</div>
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
<button mat-stroked-button color="primary" tabindex="8" type="button" (click)="onVerifyToken()">{{tokenFormGroup.controls.token.errors.notValid ? 'Retry' : 'Verify'}}</button>
</div>
</div>
<div *ngIf="flgValidated && isTokenValid">
<strong>Success! You are all set.</strong>
</div>
</form>
</mat-step>
<mat-step [stepControl]="disableFormGroup" *ngIf="showDisableStepper">
<form [formGroup]="disableFormGroup" fxLayout="column" fxLayoutAlign="start" class="my-1 pr-1">
<ng-template matStepLabel>{{disableFormLabel}}</ng-template>
<div fxLayout="column" *ngIf="!flgValidated || !isTokenValid">
<div fxFlex="100" class="w-100 alert alert-warn">
<fa-icon [icon]="faExclamationTriangle" class="mt-1 mr-1 alert-icon"></fa-icon>
<span>You are about to disable two-factor authentication security from RTL. Are you sure you want to turn it off?</span>
</div>
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
<button mat-stroked-button color="primary" tabindex="8" type="button" (click)="onVerifyToken()">Disable</button>
</div>
</div>
<div *ngIf="flgValidated && isTokenValid">
<strong>Two factor authentication removed from RTL.</strong>
</div>
</form>
</mat-step>
</mat-vertical-stepper>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="end center">
<button mat-stroked-button color="primary" tabindex="12" type="button" [mat-dialog-close]="false" default>{{flgValidated && isTokenValid ? 'Close' : 'Cancel'}}</button>
</div>
</div>
</mat-card-content>
</div>
</div>

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

@ -0,0 +1,143 @@
import { Component, OnInit, OnDestroy, Inject, ViewChild } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { MAT_DIALOG_DATA, MatDialogRef, MatVerticalStepper, MatSnackBar } from '@angular/material';
import { authenticator } from 'otplib/otplib-browser';
import { faInfoCircle, faCopy, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import * as sha256 from 'sha256';
import { RTLConfiguration } from '../../../models/RTLconfig';
import { AuthConfig } from '../../../models/alertData';
import { RTLEffects } from '../../../../store/rtl.effects';
import * as fromRTLReducer from '../../../../store/rtl.reducers';
import * as RTLActions from '../../../../store/rtl.actions';
@Component({
selector: 'rtl-two-factor-auth',
templateUrl: './two-factor-auth.component.html',
styleUrls: ['./two-factor-auth.component.scss']
})
export class TwoFactorAuthComponent implements OnInit, OnDestroy {
@ViewChild('twoFAForm', { static: true }) twoFAForm: any;
@ViewChild('stepper', { static: false }) stepper: MatVerticalStepper;
public faExclamationTriangle = faExclamationTriangle;
public faCopy = faCopy;
public faInfoCircle = faInfoCircle;
public flgValidated = false;
public isTokenValid = true;
public otpauth: string;
public appConfig: RTLConfiguration;
public flgEditable = true;
public showDisableStepper = false;
public passwordFormLabel = 'Authenticate with your RTL password';
public secretFormLabel = 'Scan or copy the secret';
public tokenFormLabel = 'Verify your authentication is working';
public disableFormLabel = 'Disable two factor authentication';
passwordFormGroup: FormGroup;
secretFormGroup: FormGroup;
tokenFormGroup: FormGroup;
disableFormGroup: FormGroup;
unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<TwoFactorAuthComponent>, @Inject(MAT_DIALOG_DATA) public data: AuthConfig, private store: Store<fromRTLReducer.RTLState>, private formBuilder: FormBuilder, private rtlEffects: RTLEffects, private snackBar: MatSnackBar) {}
ngOnInit() {
this.appConfig = this.data.appConfig;
this.showDisableStepper = !!this.appConfig.enable2FA;
this.passwordFormGroup = this.formBuilder.group({
hiddenPassword: ['', [Validators.required]],
password: ['', [Validators.required]]
});
this.secretFormGroup = this.formBuilder.group({
secret: [{value: !this.appConfig.enable2FA ? this.generateSecret() : '', disabled: true}, Validators.required]
});
this.tokenFormGroup = this.formBuilder.group({
token: ['', Validators.required]
});
this.disableFormGroup = this.formBuilder.group({});
}
generateSecret() {
let secret2fa = authenticator.generateSecret();
this.otpauth = authenticator.keyuri('', 'Ride The Lightning (RTL)', secret2fa);
return secret2fa;
}
onAuthenticate() {
if (!this.passwordFormGroup.controls.password.value) { return true; }
this.flgValidated = false;
this.store.dispatch(new RTLActions.IsAuthorized(sha256(this.passwordFormGroup.controls.password.value)));
this.rtlEffects.isAuthorizedRes
.pipe(take(1))
.subscribe(authRes => {
if (authRes !== 'ERROR') {
this.passwordFormGroup.controls.hiddenPassword.setValue(this.passwordFormGroup.controls.password.value);
this.stepper.next();
} else {
this.dialogRef.close();
this.snackBar.open('Unauthorized User. Logging out from RTL.');
}
});
}
onCopySecret(payload: string) {
this.snackBar.open('Secret code ' + this.secretFormGroup.controls.secret.value + ' copied.');
}
onVerifyToken() {
if (this.appConfig.enable2FA) {
this.store.dispatch(new RTLActions.OpenSpinner('Updating Settings...'));
this.store.dispatch(new RTLActions.TwoFASaveSettings({secret2fa: ''}));
this.generateSecret();
this.isTokenValid = true;
} else {
if (!this.tokenFormGroup.controls.token.value) { return true; }
this.isTokenValid = authenticator.check(this.tokenFormGroup.controls.token.value, this.secretFormGroup.controls.secret.value);
if (!this.isTokenValid) {
this.tokenFormGroup.controls.token.setErrors({ notValid: true });
return true;
}
this.store.dispatch(new RTLActions.OpenSpinner('Updating Settings...'));
this.store.dispatch(new RTLActions.TwoFASaveSettings({secret2fa: this.secretFormGroup.controls.secret.value}));
this.tokenFormGroup.controls.token.setValue('');
}
this.flgValidated = true;
this.appConfig.enable2FA = !this.appConfig.enable2FA;
}
stepSelectionChanged(event: any) {
switch (event.selectedIndex) {
case 0:
this.passwordFormLabel = 'Authenticate with your RTL password';
break;
case 1:
this.passwordFormLabel = 'User authenticated successfully';
break;
case 2:
this.passwordFormLabel = 'User authenticated successfully';
break;
default:
this.passwordFormLabel = 'Authenticate with your RTL password';
break;
}
if (event.selectedIndex < event.previouslySelectedIndex) {
if (event.selectedIndex === 0) {
this.passwordFormGroup.controls.hiddenPassword.setValue('');
}
}
}
ngOnDestroy() {
this.unSubs.forEach(unsub => {
unsub.next();
unsub.complete();
});
}
}

@ -11,9 +11,10 @@
<input autoFocus matInput placeholder="Password" type="password" id="password" name="password" [(ngModel)]="password" tabindex="1" required> <input autoFocus matInput placeholder="Password" type="password" id="password" name="password" [(ngModel)]="password" tabindex="1" required>
<mat-error *ngIf="!password">Password is required.</mat-error> <mat-error *ngIf="!password">Password is required.</mat-error>
</mat-form-field> </mat-form-field>
<p *ngIf="loginErrorMessage !== ''" fxFlex="100" class="color-warn" fxLayoutAlign="start center"><mat-icon class="mr-1 icon-small">close</mat-icon>{{loginErrorMessage}}</p>
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch" class="mt-2"> <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="2" type="reset" (click)="resetData()">Clear</button> <button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="3" type="reset" (click)="resetData()">Clear</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-flat-button color="primary" tabindex="3" type="submit">Login</button> <button fxFlex="48" fxLayoutAlign="center center" mat-flat-button color="primary" tabindex="4" type="submit">Login</button>
</div> </div>
</form> </form>
</mat-card-content> </mat-card-content>

@ -3,11 +3,13 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import * as sha256 from 'sha256'; import * as sha256 from 'sha256';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { faUnlockAlt } from '@fortawesome/free-solid-svg-icons'; import { faUnlockAlt } from '@fortawesome/free-solid-svg-icons';
import { ConfigSettingsNode } from '../../models/RTLconfig'; import { LoginTokenComponent } from '../data-modal/login-2fa-token/login-2fa-token.component';
import { ConfigSettingsNode, RTLConfiguration } from '../../models/RTLconfig';
import { LoggerService } from '../../services/logger.service'; import { LoggerService } from '../../services/logger.service';
import { RTLEffects } from '../../../store/rtl.effects';
import * as fromRTLReducer from '../../../store/rtl.reducers'; import * as fromRTLReducer from '../../../store/rtl.reducers';
import * as RTLActions from '../../../store/rtl.actions'; import * as RTLActions from '../../../store/rtl.actions';
@ -19,38 +21,60 @@ import * as RTLActions from '../../../store/rtl.actions';
export class LoginComponent implements OnInit, OnDestroy { export class LoginComponent implements OnInit, OnDestroy {
public faUnlockAlt = faUnlockAlt; public faUnlockAlt = faUnlockAlt;
public selNode: ConfigSettingsNode; public selNode: ConfigSettingsNode;
public appConfig: RTLConfiguration;
public password = ''; public password = '';
public rtlSSO = 0; public rtlSSO = 0;
public rtlCookiePath = ''; public rtlCookiePath = '';
public accessKey = ''; public accessKey = '';
public loginErrorMessage = '';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()]; constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects) { }
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>) { }
ngOnInit() { ngOnInit() {
this.store.select('root') this.store.select('root')
.pipe(takeUntil(this.unsub[0])) .pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => { .subscribe((rtlStore) => {
rtlStore.effectErrorsRoot.forEach(effectsErr => { rtlStore.effectErrorsRoot.forEach(effectsErr => {
if (effectsErr.action === 'Login' || effectsErr.action === 'IsAuthorized') {
this.loginErrorMessage = this.loginErrorMessage + effectsErr.message + ' ';
}
this.logger.error(effectsErr); this.logger.error(effectsErr);
}); });
this.selNode = rtlStore.selNode; this.selNode = rtlStore.selNode;
this.appConfig = rtlStore.appConfig;
this.logger.info(rtlStore); this.logger.info(rtlStore);
}); });
this.rtlEffects.isAuthorizedRes
.pipe(takeUntil(this.unSubs[1]))
.subscribe(authRes => {
if (authRes !== 'ERROR') {
this.store.dispatch(new RTLActions.OpenAlert({ maxWidth: '35rem', data: {
authRes: authRes,
component: LoginTokenComponent
}}));
}
});
} }
onLogin() { onLogin() {
if(!this.password) { return true; } if(!this.password) { return true; }
this.store.dispatch(new RTLActions.Login({password: sha256(this.password), initialPass: this.password === 'password'})); this.loginErrorMessage = '';
if (this.appConfig.enable2FA) {
this.store.dispatch(new RTLActions.IsAuthorized(sha256(this.password)));
} else {
this.store.dispatch(new RTLActions.Login({password: sha256(this.password), initialPass: this.password === 'password'}));
}
} }
resetData() { resetData() {
this.password = ''; this.password = '';
this.loginErrorMessage = '';
} }
ngOnDestroy() { ngOnDestroy() {
this.unsub.forEach(completeSub => { this.unSubs.forEach(completeSub => {
completeSub.next(); completeSub.next();
completeSub.complete(); completeSub.complete();
}); });

@ -70,10 +70,10 @@
</div> </div>
</div> </div>
</div> </div>
<div fxLayout="row" fxFlex="100" class="mt-2"> <div fxLayout="row">
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch"> <div fxLayout="row" fxLayoutAlign="start start" class="mt-2">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" (click)="onResetSettings()" tabindex="10">Reset</button> <button mat-stroked-button color="primary" (click)="onResetSettings()" tabindex="10" class="mr-1">Reset</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-flat-button color="primary" (click)="onUpdateSettings()" tabindex="11">Update</button> <button mat-flat-button color="primary" (click)="onUpdateSettings()" tabindex="11">Update</button>
</div> </div>
</div> </div>
</form> </form>

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -39,7 +39,6 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
public screenSize = ''; public screenSize = '';
public screenSizeEnum = ScreenSizeEnum; public screenSizeEnum = ScreenSizeEnum;
unSubs: Array<Subject<void>> = [new Subject(), new Subject()]; unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
@Output('done') done: EventEmitter<void> = new EventEmitter();
constructor(private logger: LoggerService, private commonService: CommonService, private store: Store<fromRTLReducer.RTLState>) { constructor(private logger: LoggerService, private commonService: CommonService, private store: Store<fromRTLReducer.RTLState>) {
this.screenSize = this.commonService.getScreenSize(); this.screenSize = this.commonService.getScreenSize();
@ -92,7 +91,6 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
this.store.dispatch(new RTLActions.SaveSettings({settings: this.selNode.settings, defaultNodeIndex: defaultNodeIndex})); 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, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation, swapServerUrl: this.selNode.settings.swapServerUrl})); 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, lnImplementation: this.selNode.lnImplementation, swapServerUrl: this.selNode.settings.swapServerUrl}));
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, lnImplementation: this.selNode.lnImplementation, swapServerUrl: this.selNode.settings.swapServerUrl})); 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, lnImplementation: this.selNode.lnImplementation, swapServerUrl: this.selNode.settings.swapServerUrl}));
this.done.emit();
} }
onResetSettings() { onResetSettings() {

@ -1,30 +1,42 @@
<div fxLayout="column" fxFlex="100" class="overflow-x-hidden"> <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"> <form (ngSubmit)="onChangePassword()" fxLayout="column" fxLayoutAlign="start stretch" class="settings-container page-sub-title-container mt-1" #authForm="ngForm">
<div fxFlex="100" class="mb-1"> <div fxFlex="100">
<fa-icon [icon]="faKey" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faUserLock" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Reset Password</span> <span class="page-title">Password</span>
</div> </div>
<div fxLayout="row"> <div fxLayout="row">
<div fxLayout="column" fxFlex="100" fxFlex.gt-sm="100" fxLayoutAlign="space-between stretch" class="mt-2"> <div fxLayout="column" fxFlex="100" fxFlex.gt-sm="100" fxLayoutAlign="space-between stretch">
<mat-form-field> <mat-form-field>
<input matInput placeholder="Old Password" type="password" id="oldpassword" name="oldpassword" [(ngModel)]="oldPassword" tabindex="1" required> <input matInput placeholder="Current Password" type="password" id="currpassword" name="currpassword" [(ngModel)]="currPassword" tabindex="4" required>
<mat-error *ngIf="!oldPassword">Old password is required.</mat-error> <mat-error *ngIf="!currPassword">Current password is required.</mat-error>
</mat-form-field> </mat-form-field>
<mat-divider [inset]="true"></mat-divider>
<mat-form-field> <mat-form-field>
<input matInput placeholder="New Password" type="password" id="newpassword" name="newpassword" [(ngModel)]="newPassword" tabindex="2" required> <input matInput placeholder="New Password" type="password" id="newpassword" name="newpassword" [(ngModel)]="newPassword" tabindex="5" required>
<mat-error *ngIf="matchOldAndNewPasswords()">{{errorMsg}}</mat-error> <mat-error *ngIf="matchOldAndNewPasswords()">{{errorMsg}}</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field> <mat-form-field>
<input matInput placeholder="Confirm New Password" type="password" id="confirmpassword" name="confirmpassword" [(ngModel)]="confirmPassword" tabindex="3" required> <input matInput placeholder="New Password" type="password" id="confirmpassword" name="confirmpassword" [(ngModel)]="confirmPassword" tabindex="6" required>
<mat-error *ngIf="matchNewPasswords()">{{errorConfirmMsg}}</mat-error> <mat-error *ngIf="matchNewPasswords()">{{errorConfirmMsg}}</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
</div> </div>
<div fxLayout="row"> <div fxLayout="row">
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch" class="mt-2"> <div fxLayout="row" fxLayoutAlign="start start" class="mt-1">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="4" type="reset" (click)="resetData()">Clear</button> <button mat-flat-button color="primary" tabindex="8" type="submit">Change Password</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-flat-button color="primary" tabindex="5" type="submit">Reset Password</button>
</div> </div>
</div> </div>
</form> </form>
<mat-divider class="my-2" [inset]="true"></mat-divider>
<div fxFlex="100" class="mb-1 settings-container page-sub-title-container mt-1">
<fa-icon [icon]="faUserClock" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Two Factor Authentication</span>
</div>
<div fxFlex="100" class="w-100 alert alert-info">
<fa-icon [icon]="faInfoCircle" class="mt-1 mr-1 alert-icon"></fa-icon>
<span>Protect your account from unauthorized access by requiring a second authentication method in addition to your password.</span>
</div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between start" class="mt-1">
<button mat-flat-button color="primary" tabindex="3" (click)="on2FAuth()">{{appConfig.enable2FA ? 'Disable 2FA' : 'Enable 2FA'}}</button>
</div>
</div> </div>

@ -1,8 +1,14 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { faKey } from '@fortawesome/free-solid-svg-icons'; import { faUserLock, faUserClock, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import * as sha256 from 'sha256'; import * as sha256 from 'sha256';
import { TwoFactorAuthComponent } from '../../data-modal/two-factor-auth/two-factor-auth.component';
import { RTLConfiguration } from '../../../models/RTLconfig';
import { LoggerService } from '../../../services/logger.service';
import * as fromRTLReducer from '../../../../store/rtl.reducers'; import * as fromRTLReducer from '../../../../store/rtl.reducers';
import * as RTLActions from '../../../../store/rtl.actions'; import * as RTLActions from '../../../../store/rtl.actions';
@ -11,22 +17,33 @@ import * as RTLActions from '../../../../store/rtl.actions';
templateUrl: './auth-settings.component.html', templateUrl: './auth-settings.component.html',
styleUrls: ['./auth-settings.component.scss'] styleUrls: ['./auth-settings.component.scss']
}) })
export class AuthSettingsComponent implements OnInit { export class AuthSettingsComponent implements OnInit, OnDestroy {
@ViewChild('authForm', { static: true }) form: any; @ViewChild('authForm', { static: true }) form: any;
public faKey = faKey; public faInfoCircle = faInfoCircle;
public oldPassword = ''; public faUserLock = faUserLock;
public faUserClock = faUserClock;
public currPassword = '';
public newPassword = ''; public newPassword = '';
public confirmPassword = ''; public confirmPassword = '';
public errorMsg = ''; public errorMsg = '';
public errorConfirmMsg = ''; public errorConfirmMsg = '';
public appConfig: RTLConfiguration;
unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>) {} constructor(private store: Store<fromRTLReducer.RTLState>, private logger: LoggerService) {}
ngOnInit() {} ngOnInit() {
this.store.select('root')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
this.appConfig = rtlStore.appConfig;
this.logger.info(rtlStore);
});
}
onResetPassword() { onChangePassword() {
if(!this.oldPassword || !this.newPassword || !this.confirmPassword || this.oldPassword === this.newPassword || this.newPassword !== this.confirmPassword) { return true; } if(!this.currPassword || !this.newPassword || !this.confirmPassword || this.currPassword === this.newPassword || this.newPassword !== this.confirmPassword) { return true; }
this.store.dispatch(new RTLActions.ResetPassword({oldPassword: sha256(this.oldPassword), newPassword: sha256(this.newPassword)})); this.store.dispatch(new RTLActions.ResetPassword({currPassword: sha256(this.currPassword), newPassword: sha256(this.newPassword)}));
} }
matchOldAndNewPasswords(): boolean { matchOldAndNewPasswords(): boolean {
@ -36,7 +53,7 @@ export class AuthSettingsComponent implements OnInit {
this.form.controls.newpassword.setErrors({invalid: true}); this.form.controls.newpassword.setErrors({invalid: true});
this.errorMsg = 'New password is required.'; this.errorMsg = 'New password is required.';
invalid = true; invalid = true;
} else if (this.oldPassword !== '' && this.newPassword !== '' && this.oldPassword === this.newPassword) { } else if (this.currPassword !== '' && this.newPassword !== '' && this.currPassword === this.newPassword) {
this.form.controls.newpassword.setErrors({invalid: true}); this.form.controls.newpassword.setErrors({invalid: true});
this.errorMsg = 'Old and New password cannot be same.'; this.errorMsg = 'Old and New password cannot be same.';
invalid = true; invalid = true;
@ -70,9 +87,23 @@ export class AuthSettingsComponent implements OnInit {
} }
resetData() { resetData() {
this.oldPassword = ''; this.currPassword = '';
this.newPassword = ''; this.newPassword = '';
this.confirmPassword = ''; this.confirmPassword = '';
} }
on2FAuth() {
this.store.dispatch(new RTLActions.OpenAlert({ data: {
appConfig: this.appConfig,
component: TwoFactorAuthComponent
}}));
}
ngOnDestroy() {
this.unSubs.forEach(unsub => {
unsub.next();
unsub.complete();
});
}
} }

@ -1,13 +1,13 @@
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container"> <div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
<fa-icon [icon]="faTools" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faTools" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Global Settings</span> <span class="page-title">Settings</span>
</div> </div>
<div fxLayout="column" class="padding-gap-x"> <div fxLayout="column" class="padding-gap-x">
<mat-card> <mat-card>
<mat-card-content fxLayout="column"> <mat-card-content fxLayout="column">
<mat-tab-group selectedIndex="{{loadTab === 'authSettings' ? 1 : 0}}"> <mat-tab-group selectedIndex="{{loadTab === 'authSettings' ? 1 : 0}}">
<mat-tab id="appSettings" label="Settings"><rtl-app-settings></rtl-app-settings></mat-tab> <mat-tab id="appSettings" label="Layout"><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="!appConfig.sso.rtlSSO" label="Authentication"><rtl-auth-settings></rtl-auth-settings></mat-tab>
<mat-tab *ngIf="showLnConfig" [label]="lnImplementationStr"> <mat-tab *ngIf="showLnConfig" [label]="lnImplementationStr">
<ng-template matTabContent> <ng-template matTabContent>
<rtl-server-config [selectedNodeType]="'ln'"></rtl-server-config> <rtl-server-config [selectedNodeType]="'ln'"></rtl-server-config>

@ -44,6 +44,7 @@ export class RTLConfiguration {
public defaultNodeIndex: number, public defaultNodeIndex: number,
public selectedNodeIndex: number, public selectedNodeIndex: number,
public sso: SSO, public sso: SSO,
public enable2FA: boolean,
public nodes: ConfigSettingsNode[] public nodes: ConfigSettingsNode[]
) { } ) { }
} }

@ -1,5 +1,5 @@
import { DataTypeEnum, SwapTypeEnum } from '../services/consts-enums-functions'; import { DataTypeEnum, SwapTypeEnum } from '../services/consts-enums-functions';
import { GetInfoRoot } from './RTLconfig'; import { GetInfoRoot, RTLConfiguration } from './RTLconfig';
import { GetInfo, Invoice, Channel } from './lndModels'; import { GetInfo, Invoice, Channel } from './lndModels';
import { InvoiceCL, GetInfoCL } from './clModels'; import { InvoiceCL, GetInfoCL } from './clModels';
import { LoopQuote } from './loopModels'; import { LoopQuote } from './loopModels';
@ -73,6 +73,11 @@ export interface ShowPubkeyData {
component?: any; component?: any;
} }
export interface LoginTokenData {
authRes: {token: string};
component?: any;
}
export interface LoopAlert { export interface LoopAlert {
channel: Channel; channel: Channel;
minQuote: LoopQuote; minQuote: LoopQuote;
@ -114,8 +119,14 @@ export interface ErrorData {
component?: any; component?: any;
} }
export interface AuthConfig {
appConfig?: RTLConfiguration;
component?: any;
}
export interface DialogConfig { export interface DialogConfig {
width?: string; width?: string;
maxWidth?: string;
minHeight?: string; minHeight?: string;
data: AlertData | ConfirmationData | ErrorData | OpenChannelAlert | CLOpenChannelAlert | InvoiceInformation | CLInvoiceInformation | ChannelInformation | OnChainAddressInformation | ShowPubkeyData | LoopAlert; data: AlertData | ConfirmationData | ErrorData | OpenChannelAlert | CLOpenChannelAlert | InvoiceInformation | CLInvoiceInformation | ChannelInformation | OnChainAddressInformation | ShowPubkeyData | LoopAlert | AuthConfig | LoginTokenData;
} }

@ -382,6 +382,10 @@ body {
margin: 2rem 0 !important; margin: 2rem 0 !important;
} }
.my-3 {
margin: 3rem 0 !important;
}
.my-4 { .my-4 {
margin: 4rem 0 !important; margin: 4rem 0 !important;
} }
@ -655,7 +659,6 @@ body {
.mat-flat-button { .mat-flat-button {
width: 100%; width: 100%;
margin-top: 0.5rem;
max-height: 3.6rem; max-height: 3.6rem;
} }
} }
@ -764,6 +767,10 @@ body {
height: 40rem !important; height: 40rem !important;
} }
.h-10 {
height: 10rem !important;
}
.h-4 { .h-4 {
height: 4rem !important; height: 4rem !important;
} }

@ -27,6 +27,7 @@ export const SET_STORE = 'SET_STORE';
export const FETCH_RTL_CONFIG = 'FETCH_RTL_CONFIG'; export const FETCH_RTL_CONFIG = 'FETCH_RTL_CONFIG';
export const SET_RTL_CONFIG = 'SET_RTL_CONFIG'; export const SET_RTL_CONFIG = 'SET_RTL_CONFIG';
export const SAVE_SETTINGS = 'SAVE_SETTINGS'; export const SAVE_SETTINGS = 'SAVE_SETTINGS';
export const TWO_FA_SAVE_SETTINGS = 'TWO_FA_SAVE_SETTINGS';
export const SET_SELECTED_NODE = 'SET_SELECTED_NODE'; export const SET_SELECTED_NODE = 'SET_SELECTED_NODE';
export const SET_NODE_DATA = 'SET_NODE_DATA'; export const SET_NODE_DATA = 'SET_NODE_DATA';
@ -95,6 +96,7 @@ export const SHOW_CONFIG = 'SHOW_CONFIG';
export const IS_AUTHORIZED = 'IS_AUTHORIZED'; export const IS_AUTHORIZED = 'IS_AUTHORIZED';
export const IS_AUTHORIZED_RES = 'IS_AUTHORIZED_RES'; export const IS_AUTHORIZED_RES = 'IS_AUTHORIZED_RES';
export const LOGIN = 'LOGIN'; export const LOGIN = 'LOGIN';
export const VERIFY_TWO_FA = 'VERIFY_TWO_FA';
export const LOGOUT = 'LOGOUT'; export const LOGOUT = 'LOGOUT';
export const RESET_PASSWORD = 'RESET_PASSWORD'; export const RESET_PASSWORD = 'RESET_PASSWORD';
export const PEER_LOOKUP = 'PEER_LOOKUP'; export const PEER_LOOKUP = 'PEER_LOOKUP';
@ -262,6 +264,11 @@ export class SaveSettings implements Action {
constructor(public payload: {settings: Settings, defaultNodeIndex?: number}) {} constructor(public payload: {settings: Settings, defaultNodeIndex?: number}) {}
} }
export class TwoFASaveSettings implements Action {
readonly type = TWO_FA_SAVE_SETTINGS;
constructor(public payload: {secret2fa: string}) {}
}
export class SetSelelectedNode implements Action { export class SetSelelectedNode implements Action {
readonly type = SET_SELECTED_NODE; readonly type = SET_SELECTED_NODE;
constructor(public payload: { lnNode: ConfigSettingsNode, isInitialSetup: boolean }) {} constructor(public payload: { lnNode: ConfigSettingsNode, isInitialSetup: boolean }) {}
@ -623,6 +630,11 @@ export class Login implements Action {
constructor(public payload: {password: string, initialPass: boolean}) {} constructor(public payload: {password: string, initialPass: boolean}) {}
} }
export class VerifyTwoFA implements Action {
readonly type = VERIFY_TWO_FA;
constructor(public payload: {token: string, authResponse: any}) {}
}
export class Logout implements Action { export class Logout implements Action {
readonly type = LOGOUT; readonly type = LOGOUT;
constructor() {} constructor() {}
@ -630,7 +642,7 @@ export class Logout implements Action {
export class ResetPassword implements Action { export class ResetPassword implements Action {
readonly type = RESET_PASSWORD; readonly type = RESET_PASSWORD;
constructor(public payload: {oldPassword: string, newPassword: string}) {} constructor(public payload: {currPassword: string, newPassword: string}) {}
} }
export class SetChildNodeSettingsCL implements Action { export class SetChildNodeSettingsCL implements Action {
@ -875,7 +887,7 @@ export type RTLActions =
GetNewAddress | SetNewAddress | SetChannelTransaction | GetNewAddress | SetNewAddress | SetChannelTransaction |
GenSeed | GenSeedResponse | InitWallet | InitWalletResponse | UnlockWallet | GenSeed | GenSeedResponse | InitWallet | InitWalletResponse | UnlockWallet |
FetchConfig | ShowConfig | PeerLookup | ChannelLookup | InvoiceLookup | SetLookup | FetchConfig | ShowConfig | PeerLookup | ChannelLookup | InvoiceLookup | SetLookup |
FetchLoopSwaps | SetLoopSwaps | IsAuthorized | IsAuthorizedRes | Login | Logout | ResetPassword | FetchLoopSwaps | SetLoopSwaps | IsAuthorized | IsAuthorizedRes | Login | VerifyTwoFA | Logout | ResetPassword |
SetChildNodeSettingsCL | FetchInfoCL | SetInfoCL | FetchFeesCL | SetFeesCL | FetchFeeRatesCL | SetFeeRatesCL | SetChildNodeSettingsCL | FetchInfoCL | SetInfoCL | FetchFeesCL | SetFeesCL | FetchFeeRatesCL | SetFeeRatesCL |
FetchBalanceCL | SetBalanceCL | FetchLocalRemoteBalanceCL | SetLocalRemoteBalanceCL | FetchBalanceCL | SetBalanceCL | FetchLocalRemoteBalanceCL | SetLocalRemoteBalanceCL |
GetNewAddressCL | SetNewAddressCL | GetNewAddressCL | SetNewAddressCL |

@ -201,6 +201,25 @@ export class RTLEffects implements OnDestroy {
}) })
)); ));
@Effect()
twoFASettingSave = this.actions$.pipe(
ofType(RTLActions.TWO_FA_SAVE_SETTINGS),
mergeMap((action: RTLActions.TwoFASaveSettings) => {
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('Update2FASettings'));
return this.httpClient.post(environment.CONF_API + '/update2FA', { secret2fa: action.payload.secret2fa });
}),
map((updateStatus: any) => {
this.store.dispatch(new RTLActions.CloseSpinner());
this.logger.info(updateStatus);
return { type: RTLActions.VOID };
},
catchError((err) => {
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: 'Update2FASettings', code: (!err.length) ? err.status : err[0].status, message: (!err.length) ? err.error.error : err[0].error.error }));
this.handleErrorWithAlert('ERROR', 'Update 2FA Settings Failed!', environment.CONF_API, (!err.length) ? err : err[0]);
return of({type: RTLActions.VOID});
})
));
@Effect() @Effect()
configFetch = this.actions$.pipe( configFetch = this.actions$.pipe(
ofType(RTLActions.FETCH_CONFIG), ofType(RTLActions.FETCH_CONFIG),
@ -270,6 +289,18 @@ export class RTLEffects implements OnDestroy {
}) })
); );
setLoggedInDetails(initialPass: boolean, postRes: any, rootStore: any) {
this.logger.info('Successfully Authorized!');
this.SetToken(postRes.token);
rootStore.selNode.settings.currencyUnits = [...CURRENCY_UNITS, rootStore.selNode.settings.currencyUnit];
if(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}));
}
}
@Effect({ dispatch: false }) @Effect({ dispatch: false })
authLogin = this.actions$.pipe( authLogin = this.actions$.pipe(
ofType(RTLActions.LOGIN), ofType(RTLActions.LOGIN),
@ -285,20 +316,12 @@ export class RTLEffects implements OnDestroy {
.pipe( .pipe(
map((postRes: any) => { map((postRes: any) => {
this.logger.info(postRes); this.logger.info(postRes);
this.logger.info('Successfully Authorized!'); this.setLoggedInDetails(action.payload.initialPass, postRes, rootStore);
this.SetToken(postRes.token);
rootStore.selNode.settings.currencyUnits = [...CURRENCY_UNITS, rootStore.selNode.settings.currencyUnit];
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) => { catchError((err) => {
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 Login Error Page'); this.logger.info('Redirecting to Login Error Page');
this.handleErrorWithAlert('ERROR', 'Authorization Failed!', environment.AUTHENTICATE_API, {status: err.status, error: err.error.error});
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: 'Login', code: err.status, message: err.error.error }));
if (+rootStore.appConfig.sso.rtlSSO) { if (+rootStore.appConfig.sso.rtlSSO) {
this.router.navigate(['/error'], { state: { errorCode: '401', errorMessage: 'Single Sign On Failed!' }}); this.router.navigate(['/error'], { state: { errorCode: '401', errorMessage: 'Single Sign On Failed!' }});
} else { } else {
@ -309,6 +332,27 @@ export class RTLEffects implements OnDestroy {
); );
})); }));
@Effect({ dispatch: false })
tokenVerify = this.actions$.pipe(
ofType(RTLActions.VERIFY_TWO_FA),
withLatestFrom(this.store.select('root')),
mergeMap(([action, rootStore]: [RTLActions.VerifyTwoFA, fromRTLReducer.RootState]) => {
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('VerifyToken'));
return this.httpClient.post(environment.AUTHENTICATE_API + '/token', {authentication2FA: action.payload.token})
.pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.logger.info('Token Successfully Verified!');
this.setLoggedInDetails(false, action.payload.authResponse, rootStore);
}),
catchError((err) => {
this.handleErrorWithAlert('ERROR', 'Authorization Failed!', environment.AUTHENTICATE_API + '/token', {status: err.status, error: err.error.error});
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: 'VerifyToken', code: err.status, message: err.error.error }));
return of({type: RTLActions.VOID});
})
);
}));
@Effect({ dispatch: false }) @Effect({ dispatch: false })
logOut = this.actions$.pipe( logOut = this.actions$.pipe(
ofType(RTLActions.LOGOUT), ofType(RTLActions.LOGOUT),
@ -334,7 +378,7 @@ export class RTLEffects implements OnDestroy {
mergeMap(([action, rootStore]: [RTLActions.ResetPassword, fromRTLReducer.RootState]) => { mergeMap(([action, rootStore]: [RTLActions.ResetPassword, fromRTLReducer.RootState]) => {
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('ResetPassword')); this.store.dispatch(new RTLActions.ClearEffectErrorRoot('ResetPassword'));
return this.httpClient.post(environment.AUTHENTICATE_API + '/reset', { return this.httpClient.post(environment.AUTHENTICATE_API + '/reset', {
oldPassword: action.payload.oldPassword, currPassword: action.payload.currPassword,
newPassword: action.payload.newPassword newPassword: action.payload.newPassword
}) })
.pipe( .pipe(

@ -23,6 +23,7 @@ const initRootState: RootState = {
defaultNodeIndex: -1, defaultNodeIndex: -1,
selectedNodeIndex: -1, selectedNodeIndex: -1,
sso: { rtlSSO: 0, logoutRedirectLink: '/login' }, sso: { rtlSSO: 0, logoutRedirectLink: '/login' },
enable2FA: false,
nodes: [{ settings: initNodeSettings, authentication: initNodeAuthentication}] nodes: [{ settings: initNodeSettings, authentication: initNodeAuthentication}]
}, },
nodeData: {} nodeData: {}

Loading…
Cancel
Save