RTL/controllers/shared/authenticate.js
ShahanaFarooqui e45d6d598a
Release 0.11.0 (#713)
- Package updates
    Updated docker NodeJS from 10 to 14
    Updated Angular from 11 to 12
    Updated Material from 11 to 12
    Updated Angular cli from 11 to 12
    Updated Karma from 5 to 6
    Updated rxjs from 6 to 7
    Updated ngrx from 10 to 12
    Updated angularx-qrcode from 10 to 11
    Updated @angular/flex-layout from 11 to 12
    Updated angular-user-idle from 2.2.4 to 2.2.5
    Updated typescript from 4.0.2 to 4.2.4
    Updated zone.js from 0.10.2 to 0.11.4
    Migrated from TSLint to ESLint
    Installed save-dev crypto-browserify & stream-browserify

- Mask password with fixed length #689
- CSRF Token (#696)
- Route lock default password (#700)
- ECL Invoice amount mislabeled #694
- ECL & LND Fee report time zone offset bug fixes #692 & #693
- Loop remove max routing fee validation #690
- Child route refresh bug
- Adding Password Blacklist (#704)
- Fee rate in percentage #621 (#705)
- ECL Adding BaseFee and FeeRate on Channels
- LND Invoice and Payment pagination fix (#707)
- Keysend missing QR code bug fix
- Login page XS layout fix
- Reports tables load time improved (#709)
- Report initial table load bug fix
2021-06-20 16:27:08 -04:00

128 lines
5.9 KiB
JavaScript

var common = require('../../routes/common');
var connect = require('../../routes/connect');
var logger = require('./logger');
const jwt = require("jsonwebtoken");
const otplib = require("otplib");
var crypto = require('crypto');
var ONE_MINUTE = 60000;
var LOCKING_PERIOD = 30 * ONE_MINUTE; // HALF AN HOUR
var ALLOWED_LOGIN_ATTEMPTS = 5;
var failedLoginAttempts = {};
setInterval(() => {
for (var ip in failedLoginAttempts) {
if (new Date().getTime() > (failedLoginAttempts[ip].lastTried + LOCKING_PERIOD)) {
delete failedLoginAttempts[ip];
}
}
}, LOCKING_PERIOD);
getFailedInfo = (reqIP, currentTime) => {
let failed = failedLoginAttempts[reqIP] ? failedLoginAttempts[reqIP] : failedLoginAttempts[reqIP] = {count: 0, lastTried: currentTime};
if (currentTime > (failed.lastTried + LOCKING_PERIOD)) {
failed = failedLoginAttempts[reqIP] = {count: 0, lastTried: currentTime};
}
return failed;
}
handleError = (failed, currentTime, errMsg) => {
if (failed.count >= ALLOWED_LOGIN_ATTEMPTS && (currentTime <= (failed.lastTried + LOCKING_PERIOD))) {
return {
message: "Multiple Failed Login Attempts!",
error: "Application locked for " + (LOCKING_PERIOD/ONE_MINUTE) + " minutes due to multiple failed login attempts! Try again after " + common.convertTimestampToTime((failed.lastTried + LOCKING_PERIOD)/1000) + "!"
};
} else {
return {
message: "Authentication Failed!",
error: errMsg + "\nApplication will be locked after " + (ALLOWED_LOGIN_ATTEMPTS - failed.count) + " more unsuccessful attempts!"
};
}
}
exports.verifyToken = (twoFAToken) => {
if (common.rtl_secret2fa && common.rtl_secret2fa !== '' && otplib.authenticator.check(twoFAToken, common.rtl_secret2fa)) {
return true;
}
return false;
};
exports.authenticateUser = (req, res, next) => {
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'Authenticating User..'});
if(+common.rtl_sso) {
if(req.body.authenticateWith === 'JWT' && jwt.verify(req.body.authenticationValue, common.secret_key)) {
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'User Authenticated'});
res.status(200).json({ token: token });
} else if (req.body.authenticateWith === 'PASSWORD' && common.cookie.trim().length >= 32 && crypto.timingSafeEqual(Buffer.from(crypto.createHash('sha256').update(common.cookie).digest('hex'), 'utf-8'), Buffer.from(req.body.authenticationValue, 'utf-8'))) {
connect.refreshCookie(common.rtl_cookie_path);
const token = jwt.sign(
{ user: 'SSO_USER', configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'User Authenticated.'});
res.status(200).json({ token: token });
} else {
logger.log({level: 'ERROR', fileName: 'Authenticate', msg: 'SSO Authentication Failed! Access key too short or does not match.', error: {error: 'Access key too short or does not match.'}});
res.status(406).json({
message: "SSO Authentication Failed!",
error: "SSO failed. Access key too short or does not match."
});
}
} else {
const currentTime = new Date().getTime();
const reqIP = common.getRequestIP(req);
let failed = getFailedInfo(reqIP, currentTime);
const password = req.body.authenticationValue;
if (common.rtl_pass === password && failed.count < ALLOWED_LOGIN_ATTEMPTS) {
if (req.body.twoFAToken && req.body.twoFAToken !== '') {
if (!this.verifyToken(req.body.twoFAToken)) {
logger.log({level: 'ERROR', fileName: 'Authenticate', msg: 'Invalid Token! Failed IP ' + reqIP, error: {error: 'Invalid token.'}});
failed.count = failed.count + 1;
failed.lastTried = currentTime;
return res.status(401).json(handleError(failed, currentTime, 'Invalid 2FA Token!'));
}
}
delete failedLoginAttempts[reqIP];
let rpcUser = 'NODE_USER';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'User Authenticated'});
res.status(200).json({ token: token });
} else {
logger.log({level: 'ERROR', fileName: 'Authenticate', msg: 'Invalid Password! Failed IP ' + reqIP, error: {error: 'Invalid password.'}});
failed.count = common.rtl_pass !== password ? (failed.count + 1) : failed.count;
failed.lastTried = common.rtl_pass !== password ? currentTime : failed.lastTried;
return res.status(401).json(handleError(failed, currentTime, 'Invalid Password!'));
}
}
};
exports.resetPassword = (req, res, next) => {
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'Resetting Password..'});
if(+common.rtl_sso) {
logger.log({level: 'ERROR', fileName: 'Authenticate', msg: 'Password Reset Failed!', error: {error: 'Password reset failed.'}});
res.status(401).json({
message: "Password Reset Failed!",
error: "Password cannot be reset for SSO authentication!"
});
} else {
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(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'Password Reset Successful'});
res.status(200).json({ token: token });
} else {
logger.log({level: 'ERROR', fileName: 'Authenticate', msg: 'Password Reset Failed!', error: {error: 'Password reset failed.'}});
res.status(401).json({
message: "Password Reset Failed!",
error: "Old password is not correct!"
});
}
}
};