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.
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
MIT
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">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="styles.f04e677f00c07a00a61f.css"></head>
<link rel="stylesheet" href="styles.807ae95794a05be97fba.css"></head>
<body>
<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>

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_pass = '';
common.rtl_secret2fa = '';
common.rtl_sso = 0;
common.port = 3000;
common.rtl_cookie_path = '';

@ -107,6 +107,7 @@ connect.validateNodeConfig = (config) => {
} else {
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;
if (config.nodes && config.nodes.length > 0) {

@ -31,6 +31,7 @@ exports.getRTLConfig = (req, res, next) => {
} else {
const nodeConfData = JSON.parse(data);
const sso = { rtlSSO: common.rtl_sso, logoutRedirectLink: common.logout_redirect_link };
const enable2FA = !common.rtl_secret2fa ? false : true;
var nodesArr = [];
if (common.nodes && common.nodes.length > 0) {
common.nodes.forEach((node, i) => {
@ -59,7 +60,7 @@ exports.getRTLConfig = (req, res, next) => {
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) => {
RTLConfFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));

@ -3,6 +3,7 @@ var connect = require('../connect');
const jwt = require("jsonwebtoken");
var crypto = require('crypto');
var logger = require('./logger');
const otplib = require("otplib");
exports.authenticateUser = (req, res, next) => {
if(+common.rtl_sso) {
@ -49,8 +50,8 @@ exports.resetPassword = (req, res, next) => {
error: "Password cannot be reset for SSO authentication!"
});
} else {
const oldPassword = req.body.oldPassword;
if (common.rtl_pass === oldPassword) {
const currPassword = req.body.currPassword;
if (common.rtl_pass === currPassword) {
common.rtl_pass = connect.replacePasswordWithHash(req.body.newPassword);
var rpcUser = 'NODE_USER';
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"
}
},
"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": {
"version": "1.0.0",
"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": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",

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

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

@ -3,6 +3,7 @@ const express = require("express");
const router = express.Router();
router.post("/", AuthenticateController.authenticateUser);
router.post("/token", AuthenticateController.verifyToken);
router.post("/reset", AuthenticateController.resetPassword);
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 { ErrorMessageComponent } from './shared/components/data-modal/error-message/error-message.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({
imports: [
@ -70,7 +72,9 @@ import { LoopModalComponent } from './lnd/loop/loop-modal/loop-modal.component';
ConfirmationMessageComponent,
ErrorMessageComponent,
CloseChannelComponent,
LoopModalComponent
LoopModalComponent,
TwoFactorAuthComponent,
LoginTokenComponent
],
entryComponents: [
CLInvoiceInformationComponent,
@ -85,7 +89,9 @@ import { LoopModalComponent } from './lnd/loop/loop-modal/loop-modal.component';
ConfirmationMessageComponent,
ErrorMessageComponent,
CloseChannelComponent,
LoopModalComponent
LoopModalComponent,
TwoFactorAuthComponent,
LoginTokenComponent
],
providers: [
{ provide: LoggerService, useClass: ConsoleLoggerService },

@ -7,7 +7,6 @@
<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 -->
<title>Loop_Step01</title>
<desc>Created with Sketch.</desc>
<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">
@ -68,10 +67,10 @@
<div class="mt-6" fxLayout="column"
[fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'"
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">
<p class="font-size-120" fxFlex="80">Lightning loop is non custodial service offered by lightning labs to bridge
on-chain and off-chain bitcoin using submarine swaps.</p>
<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>
</mat-card-subtitle>
</div>
</div>
@ -82,7 +81,6 @@
<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 -->
<title>Loop_Step02</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
@ -219,9 +217,9 @@
<div class="mt-6" fxLayout="column"
[fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'"
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">
<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>
</div>
</div>
@ -232,7 +230,6 @@
<svg viewBox="0 0 364 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 -->
<title>Loop_Step03</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="8.86848147e-15%" id="linearGradient-1">
@ -428,7 +425,7 @@
fxLayoutAlign="start center">
<mat-card-title>Step 2: Send on-chain payment out</mat-card-title>
<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>
</div>
</div>
@ -439,7 +436,6 @@
<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 -->
<title>Loop_Step04</title>
<desc>Created with Sketch.</desc>
<g id="Loopv0.3" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="LoopIn_Step04" transform="translate(-1799.000000, -756.000000)">
@ -546,7 +542,7 @@
fxLayoutAlign="start center">
<mat-card-title>Step 3: Recieve Funds Off-chain</mat-card-title>
<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>
</div>
</div>
@ -557,7 +553,6 @@
<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 -->
<title>Loop_Step05</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
@ -659,8 +654,8 @@
fxLayoutAlign="start center">
<mat-card-title>Done!</mat-card-title>
<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
balance to the local side of the node.</p>
<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, gaining outgoing capacity.</p>
</mat-card-subtitle>
</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?.max">Percentage must be less than or equal to {{maxRoutingFeePercentage}}.</mat-error>
</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 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>
@ -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-in-info-graphics *ngIf="direction === swapTypeEnum.LOOP_IN" [(stepNumber)]="stepNumber" [animationDirection]="animationDirection"></rtl-loop-in-info-graphics>
</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">
<p class="dot tiny-dot mr-0" [ngClass]="{'dot-primary': stepNumber === i, 'dot-primary-lighter': stepNumber !== i}"></p>
</span>
</div>
<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-flat-button color="primary" tabindex="16" type="button" (click)="flgShowInfo=false;stepNumber=1;">Close</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="18" type="button" (click)="onStepChanged(stepNumber + 1)">Next</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-flat-button color="primary" tabindex="17" type="button" (click)="flgShowInfo=false;stepNumber=1;">Close</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>

@ -5,7 +5,6 @@
<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">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step01</title>
<desc>Created with Sketch.</desc>
<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">
@ -53,10 +52,10 @@
<div class="mt-6" fxLayout="column"
[fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'"
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">
<p class="font-size-120" fxFlex="80">Lightning loop is non custodial service offered by lightning labs to bridge
on-chain and off-chain bitcoin using submarine swaps.</p>
<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>
</mat-card-subtitle>
</div>
</div>
@ -65,7 +64,6 @@
<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">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step02</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
@ -152,9 +150,9 @@
<div class="mt-6" fxLayout="column"
[fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '40' : '30'"
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">
<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>
</div>
</div>
@ -163,7 +161,6 @@
<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">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step03</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="8.86848147e-15%" id="linearGradient-1">
@ -308,8 +305,8 @@
fxLayoutAlign="start center">
<mat-card-title>Step 2: Send lightning payment</mat-card-title>
<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
service. Moving that local balance, to the remote side of the channel.</p>
<p class="font-size-120 h-10" fxFlex="80">Your node pays a lightning invoice for the amount requested via the loop
service. This moves the local balance, for the amount paid, to the remote side of the channel.</p>
</mat-card-subtitle>
</div>
</div>
@ -318,7 +315,6 @@
<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">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step04</title>
<desc>Created with Sketch.</desc>
<g id="Loopv0.2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="LoopOut_Step04" transform="translate(-503.000000, -212.000000)">
@ -420,7 +416,7 @@
fxLayoutAlign="start center">
<mat-card-title>Step 3: Receive funds back on-chain</mat-card-title>
<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>
</div>
</div>
@ -429,7 +425,6 @@
<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">
<!-- Generator: Sketch 63.1 (92452) - https://sketch.com -->
<title>Loop_Step05</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="50%" y1="100%" x2="50%" y2="0%" id="linearGradient-1">
@ -557,8 +552,8 @@
fxLayoutAlign="start center">
<mat-card-title>Done!</mat-card-title>
<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
balance to the remote side of the channel.</p>
<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, gaining inbound capacity.</p>
</mat-card-subtitle>
</div>
</div>

@ -15,7 +15,7 @@
<ng-template #quoteDetailsBlock>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
<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>
<span class="foreground-secondary-text">{{quote?.swap_fee | number}}</span>
</div>
@ -41,7 +41,7 @@
</div>
<mat-divider class="w-100 my-1" *ngIf="quote?.swap_payment_dest !== ''"></mat-divider>
<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>
<span class="foreground-secondary-text">{{quote?.swap_payment_dest}}</span>
</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>
<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 class="pr-2">
<mat-card-content class="pr-1">
<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}}" [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>
<mat-error *ngIf="!password">Password is required.</mat-error>
</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">
<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-flat-button color="primary" tabindex="3" type="submit">Login</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="4" type="submit">Login</button>
</div>
</form>
</mat-card-content>

@ -3,11 +3,13 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import * as sha256 from 'sha256';
import { Store } from '@ngrx/store';
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 { RTLEffects } from '../../../store/rtl.effects';
import * as fromRTLReducer from '../../../store/rtl.reducers';
import * as RTLActions from '../../../store/rtl.actions';
@ -19,38 +21,60 @@ import * as RTLActions from '../../../store/rtl.actions';
export class LoginComponent implements OnInit, OnDestroy {
public faUnlockAlt = faUnlockAlt;
public selNode: ConfigSettingsNode;
public appConfig: RTLConfiguration;
public password = '';
public rtlSSO = 0;
public rtlCookiePath = '';
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>) { }
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects) { }
ngOnInit() {
this.store.select('root')
.pipe(takeUntil(this.unsub[0]))
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsRoot.forEach(effectsErr => {
if (effectsErr.action === 'Login' || effectsErr.action === 'IsAuthorized') {
this.loginErrorMessage = this.loginErrorMessage + effectsErr.message + ' ';
}
this.logger.error(effectsErr);
});
this.selNode = rtlStore.selNode;
this.appConfig = rtlStore.appConfig;
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() {
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() {
this.password = '';
this.loginErrorMessage = '';
}
ngOnDestroy() {
this.unsub.forEach(completeSub => {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});

@ -70,10 +70,10 @@
</div>
</div>
</div>
<div fxLayout="row" fxFlex="100" class="mt-2">
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" (click)="onResetSettings()" tabindex="10">Reset</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-flat-button color="primary" (click)="onUpdateSettings()" tabindex="11">Update</button>
<div fxLayout="row">
<div fxLayout="row" fxLayoutAlign="start start" class="mt-2">
<button mat-stroked-button color="primary" (click)="onResetSettings()" tabindex="10" class="mr-1">Reset</button>
<button mat-flat-button color="primary" (click)="onUpdateSettings()" tabindex="11">Update</button>
</div>
</div>
</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 { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
@ -39,7 +39,6 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
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>) {
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.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.done.emit();
}
onResetSettings() {

@ -1,30 +1,42 @@
<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>
<form (ngSubmit)="onChangePassword()" fxLayout="column" fxLayoutAlign="start stretch" class="settings-container page-sub-title-container mt-1" #authForm="ngForm">
<div fxFlex="100">
<fa-icon [icon]="faUserLock" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Password</span>
</div>
<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>
<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>
<input matInput placeholder="Current Password" type="password" id="currpassword" name="currpassword" [(ngModel)]="currPassword" tabindex="4" required>
<mat-error *ngIf="!currPassword">Current password is required.</mat-error>
</mat-form-field>
<mat-divider [inset]="true"></mat-divider>
<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-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-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 fxLayout="row" fxLayoutAlign="start start" class="mt-1">
<button mat-flat-button color="primary" tabindex="8" type="submit">Change Password</button>
</div>
</div>
</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>

@ -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 { faKey } from '@fortawesome/free-solid-svg-icons';
import { faUserLock, faUserClock, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
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 RTLActions from '../../../../store/rtl.actions';
@ -11,22 +17,33 @@ import * as RTLActions from '../../../../store/rtl.actions';
templateUrl: './auth-settings.component.html',
styleUrls: ['./auth-settings.component.scss']
})
export class AuthSettingsComponent implements OnInit {
export class AuthSettingsComponent implements OnInit, OnDestroy {
@ViewChild('authForm', { static: true }) form: any;
public faKey = faKey;
public oldPassword = '';
public faInfoCircle = faInfoCircle;
public faUserLock = faUserLock;
public faUserClock = faUserClock;
public currPassword = '';
public newPassword = '';
public confirmPassword = '';
public errorMsg = '';
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() {
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)}));
onChangePassword() {
if(!this.currPassword || !this.newPassword || !this.confirmPassword || this.currPassword === this.newPassword || this.newPassword !== this.confirmPassword) { return true; }
this.store.dispatch(new RTLActions.ResetPassword({currPassword: sha256(this.currPassword), newPassword: sha256(this.newPassword)}));
}
matchOldAndNewPasswords(): boolean {
@ -36,7 +53,7 @@ export class AuthSettingsComponent implements OnInit {
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) {
} else if (this.currPassword !== '' && this.newPassword !== '' && this.currPassword === this.newPassword) {
this.form.controls.newpassword.setErrors({invalid: true});
this.errorMsg = 'Old and New password cannot be same.';
invalid = true;
@ -70,9 +87,23 @@ export class AuthSettingsComponent implements OnInit {
}
resetData() {
this.oldPassword = '';
this.currPassword = '';
this.newPassword = '';
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">
<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 fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column">
<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 id="appSettings" label="Layout"><rtl-app-settings></rtl-app-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">
<ng-template matTabContent>
<rtl-server-config [selectedNodeType]="'ln'"></rtl-server-config>

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

@ -1,5 +1,5 @@
import { DataTypeEnum, SwapTypeEnum } from '../services/consts-enums-functions';
import { GetInfoRoot } from './RTLconfig';
import { GetInfoRoot, RTLConfiguration } from './RTLconfig';
import { GetInfo, Invoice, Channel } from './lndModels';
import { InvoiceCL, GetInfoCL } from './clModels';
import { LoopQuote } from './loopModels';
@ -73,6 +73,11 @@ export interface ShowPubkeyData {
component?: any;
}
export interface LoginTokenData {
authRes: {token: string};
component?: any;
}
export interface LoopAlert {
channel: Channel;
minQuote: LoopQuote;
@ -114,8 +119,14 @@ export interface ErrorData {
component?: any;
}
export interface AuthConfig {
appConfig?: RTLConfiguration;
component?: any;
}
export interface DialogConfig {
width?: string;
maxWidth?: 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;
}
.my-3 {
margin: 3rem 0 !important;
}
.my-4 {
margin: 4rem 0 !important;
}
@ -655,7 +659,6 @@ body {
.mat-flat-button {
width: 100%;
margin-top: 0.5rem;
max-height: 3.6rem;
}
}
@ -764,6 +767,10 @@ body {
height: 40rem !important;
}
.h-10 {
height: 10rem !important;
}
.h-4 {
height: 4rem !important;
}

@ -27,6 +27,7 @@ export const SET_STORE = 'SET_STORE';
export const FETCH_RTL_CONFIG = 'FETCH_RTL_CONFIG';
export const SET_RTL_CONFIG = 'SET_RTL_CONFIG';
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_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_RES = 'IS_AUTHORIZED_RES';
export const LOGIN = 'LOGIN';
export const VERIFY_TWO_FA = 'VERIFY_TWO_FA';
export const LOGOUT = 'LOGOUT';
export const RESET_PASSWORD = 'RESET_PASSWORD';
export const PEER_LOOKUP = 'PEER_LOOKUP';
@ -262,6 +264,11 @@ export class SaveSettings implements Action {
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 {
readonly type = SET_SELECTED_NODE;
constructor(public payload: { lnNode: ConfigSettingsNode, isInitialSetup: boolean }) {}
@ -623,6 +630,11 @@ export class Login implements Action {
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 {
readonly type = LOGOUT;
constructor() {}
@ -630,7 +642,7 @@ export class Logout implements Action {
export class ResetPassword implements Action {
readonly type = RESET_PASSWORD;
constructor(public payload: {oldPassword: string, newPassword: string}) {}
constructor(public payload: {currPassword: string, newPassword: string}) {}
}
export class SetChildNodeSettingsCL implements Action {
@ -875,7 +887,7 @@ export type RTLActions =
GetNewAddress | SetNewAddress | SetChannelTransaction |
GenSeed | GenSeedResponse | InitWallet | InitWalletResponse | UnlockWallet |
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 |
FetchBalanceCL | SetBalanceCL | FetchLocalRemoteBalanceCL | SetLocalRemoteBalanceCL |
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()
configFetch = this.actions$.pipe(
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 })
authLogin = this.actions$.pipe(
ofType(RTLActions.LOGIN),
@ -285,20 +316,12 @@ export class RTLEffects implements OnDestroy {
.pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.logger.info('Successfully Authorized!');
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}));
}
this.setLoggedInDetails(action.payload.initialPass, postRes, rootStore);
}),
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.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) {
this.router.navigate(['/error'], { state: { errorCode: '401', errorMessage: 'Single Sign On Failed!' }});
} 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 })
logOut = this.actions$.pipe(
ofType(RTLActions.LOGOUT),
@ -334,7 +378,7 @@ export class RTLEffects implements OnDestroy {
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,
currPassword: action.payload.currPassword,
newPassword: action.payload.newPassword
})
.pipe(

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

Loading…
Cancel
Save