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!"
} ) ;
}
}
} ;