2015-10-19 23:21:47 +00:00
/* jslint node: true */
'use strict' ;
2018-06-23 03:26:46 +00:00
// ENiGMA½
2022-06-05 20:04:25 +00:00
const setClientTheme = require ( './theme.js' ) . setClientTheme ;
2018-06-23 03:26:46 +00:00
const clientConnections = require ( './client_connections.js' ) . clientConnections ;
2022-06-05 20:04:25 +00:00
const StatLog = require ( './stat_log.js' ) ;
const logger = require ( './logger.js' ) ;
const Events = require ( './events.js' ) ;
const Config = require ( './config.js' ) . get ;
const { Errors , ErrorReasons } = require ( './enig_error.js' ) ;
const UserProps = require ( './user_property.js' ) ;
const SysProps = require ( './system_property.js' ) ;
const SystemLogKeys = require ( './system_log.js' ) ;
const User = require ( './user.js' ) ;
2019-09-10 03:35:12 +00:00
const {
getMessageConferenceByTag ,
getMessageAreaByTag ,
2019-09-12 03:21:33 +00:00
getSuitableMessageConfAndAreaTags ,
2022-06-05 20:04:25 +00:00
} = require ( './message_area.js' ) ;
const { getFileAreaByTag , getDefaultFileAreaTag } = require ( './file_base_area.js' ) ;
2015-10-19 23:21:47 +00:00
2018-06-23 03:26:46 +00:00
// deps
const async = require ( 'async' ) ;
2018-11-23 06:07:37 +00:00
const _ = require ( 'lodash' ) ;
2019-05-10 01:56:04 +00:00
const assert = require ( 'assert' ) ;
2020-11-27 02:51:00 +00:00
const moment = require ( 'moment' ) ;
2015-10-19 23:21:47 +00:00
2022-06-05 20:04:25 +00:00
exports . userLogin = userLogin ;
exports . recordLogin = recordLogin ;
exports . transformLoginError = transformLoginError ;
2015-10-19 23:21:47 +00:00
2019-02-21 06:55:09 +00:00
function userLogin ( client , username , password , options , cb ) {
2022-06-05 20:04:25 +00:00
if ( ! cb && _ . isFunction ( options ) ) {
2019-02-21 06:55:09 +00:00
cb = options ;
options = { } ;
}
2018-12-24 22:32:38 +00:00
const config = Config ( ) ;
if ( config . users . badUserNames . includes ( username . toLowerCase ( ) ) ) {
2022-05-07 17:47:04 +00:00
client . log . info ( { username , ip : client . remoteAddress } , ` Attempt to login with banned username " ${ username } " ` ) ;
2018-12-25 07:18:04 +00:00
// slow down a bit to thwart brute force attacks
2022-06-05 20:04:25 +00:00
return setTimeout ( ( ) => {
2018-12-25 07:18:04 +00:00
return cb ( Errors . BadLogin ( 'Disallowed username' , ErrorReasons . NotAllowed ) ) ;
} , 2000 ) ;
2018-12-24 22:32:38 +00:00
}
2018-11-23 06:07:37 +00:00
2019-02-23 05:51:12 +00:00
const authInfo = {
username ,
password ,
} ;
2022-06-05 20:04:25 +00:00
authInfo . type = options . authType || User . AuthFactor1Types . Password ;
2019-02-23 05:51:12 +00:00
authInfo . pubKey = options . ctx ;
client . user . authenticateFactor1 ( authInfo , err => {
2022-06-05 20:04:25 +00:00
if ( err ) {
2019-05-08 03:36:33 +00:00
return cb ( transformLoginError ( err , client , username ) ) ;
2018-06-22 05:15:04 +00:00
}
2018-11-23 06:07:37 +00:00
const user = client . user ;
// Good login; reset any failed attempts
2019-05-08 03:36:33 +00:00
delete client . sessionFailedLoginAttempts ;
2015-10-19 23:21:47 +00:00
2018-06-22 05:15:04 +00:00
//
2018-06-23 03:26:46 +00:00
// Ensure this user is not already logged in.
2018-06-22 05:15:04 +00:00
//
2018-11-22 02:50:03 +00:00
const existingClientConnection = clientConnections . find ( cc => {
2022-06-05 20:04:25 +00:00
return (
user !== cc . user && // not current connection
user . userId === cc . user . userId
) ; // ...but same user
2018-06-22 05:15:04 +00:00
} ) ;
2015-10-19 23:21:47 +00:00
2018-06-22 05:15:04 +00:00
if ( existingClientConnection ) {
2022-05-07 17:47:04 +00:00
client . log . warn (
2018-06-22 05:15:04 +00:00
{
2022-06-05 20:04:25 +00:00
existingNodeId : existingClientConnection . node ,
username : user . username ,
userId : user . userId ,
2018-06-22 05:15:04 +00:00
} ,
2022-05-07 17:47:04 +00:00
` User " ${ user . username } " already logged in on node ${ existingClientConnection . node } `
2018-06-22 05:15:04 +00:00
) ;
2015-10-19 23:21:47 +00:00
2022-06-05 20:04:25 +00:00
return cb (
Errors . BadLogin (
` User ${ user . username } already logged in. ` ,
ErrorReasons . AlreadyLoggedIn
)
) ;
2018-06-22 05:15:04 +00:00
}
2015-10-19 23:21:47 +00:00
2018-06-23 03:26:46 +00:00
// update client logger with addition of username
2022-06-05 20:04:25 +00:00
client . log = logger . log . child ( {
nodeId : client . log . fields . nodeId ,
sessionId : client . log . fields . sessionId ,
username : user . username ,
} ) ;
2020-05-14 01:30:57 +00:00
2022-05-07 17:47:04 +00:00
client . log . info ( ` User " ${ user . username } " successfully logged in ` ) ;
2015-10-19 23:21:47 +00:00
2018-06-23 03:26:46 +00:00
// User's unique session identifier is the same as the connection itself
2022-06-05 20:04:25 +00:00
user . sessionId = client . session . uniqueId ; // convenience
2018-06-04 01:58:31 +00:00
2022-06-05 20:04:25 +00:00
Events . emit ( Events . getSystemEvents ( ) . UserLogin , { user } ) ;
2018-06-03 23:59:16 +00:00
2019-05-08 03:36:33 +00:00
setClientTheme ( client , user . properties [ UserProps . ThemeId ] ) ;
2019-09-10 03:35:12 +00:00
postLoginPrep ( client , err => {
2022-06-05 20:04:25 +00:00
if ( err ) {
2019-09-10 03:35:12 +00:00
return cb ( err ) ;
}
2022-06-05 20:04:25 +00:00
if ( user . authenticated ) {
2019-09-10 03:35:12 +00:00
return recordLogin ( client , cb ) ;
}
// recordLogin() must happen after 2FA!
return cb ( null ) ;
} ) ;
2018-06-22 05:15:04 +00:00
} ) ;
2019-05-08 03:36:33 +00:00
}
2019-09-10 03:35:12 +00:00
function postLoginPrep ( client , cb ) {
async . series (
[
2022-06-05 20:04:25 +00:00
callback => {
2019-09-10 03:35:12 +00:00
//
// User may (no longer) have read (view) rights to their current
// message, conferences and/or areas. Move them out if so.
//
2022-06-05 20:04:25 +00:00
const confTag = client . user . getProperty ( UserProps . MessageConfTag ) ;
const conf = getMessageConferenceByTag ( confTag ) || { } ;
const area =
getMessageAreaByTag (
client . user . getProperty ( UserProps . MessageAreaTag ) ,
confTag
) || { } ;
if (
! client . acs . hasMessageConfRead ( conf ) ||
! client . acs . hasMessageAreaRead ( area )
) {
2019-09-12 03:21:33 +00:00
// move them out of both area and possibly conf to something suitable, hopefully.
2022-06-05 20:04:25 +00:00
const [ newConfTag , newAreaTag ] =
getSuitableMessageConfAndAreaTags ( client ) ;
client . user . persistProperties (
{
[ UserProps . MessageConfTag ] : newConfTag ,
[ UserProps . MessageAreaTag ] : newAreaTag ,
} ,
err => {
return callback ( err ) ;
}
) ;
2019-09-10 03:35:12 +00:00
} else {
return callback ( null ) ;
}
} ,
2022-06-05 20:04:25 +00:00
callback => {
2019-09-10 03:35:12 +00:00
// Likewise for file areas
2022-06-05 20:04:25 +00:00
const area =
getFileAreaByTag ( client . user . getProperty ( UserProps . FileAreaTag ) ) ||
{ } ;
if ( ! client . acs . hasFileAreaRead ( area ) ) {
2019-09-10 03:35:12 +00:00
const areaTag = getDefaultFileAreaTag ( client ) || '' ;
client . user . persistProperty ( UserProps . FileAreaTag , areaTag , err => {
return callback ( err ) ;
} ) ;
} else {
return callback ( null ) ;
}
2022-06-05 20:04:25 +00:00
} ,
2019-09-10 03:35:12 +00:00
] ,
err => {
return cb ( err ) ;
}
) ;
}
2019-05-08 03:36:33 +00:00
function recordLogin ( client , cb ) {
2022-06-05 20:04:25 +00:00
assert ( client . user . authenticated ) ; // don't get in situations where this isn't true
2019-05-10 01:56:04 +00:00
2019-05-08 03:36:33 +00:00
const user = client . user ;
2020-11-27 02:51:00 +00:00
const loginTimestamp = StatLog . now ;
2019-05-08 03:36:33 +00:00
async . parallel (
[
2022-06-05 20:04:25 +00:00
callback => {
2019-05-08 03:36:33 +00:00
StatLog . incrementNonPersistentSystemStat ( SysProps . LoginsToday , 1 ) ;
return StatLog . incrementSystemStat ( SysProps . LoginCount , 1 , callback ) ;
} ,
( callback ) => {
2020-11-27 02:51:00 +00:00
return StatLog . setUserStat ( user , UserProps . LastLoginTs , loginTimestamp , callback ) ;
2019-05-08 03:36:33 +00:00
} ,
2022-06-05 20:04:25 +00:00
callback => {
2019-05-08 03:36:33 +00:00
return StatLog . incrementUserStat ( user , UserProps . LoginCount , 1 , callback ) ;
} ,
2022-06-05 20:04:25 +00:00
callback => {
2019-05-08 03:36:33 +00:00
const loginHistoryMax = Config ( ) . statLog . systemEvents . loginHistoryMax ;
const historyItem = JSON . stringify ( {
2022-06-05 20:04:25 +00:00
userId : user . userId ,
sessionId : user . sessionId ,
2019-05-08 03:36:33 +00:00
} ) ;
return StatLog . appendSystemLogEntry (
SystemLogKeys . UserLoginHistory ,
historyItem ,
loginHistoryMax ,
StatLog . KeepType . Max ,
callback
) ;
2020-11-27 02:51:00 +00:00
} ,
( callback ) => {
// Update live last login information which includes additional
// (pre-resolved) information such as user name/etc.
const lastLogin = {
userId : user . userId ,
sessionId : user . sessionId ,
userName : user . username ,
realName : user . getProperty ( UserProps . RealName ) ,
affiliation : user . getProperty ( UserProps . Affiliations ) ,
emailAddress : user . getProperty ( UserProps . EmailAddress ) ,
sex : user . getProperty ( UserProps . Sex ) ,
location : user . getProperty ( UserProps . Location ) ,
timestamp : moment ( loginTimestamp ) ,
} ;
StatLog . setNonPersistentSystemStat ( SysProps . LastLogin , lastLogin ) ;
return callback ( null ) ;
2019-05-08 03:36:33 +00:00
}
] ,
err => {
return cb ( err ) ;
}
) ;
}
function transformLoginError ( err , client , username ) {
2022-06-05 20:04:25 +00:00
client . sessionFailedLoginAttempts =
_ . get ( client , 'sessionFailedLoginAttempts' , 0 ) + 1 ;
2019-05-08 03:36:33 +00:00
const disconnect = Config ( ) . users . failedLogin . disconnect ;
2022-06-05 20:04:25 +00:00
if ( disconnect > 0 && client . sessionFailedLoginAttempts >= disconnect ) {
2019-05-08 03:36:33 +00:00
err = Errors . BadLogin ( 'To many failed login attempts' , ErrorReasons . TooMany ) ;
}
2022-06-04 23:39:48 +00:00
client . log . warn ( { username , ip : client . remoteAddress , reason : err . message } , ` Failed login attempt for user " ${ username } ", ${ client . friendlyRemoteAddress ( ) } ` ) ;
2019-05-08 03:36:33 +00:00
return err ;
2022-06-05 20:04:25 +00:00
}