From e5398db07b28a9d8153ec5ad256f60ecce57d4c3 Mon Sep 17 00:00:00 2001 From: Bryan Ashby Date: Tue, 7 May 2019 21:36:33 -0600 Subject: [PATCH] WIP on OTP 2FA, stats, etc. --- core/enig_error.js | 1 + core/user_login.js | 103 +++++++++++++++++++++++++-------------------- 2 files changed, 59 insertions(+), 45 deletions(-) diff --git a/core/enig_error.js b/core/enig_error.js index 33771564..08a3312e 100644 --- a/core/enig_error.js +++ b/core/enig_error.js @@ -51,4 +51,5 @@ exports.ErrorReasons = { Inactive : 'INACTIVE', Locked : 'LOCKED', NotAllowed : 'NOTALLOWED', + Invalid2FA : 'INVALID2FA', }; diff --git a/core/user_login.js b/core/user_login.js index 98bdeefb..ed2ba6a5 100644 --- a/core/user_login.js +++ b/core/user_login.js @@ -21,7 +21,9 @@ const User = require('./user.js'); const async = require('async'); const _ = require('lodash'); -exports.userLogin = userLogin; +exports.userLogin = userLogin; +exports.recordLogin = recordLogin; +exports.transformLoginError = transformLoginError; function userLogin(client, username, password, options, cb) { if(!cb && _.isFunction(options)) { @@ -50,20 +52,13 @@ function userLogin(client, username, password, options, cb) { client.user.authenticateFactor1(authInfo, err => { if(err) { - client.user.sessionFailedLoginAttempts = _.get(client.user, 'sessionFailedLoginAttempts', 0) + 1; - const disconnect = config.users.failedLogin.disconnect; - if(disconnect > 0 && client.user.sessionFailedLoginAttempts >= disconnect) { - err = Errors.BadLogin('To many failed login attempts', ErrorReasons.TooMany); - } - - client.log.info( { username, ip : client.remoteAddress, reason : err.message }, 'Failed login attempt'); - return cb(err); + return cb(transformLoginError(err, client, username)); } const user = client.user; // Good login; reset any failed attempts - delete user.sessionFailedLoginAttempts; + delete client.sessionFailedLoginAttempts; // // Ensure this user is not already logged in. @@ -104,41 +99,59 @@ function userLogin(client, username, password, options, cb) { Events.emit(Events.getSystemEvents().UserLogin, { user } ); - async.parallel( - [ - function setTheme(callback) { - setClientTheme(client, user.properties[UserProps.ThemeId]); - return callback(null); - }, - function updateSystemLoginCount(callback) { - StatLog.incrementNonPersistentSystemStat(SysProps.LoginsToday, 1); - return StatLog.incrementSystemStat(SysProps.LoginCount, 1, callback); - }, - function recordLastLogin(callback) { - return StatLog.setUserStat(user, UserProps.LastLoginTs, StatLog.now, callback); - }, - function updateUserLoginCount(callback) { - return StatLog.incrementUserStat(user, UserProps.LoginCount, 1, callback); - }, - function recordLoginHistory(callback) { - const loginHistoryMax = Config().statLog.systemEvents.loginHistoryMax; - const historyItem = JSON.stringify({ - userId : user.userId, - sessionId : user.sessionId, - }); + setClientTheme(client, user.properties[UserProps.ThemeId]); + if(user.authenticated) { + return recordLogin(client, cb); + } - return StatLog.appendSystemLogEntry( - SystemLogKeys.UserLoginHistory, - historyItem, - loginHistoryMax, - StatLog.KeepType.Max, - callback - ); - } - ], - err => { - return cb(err); - } - ); + // recordLogin() must happen after 2FA! + return cb(null); }); +} + +function recordLogin(client, cb) { + const user = client.user; + async.parallel( + [ + (callback) => { + StatLog.incrementNonPersistentSystemStat(SysProps.LoginsToday, 1); + return StatLog.incrementSystemStat(SysProps.LoginCount, 1, callback); + }, + (callback) => { + return StatLog.setUserStat(user, UserProps.LastLoginTs, StatLog.now, callback); + }, + (callback) => { + return StatLog.incrementUserStat(user, UserProps.LoginCount, 1, callback); + }, + (callback) => { + const loginHistoryMax = Config().statLog.systemEvents.loginHistoryMax; + const historyItem = JSON.stringify({ + userId : user.userId, + sessionId : user.sessionId, + }); + + return StatLog.appendSystemLogEntry( + SystemLogKeys.UserLoginHistory, + historyItem, + loginHistoryMax, + StatLog.KeepType.Max, + callback + ); + } + ], + err => { + return cb(err); + } + ); +} + +function transformLoginError(err, client, username) { + client.sessionFailedLoginAttempts = _.get(client, 'sessionFailedLoginAttempts', 0) + 1; + const disconnect = Config().users.failedLogin.disconnect; + if(disconnect > 0 && client.sessionFailedLoginAttempts >= disconnect) { + err = Errors.BadLogin('To many failed login attempts', ErrorReasons.TooMany); + } + + client.log.info( { username, ip : client.remoteAddress, reason : err.message }, 'Failed login attempt'); + return err; } \ No newline at end of file