2021-12-29 23:08:41 +00:00
import jwt from 'jsonwebtoken' ;
import * as otplib from 'otplib' ;
import * as crypto from 'crypto' ;
import { Database } from '../../utils/database.js' ;
import { Logger } from '../../utils/logger.js' ;
import { Common } from '../../utils/common.js' ;
const logger = Logger ;
const common = Common ;
const ONE _MINUTE = 60000 ;
const LOCKING _PERIOD = 30 * ONE _MINUTE ; // HALF AN HOUR
const ALLOWED _LOGIN _ATTEMPTS = 5 ;
const failedLoginAttempts = { } ;
const databaseService = Database ;
const loginInterval = setInterval ( ( ) => {
for ( const ip in failedLoginAttempts ) {
if ( new Date ( ) . getTime ( ) > ( failedLoginAttempts [ ip ] . lastTried + LOCKING _PERIOD ) ) {
delete failedLoginAttempts [ ip ] ;
clearInterval ( loginInterval ) ;
}
}
} , LOCKING _PERIOD ) ;
export const getFailedInfo = ( reqIP , currentTime ) => {
let failed = { count : 0 , lastTried : currentTime } ;
if ( ( ! failedLoginAttempts [ reqIP ] ) || ( currentTime > ( failed . lastTried + LOCKING _PERIOD ) ) ) {
failed = { count : 0 , lastTried : currentTime } ;
failedLoginAttempts [ reqIP ] = failed ;
}
else {
failed = failedLoginAttempts [ reqIP ] ;
}
return failed ;
} ;
const 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!'
} ;
}
} ;
export const verifyToken = ( twoFAToken ) => ! ! ( common . rtl _secret2fa && common . rtl _secret2fa !== '' && otplib . authenticator . check ( twoFAToken , common . rtl _secret2fa ) ) ;
export const authenticateUser = ( req , res , next ) => {
logger . log ( { selectedNode : req . session . selectedNode , 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 ( { selectedNode : req . session . selectedNode , level : 'INFO' , fileName : 'Authenticate' , msg : 'User Authenticated' } ) ;
res . status ( 406 ) . json ( { message : 'SSO Authentication Error' , error : 'Login with Password is not allowed with SSO.' } ) ;
}
else if ( req . body . authenticateWith === 'PASSWORD' ) {
2022-05-01 17:35:20 +00:00
if ( common . cookie _value . trim ( ) . length >= 32 && crypto . timingSafeEqual ( Buffer . from ( crypto . createHash ( 'sha256' ) . update ( common . cookie _value ) . digest ( 'hex' ) , 'utf-8' ) , Buffer . from ( req . body . authenticationValue , 'utf-8' ) ) ) {
2021-12-29 23:08:41 +00:00
common . refreshCookie ( ) ;
if ( ! req . session . selectedNode ) {
req . session . selectedNode = common . initSelectedNode ;
}
const token = jwt . sign ( { user : 'SSO_USER' } , common . secret _key ) ;
2022-01-16 20:55:50 +00:00
logger . log ( { selectedNode : req . session . selectedNode , level : 'INFO' , fileName : 'Authenticate' , msg : 'User Authenticated' } ) ;
2021-12-29 23:08:41 +00:00
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 , req . session . selectedNode ) ;
return res . status ( err . statusCode ) . json ( { message : err . message , error : err . error } ) ;
}
}
}
else {
const currentTime = new Date ( ) . getTime ( ) ;
const reqIP = common . getRequestIP ( req ) ;
const 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 ( ! verifyToken ( req . body . twoFAToken ) ) {
logger . log ( { selectedNode : req . session . selectedNode , 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!' ) ) ;
}
}
if ( ! req . session . selectedNode ) {
req . session . selectedNode = common . initSelectedNode ;
}
delete failedLoginAttempts [ reqIP ] ;
const token = jwt . sign ( { user : 'NODE_USER' } , common . secret _key ) ;
logger . log ( { selectedNode : req . session . selectedNode , level : 'INFO' , fileName : 'Authenticate' , msg : 'User Authenticated' } ) ;
res . status ( 200 ) . json ( { token : token } ) ;
}
else {
logger . log ( { selectedNode : req . session . selectedNode , 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!' ) ) ;
}
}
} ;
export const resetPassword = ( req , res , next ) => {
logger . log ( { selectedNode : req . session . selectedNode , 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 , req . session . selectedNode ) ;
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 = common . replacePasswordWithHash ( req . body . newPassword ) ;
const token = jwt . sign ( { user : 'NODE_USER' } , common . secret _key ) ;
logger . log ( { selectedNode : req . session . selectedNode , 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 , req . session . selectedNode ) ;
return res . status ( err . statusCode ) . json ( { message : err . message , error : err . error } ) ;
}
}
} ;
export const logoutUser = ( req , res , next ) => {
logger . log ( { selectedNode : req . session . selectedNode , level : 'INFO' , fileName : 'Authenticate' , msg : 'Logged out' } ) ;
if ( req . session . selectedNode && req . session . selectedNode . index ) {
databaseService . unloadDatabase ( + req . session . selectedNode . index ) ;
}
req . session . destroy ( ) ;
res . status ( 200 ) . json ( { loggedout : true } ) ;
} ;