You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RTL/controllers/shared/authenticate.js

112 lines
5.5 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;
}
handleMultipleFailedAttemptsError = (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 attempts!\nTry 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' }, common.secret_key);
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'User Authenticated.'});
res.status(200).json({ token: token });
} else {
const errMsg = 'SSO Authentication Failed! Access key too short or does not match.';
const err = common.handleError({ statusCode: 406, message: 'SSO Authentication Error', error: errMsg }, 'Authenticate', errMsg);
return res.status(err.statusCode).json({message: err.message, error: err.error});
}
} 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(handleMultipleFailedAttemptsError(failed, currentTime, 'Invalid 2FA Token!'));
}
}
delete failedLoginAttempts[reqIP];
const token = jwt.sign({ user: 'NODE_USER' }, 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(handleMultipleFailedAttemptsError(failed, currentTime, 'Invalid Password!'));
}
}
};
exports.resetPassword = (req, res, next) => {
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'Resetting Password..'});
if (+common.rtl_sso) {
const errMsg = 'Password cannot be reset for SSO authentication';
const err = common.handleError({ statusCode: 401, message: 'Password Reset Error', error: errMsg }, 'Authenticate', errMsg);
return res.status(err.statusCode).json({message: err.message, error: err.error});
} else {
const currPassword = req.body.currPassword;
if (common.rtl_pass === currPassword) {
common.rtl_pass = connect.replacePasswordWithHash(req.body.newPassword);
const token = jwt.sign({ user: 'NODE_USER' }, common.secret_key);
logger.log({level: 'INFO', fileName: 'Authenticate', msg: 'Password Reset Successful'});
res.status(200).json({ token: token });
} else {
const errMsg = 'Incorrect Old Password';
const err = common.handleError({ statusCode: 401, message: 'Password Reset Error', error: errMsg }, 'Authenticate', errMsg);
return res.status(err.statusCode).json({message: err.message, error: err.error});
}
}
};