2021-12-29 23:08:41 +00:00
import jwt from 'jsonwebtoken' ;
import * as otplib from 'otplib' ;
import * as crypto from 'crypto' ;
import { Database , DatabaseService } from '../../utils/database.js' ;
import { Logger , LoggerService } from '../../utils/logger.js' ;
import { Common , CommonService } from '../../utils/common.js' ;
const logger : LoggerService = Logger ;
const common : CommonService = Common ;
const ONE_MINUTE = 60000 ;
const LOCKING_PERIOD = 30 * ONE_MINUTE ; // HALF AN HOUR
const ALLOWED_LOGIN_ATTEMPTS = 5 ;
const failedLoginAttempts = { } ;
const databaseService : 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 ) {
2022-10-12 21:38:21 +00:00
databaseService . unloadDatabase ( + req . session . selectedNode . index , req . session . id ) ;
2021-12-29 23:08:41 +00:00
}
2022-08-07 21:12:47 +00:00
req . session . destroy ( ( err ) = > {
res . clearCookie ( 'connect.sid' ) ;
res . status ( 200 ) . json ( { loggedout : true } ) ;
} ) ;
2021-12-29 23:08:41 +00:00
} ;