mirror of
https://github.com/Ride-The-Lightning/RTL.git
synced 2026-08-13 12:33:07 +02:00
Google authenticator integration PR-266
Google authenticator integration PR-266
This commit is contained in:
parent
cf71a4d897
commit
3a40bd9748
44 changed files with 692 additions and 110 deletions
|
|
@ -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
1
angular/main.4b92e283188428242458.js
Normal file
1
angular/main.4b92e283188428242458.js
Normal file
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()}([]);
|
||||
1
angular/styles.807ae95794a05be97fba.css
Normal file
1
angular/styles.807ae95794a05be97fba.css
Normal file
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
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…
Add table
Add a link
Reference in a new issue