Password reset page

Password reset page
This commit is contained in:
Shahana Farooqui 2020-01-21 19:18:34 -05:00
parent 933e19df2c
commit 6065a27c50
20 changed files with 188 additions and 20 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -12,5 +12,5 @@
<link rel="stylesheet" href="styles.90ee7bcb73e8367b2a29.css"></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.2e6ad7cb977005b4f6b6.js" defer></script><script src="polyfills-es5.37b2eeccc22c1df73ce7.js" nomodule defer></script><script src="polyfills.f1c3d2a0bcdfc4e93ca8.js" defer></script><script src="main.0990540ecd60b7ec6d35.js" defer></script></body>
<script src="runtime.bd15680a9b84bab2ef08.js" defer></script><script src="polyfills-es5.37b2eeccc22c1df73ce7.js" nomodule defer></script><script src="polyfills.f1c3d2a0bcdfc4e93ca8.js" defer></script><script src="main.83987fd3a7fe4528412f.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

View file

@ -1 +0,0 @@
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"b78c4146e439be098e08",6:"30c4bc397969e1e00c8a",7:"4629778ec16eba9683e4"}[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()}([]);

View file

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

View file

@ -82,12 +82,12 @@ connect.normalizePort = val => {
return false;
};
connect.convertCustomToHash = () => {
connect.replacePasswordWithHash = (multiPassHashed) => {
common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : path.normalize(__dirname);
try {
RTLConfFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
var config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.multiPassHashed = hash.update(config.multiPass).digest('hex');
config.multiPassHashed = multiPassHashed;
delete config.multiPass;
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
console.log('Please note that, RTL has encrypted the plaintext password into its corresponding hash.');
@ -104,7 +104,7 @@ connect.validateNodeConfig = (config) => {
} else if (config.multiPassHashed !== '' && config.multiPassHashed) {
common.rtl_pass = config.multiPassHashed;
} else if (config.multiPass !== '' && config.multiPass) {
common.rtl_pass = connect.convertCustomToHash();
common.rtl_pass = connect.replacePasswordWithHash(hash.update(config.multiPass).digest('hex'));
} else {
errMsg = errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json';
}

View file

@ -11,7 +11,7 @@ exports.authenticateUser = (req, res, next) => {
} else if (req.body.authenticateWith === 'PASSWORD' && crypto.createHash('sha256').update(common.cookie).digest('hex') === req.body.authenticationValue) {
connect.refreshCookie(common.rtl_cookie_path);
const token = jwt.sign(
{ user: 'Custom_User', configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
{ user: 'SSO_USER', configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
@ -25,7 +25,7 @@ exports.authenticateUser = (req, res, next) => {
} else {
const password = req.body.authenticationValue;
if (common.rtl_pass === password) {
var rpcUser = 'Node_User';
var rpcUser = 'NODE_USER';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
@ -40,3 +40,30 @@ exports.authenticateUser = (req, res, next) => {
}
}
};
exports.resetPassword = (req, res, next) => {
if(+common.rtl_sso) {
logger.error({fileName: 'Authenticate', lineNum: 46, msg: 'Password Reset Failed!'});
res.status(402).json({
message: "Password Reset Failure!",
error: "Password cannot be reset for SSO authentication!"
});
} else {
const oldPassword = req.body.oldPassword;
if (common.rtl_pass === oldPassword) {
common.rtl_pass = connect.replacePasswordWithHash(req.body.newPassword);
var rpcUser = 'NODE_USER';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
} else {
logger.error({fileName: 'Authenticate', lineNum: 62, msg: 'Password Reset Failed!'});
res.status(402).json({
message: "Password Reset Failed!",
error: "Old password is not correct!"
});
}
}
};

View file

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

View file

@ -42,7 +42,7 @@ export class LoginComponent implements OnInit, OnDestroy {
onLogin() {
if(!this.password) { return true; }
this.store.dispatch(new RTLActions.Login(sha256(this.password)));
this.store.dispatch(new RTLActions.Login({password: sha256(this.password), initialPass: this.password === 'password'}));
}
resetData() {

View file

@ -0,0 +1,34 @@
<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" #form="ngForm">
<div fxFlex="100" class="mb-1">
<fa-icon [icon]="faKey" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Reset Password</span>
</div>
<div fxFlex="100" class="alert alert-info">
<fa-icon [icon]="faInfoCircle" class="mr-1 alert-icon"></fa-icon>
<span>Password set by the environment variable can not be reset from this page.</span>
</div>
<div fxLayout="row">
<div fxLayout="column" fxFlex="100" fxFlex.gt-sm="100" fxLayoutAlign="space-between stretch" class="mt-2">
<mat-form-field>
<input matInput placeholder="Old Password" type="password" id="oldpassword" name="oldpassword" [(ngModel)]="oldPassword" tabindex="1" required>
<mat-error *ngIf="!oldPassword">Old password is required.</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="New Password" type="password" id="newpassword" name="newpassword" [(ngModel)]="newPassword" tabindex="2" required>
<mat-error *ngIf="!newPassword">New password is required.</mat-error>
</mat-form-field>
<mat-form-field>
<input matInput placeholder="Confirm New Password" type="password" id="confirmpassword" name="confirmpassword" [(ngModel)]="confirmPassword" tabindex="3" required>
<mat-error *ngIf="!confirmPassword">Confirm new password is required.</mat-error>
</mat-form-field>
</div>
</div>
<div fxLayout="row">
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch" class="mt-2">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="4" type="reset" (click)="resetData()">Clear</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-flat-button color="primary" tabindex="5" type="submit">Reset Password</button>
</div>
</div>
</form>
</div>

View file

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

View file

@ -0,0 +1,36 @@
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import { faInfoCircle, faKey } from '@fortawesome/free-solid-svg-icons';
import * as sha256 from 'sha256';
import * as fromRTLReducer from '../../../../store/rtl.reducers';
import * as RTLActions from '../../../../store/rtl.actions';
@Component({
selector: 'rtl-auth-settings',
templateUrl: './auth-settings.component.html',
styleUrls: ['./auth-settings.component.scss']
})
export class AuthSettingsComponent implements OnInit {
public faKey = faKey;
public faInfoCircle = faInfoCircle;
public oldPassword = '';
public newPassword = '';
public confirmPassword = '';
constructor(private store: Store<fromRTLReducer.RTLState>) {}
ngOnInit() {}
onResetPassword() {
if(!this.oldPassword || !this.newPassword || !this.confirmPassword) { return true; }
this.store.dispatch(new RTLActions.ResetPassword({oldPassword: sha256(this.oldPassword), newPassword: sha256(this.newPassword)}));
}
resetData() {
this.oldPassword = '';
this.newPassword = '';
this.confirmPassword = '';
}
}

View file

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

View file

@ -6,6 +6,7 @@ import { faTools } from '@fortawesome/free-solid-svg-icons';
import { ConfigSettingsNode } from '../../models/RTLconfig';
import * as fromRTLReducer from '../../../store/rtl.reducers';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'rtl-settings',
@ -18,13 +19,19 @@ export class SettingsComponent implements OnInit, OnDestroy{
public showBitcoind = false;
public selNode: ConfigSettingsNode;
public lnImplementationStr = '';
public loadTab = 'appSettings';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>) {}
constructor(private store: Store<fromRTLReducer.RTLState>, private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.store.select('root')
this.activatedRoute.paramMap
.pipe(takeUntil(this.unSubs[0]))
.subscribe(data => {
this.loadTab = window.history.state.loadTab ? window.history.state.loadTab : 'appSettings';
});
this.store.select('root')
.pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => {
this.showLnConfig = false;
this.showBitcoind = false;

View file

@ -50,6 +50,7 @@ import { NonNegativeAmountValidator } from './directive/non-negative-amount.dire
import { RemoveLeadingZerosPipe } from './pipes/app.pipe';
import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.service';
import { AuthSettingsComponent } from './components/settings/auth-settings/auth-settings.component';
@NgModule({
imports: [
@ -179,7 +180,8 @@ import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.s
RemoveLeadingZerosPipe,
CLOpenChannelComponent,
OpenChannelComponent,
ShowPubkeyComponent
ShowPubkeyComponent,
AuthSettingsComponent
],
entryComponents: [
CLInvoiceInformationComponent,

View file

@ -94,6 +94,7 @@ export const IS_AUTHORIZED = 'IS_AUTHORIZED';
export const IS_AUTHORIZED_RES = 'IS_AUTHORIZED_RES';
export const LOGIN = 'LOGIN';
export const LOGOUT = 'LOGOUT';
export const RESET_PASSWORD = 'RESET_PASSWORD';
export const PEER_LOOKUP = 'PEER_LOOKUP';
export const CHANNEL_LOOKUP = 'CHANNEL_LOOKUP';
export const INVOICE_LOOKUP = 'INVOICE_LOOKUP';
@ -595,7 +596,7 @@ export class IsAuthorizedRes implements Action {
export class Login implements Action {
readonly type = LOGIN;
constructor(public payload: string) {} // payload = password
constructor(public payload: {password: string, initialPass: boolean}) {}
}
export class Logout implements Action {
@ -603,6 +604,11 @@ export class Logout implements Action {
constructor() {}
}
export class ResetPassword implements Action {
readonly type = RESET_PASSWORD;
constructor(public payload: {oldPassword: string, newPassword: string}) {}
}
export class SetChildNodeSettingsCL implements Action {
readonly type = SET_CHILD_NODE_SETTINGS_CL;
constructor(public payload: SelNodeChild) {}
@ -845,7 +851,7 @@ export type RTLActions =
GetNewAddress | SetNewAddress | SetChannelTransaction |
GenSeed | GenSeedResponse | InitWallet | InitWalletResponse | UnlockWallet |
FetchConfig | ShowConfig | PeerLookup | ChannelLookup | InvoiceLookup | SetLookup |
IsAuthorized | IsAuthorizedRes | Login | Logout |
IsAuthorized | IsAuthorizedRes | Login | Logout | ResetPassword |
SetChildNodeSettingsCL | FetchInfoCL | SetInfoCL | FetchFeesCL | SetFeesCL | FetchFeeRatesCL | SetFeeRatesCL |
FetchBalanceCL | SetBalanceCL | FetchLocalRemoteBalanceCL | SetLocalRemoteBalanceCL |
GetNewAddressCL | SetNewAddressCL |

View file

@ -277,8 +277,8 @@ export class RTLEffects implements OnDestroy {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchInfoCL'));
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('Login'));
return this.httpClient.post(environment.AUTHENTICATE_API, {
authenticateWith: (undefined === action.payload || action.payload == null || action.payload === '') ? AuthenticateWith.TOKEN : AuthenticateWith.PASSWORD,
authenticationValue: (undefined === action.payload || action.payload == null || action.payload === '') ? (this.sessionService.getItem('token') ? this.sessionService.getItem('token') : '') : action.payload
authenticateWith: (!action.payload.password) ? AuthenticateWith.TOKEN : AuthenticateWith.PASSWORD,
authenticationValue: (!action.payload.password) ? (this.sessionService.getItem('token') ? this.sessionService.getItem('token') : '') : action.payload.password
})
.pipe(
map((postRes: any) => {
@ -287,6 +287,10 @@ export class RTLEffects implements OnDestroy {
this.SetToken(postRes.token);
rootStore.selNode.settings.currencyUnits = [...CURRENCY_UNITS, rootStore.selNode.settings.currencyUnit];
this.store.dispatch(new RTLActions.SetSelelectedNode({lnNode: rootStore.selNode, isInitialSetup: true}))
if(action.payload.initialPass) {
this.store.dispatch(new RTLActions.OpenSnackBar('Reset your password before moving forward.'));
this.router.navigate(['/settings'], { state: { loadTab: 'authSettings' }});
}
}),
catchError((err) => {
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: 'Login', code: err.status, message: err.error.message }));
@ -320,6 +324,31 @@ export class RTLEffects implements OnDestroy {
}));
@Effect({ dispatch: false })
resetPassword = this.actions$.pipe(
ofType(RTLActions.RESET_PASSWORD),
withLatestFrom(this.store.select('root')),
mergeMap(([action, rootStore]: [RTLActions.ResetPassword, fromRTLReducer.RootState]) => {
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('ResetPassword'));
return this.httpClient.post(environment.AUTHENTICATE_API + '/reset', {
oldPassword: action.payload.oldPassword,
newPassword: action.payload.newPassword
})
.pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.logger.info('Password Reset Successful!');
this.store.dispatch(new RTLActions.OpenSnackBar('Password Reset Successful!'));
this.SetToken(postRes.token);
}),
catchError((err) => {
this.store.dispatch(new RTLActions.EffectErrorRoot({ action: 'ResetPassword', code: err.status, message: err.error.message }));
this.handleErrorWithAlert('ERROR', 'Password Reset Failed!', environment.AUTHENTICATE_API + '/reset', err.error);
return of({type: RTLActions.VOID});
})
);
}));
@Effect()
setSelectedNode = this.actions$.pipe(
ofType(RTLActions.SET_SELECTED_NODE),