* User idle timeout
* Only allow one session per user at a time * user 'timestamp' property -> 'account_created' * Better User.getLegacySecurityLevel() using group membership * Client connection management -> client_connections.js * Minor changes & cleanup
This commit is contained in:
parent
36a8d771e8
commit
8d1fac41a9
55
core/bbs.js
55
core/bbs.js
|
@ -6,6 +6,7 @@ var conf = require('./config.js');
|
||||||
var logger = require('./logger.js');
|
var logger = require('./logger.js');
|
||||||
var miscUtil = require('./misc_util.js');
|
var miscUtil = require('./misc_util.js');
|
||||||
var database = require('./database.js');
|
var database = require('./database.js');
|
||||||
|
var clientConns = require('./client_connections.js');
|
||||||
|
|
||||||
var iconv = require('iconv-lite');
|
var iconv = require('iconv-lite');
|
||||||
var paths = require('path');
|
var paths = require('path');
|
||||||
|
@ -152,8 +153,6 @@ function initialize(cb) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var clientConnections = [];
|
|
||||||
|
|
||||||
function startListening() {
|
function startListening() {
|
||||||
if(!conf.config.servers) {
|
if(!conf.config.servers) {
|
||||||
// :TODO: Log error ... output to stderr as well. We can do it all with the logger
|
// :TODO: Log error ... output to stderr as well. We can do it all with the logger
|
||||||
|
@ -189,7 +188,7 @@ function startListening() {
|
||||||
client.runtime = {};
|
client.runtime = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
addNewClient(client);
|
clientConns.addNewClient(client);
|
||||||
|
|
||||||
client.on('ready', function onClientReady() {
|
client.on('ready', function onClientReady() {
|
||||||
// Go to module -- use default error handler
|
// Go to module -- use default error handler
|
||||||
|
@ -199,7 +198,7 @@ function startListening() {
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('end', function onClientEnd() {
|
client.on('end', function onClientEnd() {
|
||||||
removeClient(client);
|
clientConns.removeClient(client);
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on('error', function onClientError(err) {
|
client.on('error', function onClientError(err) {
|
||||||
|
@ -209,7 +208,20 @@ function startListening() {
|
||||||
client.on('close', function onClientClose(hadError) {
|
client.on('close', function onClientClose(hadError) {
|
||||||
var l = hadError ? logger.log.info : logger.log.debug;
|
var l = hadError ? logger.log.info : logger.log.debug;
|
||||||
l( { clientId : client.runtime.id }, 'Connection closed');
|
l( { clientId : client.runtime.id }, 'Connection closed');
|
||||||
removeClient(client);
|
|
||||||
|
clientConns.removeClient(client);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('idle timeout', function idleTimeout() {
|
||||||
|
client.log.info('User idle timeout expired');
|
||||||
|
|
||||||
|
client.gotoMenuModule( { name : 'idleLogoff' }, function goMenuRes(err) {
|
||||||
|
if(err) {
|
||||||
|
// likely just doesn't exist
|
||||||
|
client.term.write('\nIdle timeout expired. Goodbye!\n');
|
||||||
|
client.end();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -222,39 +234,6 @@ function startListening() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNewClient(client) {
|
|
||||||
var id = client.runtime.id = clientConnections.push(client) - 1;
|
|
||||||
|
|
||||||
// Create a client specific logger
|
|
||||||
client.log = logger.log.child( { clientId : id } );
|
|
||||||
|
|
||||||
var connInfo = { ip : client.input.remoteAddress };
|
|
||||||
|
|
||||||
if(client.log.debug()) {
|
|
||||||
connInfo.port = client.input.localPort;
|
|
||||||
connInfo.family = client.input.localFamily;
|
|
||||||
}
|
|
||||||
|
|
||||||
client.log.info(connInfo, 'Client connected');
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeClient(client) {
|
|
||||||
var i = clientConnections.indexOf(client);
|
|
||||||
if(i > -1) {
|
|
||||||
clientConnections.splice(i, 1);
|
|
||||||
|
|
||||||
logger.log.info(
|
|
||||||
{
|
|
||||||
connectionCount : clientConnections.length,
|
|
||||||
clientId : client.runtime.id
|
|
||||||
},
|
|
||||||
'Client disconnected'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function prepareClient(client, cb) {
|
function prepareClient(client, cb) {
|
||||||
// :TODO: it feels like this should go somewhere else... and be a bit more elegant.
|
// :TODO: it feels like this should go somewhere else... and be a bit more elegant.
|
||||||
if('*' === conf.config.preLoginTheme) {
|
if('*' === conf.config.preLoginTheme) {
|
||||||
|
|
|
@ -37,6 +37,7 @@ var Log = require('./logger.js').log;
|
||||||
var user = require('./user.js');
|
var user = require('./user.js');
|
||||||
var moduleUtil = require('./module_util.js');
|
var moduleUtil = require('./module_util.js');
|
||||||
var menuUtil = require('./menu_util.js');
|
var menuUtil = require('./menu_util.js');
|
||||||
|
var Config = require('./config.js').config;
|
||||||
|
|
||||||
var stream = require('stream');
|
var stream = require('stream');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
@ -101,6 +102,18 @@ function Client(input, output) {
|
||||||
this.term = new term.ClientTerminal(this.output);
|
this.term = new term.ClientTerminal(this.output);
|
||||||
this.user = new user.User();
|
this.user = new user.User();
|
||||||
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
this.currentTheme = { info : { name : 'N/A', description : 'None' } };
|
||||||
|
this.lastKeyPressMs = Date.now();
|
||||||
|
|
||||||
|
//
|
||||||
|
// Every 1m, check for idle.
|
||||||
|
//
|
||||||
|
this.idleCheck = setInterval(function checkForIdle() {
|
||||||
|
var nowMs = Date.now();
|
||||||
|
|
||||||
|
if(nowMs - self.lastKeyPressMs >= (Config.misc.idleLogoutSeconds * 1000)) {
|
||||||
|
self.emit('idle timeout');
|
||||||
|
}
|
||||||
|
}, 1000 * 60);
|
||||||
|
|
||||||
Object.defineProperty(this, 'node', {
|
Object.defineProperty(this, 'node', {
|
||||||
get : function() {
|
get : function() {
|
||||||
|
@ -372,6 +385,8 @@ function Client(input, output) {
|
||||||
if(key || ch) {
|
if(key || ch) {
|
||||||
self.log.trace( { key : key, ch : escape(ch) }, 'User keyboard input');
|
self.log.trace( { key : key, ch : escape(ch) }, 'User keyboard input');
|
||||||
|
|
||||||
|
self.lastKeyPressMs = Date.now();
|
||||||
|
|
||||||
self.emit('key press', ch, key);
|
self.emit('key press', ch, key);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -390,6 +405,8 @@ require('util').inherits(Client, stream);
|
||||||
Client.prototype.end = function () {
|
Client.prototype.end = function () {
|
||||||
this.detachCurrentMenuModule();
|
this.detachCurrentMenuModule();
|
||||||
|
|
||||||
|
clearInterval(this.idleCheck);
|
||||||
|
|
||||||
return this.output.end.apply(this.output, arguments);
|
return this.output.end.apply(this.output, arguments);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -417,6 +434,8 @@ Client.prototype.gotoMenuModule = function(options, cb) {
|
||||||
assert(options.name);
|
assert(options.name);
|
||||||
|
|
||||||
// Assign a default missing module handler callback if none was provided
|
// Assign a default missing module handler callback if none was provided
|
||||||
|
var callbackOnErrorOnly = !_.isFunction(cb);
|
||||||
|
|
||||||
cb = miscUtil.valueWithDefault(cb, self.defaultHandlerMissingMod());
|
cb = miscUtil.valueWithDefault(cb, self.defaultHandlerMissingMod());
|
||||||
|
|
||||||
self.detachCurrentMenuModule();
|
self.detachCurrentMenuModule();
|
||||||
|
@ -436,6 +455,10 @@ Client.prototype.gotoMenuModule = function(options, cb) {
|
||||||
modInst.enter(self);
|
modInst.enter(self);
|
||||||
|
|
||||||
self.currentMenuModule = modInst;
|
self.currentMenuModule = modInst;
|
||||||
|
|
||||||
|
if(!callbackOnErrorOnly) {
|
||||||
|
cb(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* jslint node: true */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var logger = require('./logger.js');
|
||||||
|
|
||||||
|
exports.addNewClient = addNewClient;
|
||||||
|
exports.removeClient = removeClient;
|
||||||
|
|
||||||
|
var clientConnections = [];
|
||||||
|
exports.clientConnections = clientConnections;
|
||||||
|
|
||||||
|
function addNewClient(client) {
|
||||||
|
var id = client.runtime.id = clientConnections.push(client) - 1;
|
||||||
|
|
||||||
|
// Create a client specific logger
|
||||||
|
client.log = logger.log.child( { clientId : id } );
|
||||||
|
|
||||||
|
var connInfo = { ip : client.input.remoteAddress };
|
||||||
|
|
||||||
|
if(client.log.debug()) {
|
||||||
|
connInfo.port = client.input.localPort;
|
||||||
|
connInfo.family = client.input.localFamily;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.log.info(connInfo, 'Client connected');
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeClient(client) {
|
||||||
|
client.end();
|
||||||
|
|
||||||
|
var i = clientConnections.indexOf(client);
|
||||||
|
if(i > -1) {
|
||||||
|
clientConnections.splice(i, 1);
|
||||||
|
|
||||||
|
logger.log.info(
|
||||||
|
{
|
||||||
|
connectionCount : clientConnections.length,
|
||||||
|
clientId : client.runtime.id
|
||||||
|
},
|
||||||
|
'Client disconnected'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* :TODO: make a public API elsewhere
|
||||||
|
function getActiveClientInformation() {
|
||||||
|
var info = {};
|
||||||
|
|
||||||
|
clientConnections.forEach(function connEntry(cc) {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
*/
|
|
@ -145,6 +145,10 @@ function getDefaultConfig() {
|
||||||
*/
|
*/
|
||||||
},
|
},
|
||||||
|
|
||||||
|
misc : {
|
||||||
|
idleLogoutSeconds : 60 * 3, // 3m
|
||||||
|
},
|
||||||
|
|
||||||
logging : {
|
logging : {
|
||||||
level : 'debug'
|
level : 'debug'
|
||||||
}
|
}
|
||||||
|
|
|
@ -147,7 +147,7 @@ function connectEntry(client) {
|
||||||
displayBanner(term);
|
displayBanner(term);
|
||||||
|
|
||||||
setTimeout(function onTimeout() {
|
setTimeout(function onTimeout() {
|
||||||
client.gotoMenuModule( { name : Config.firstMenu });
|
client.gotoMenuModule( { name : Config.firstMenu } );
|
||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/* jslint node: true */
|
/* jslint node: true */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var theme = require('../core/theme.js');
|
var theme = require('./theme.js');
|
||||||
//var Log = require('../core/logger.js').log;
|
var clientConnections = require('./client_connections.js').clientConnections;
|
||||||
var ansi = require('../core/ansi_term.js');
|
var ansi = require('./ansi_term.js');
|
||||||
var userDb = require('./database.js').dbs.user;
|
var userDb = require('./database.js').dbs.user;
|
||||||
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
@ -18,11 +18,41 @@ function login(callingMenu, formData, extraArgs) {
|
||||||
if(err) {
|
if(err) {
|
||||||
client.log.info( { username : formData.value.username }, 'Failed login attempt %s', err);
|
client.log.info( { username : formData.value.username }, 'Failed login attempt %s', err);
|
||||||
|
|
||||||
|
// :TODO: if username exists, record failed login attempt to properties
|
||||||
|
// :TODO: check Config max failed logon attempts/etc.
|
||||||
|
|
||||||
client.gotoMenuModule( { name : callingMenu.menuConfig.fallback } );
|
client.gotoMenuModule( { name : callingMenu.menuConfig.fallback } );
|
||||||
} else {
|
} else {
|
||||||
var now = new Date();
|
var now = new Date();
|
||||||
var user = callingMenu.client.user;
|
var user = callingMenu.client.user;
|
||||||
|
|
||||||
|
//
|
||||||
|
// Ensure this user is not already logged in.
|
||||||
|
// Loop through active connections -- which includes the current --
|
||||||
|
// and check for matching user ID. If the count is > 1, disallow.
|
||||||
|
//
|
||||||
|
var existingClientConnection;
|
||||||
|
clientConnections.forEach(function connEntry(cc) {
|
||||||
|
if(cc.user !== user && cc.user.userId === user.userId) {
|
||||||
|
existingClientConnection = cc;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if(existingClientConnection) {
|
||||||
|
client.log.info( {
|
||||||
|
existingClientId : existingClientConnection.runtime.id,
|
||||||
|
username : user.username,
|
||||||
|
userId : user.userId },
|
||||||
|
'Already logged in'
|
||||||
|
);
|
||||||
|
|
||||||
|
// :TODO: display message/art/etc.
|
||||||
|
|
||||||
|
client.gotoMenuModule( { name : callingMenu.menuConfig.fallback } );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// use client.user so we can get correct case
|
// use client.user so we can get correct case
|
||||||
client.log.info( { username : user.username }, 'Successful login');
|
client.log.info( { username : user.username }, 'Successful login');
|
||||||
|
|
||||||
|
@ -81,12 +111,14 @@ function login(callingMenu, formData, extraArgs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function logoff(callingMenu, formData, extraArgs) {
|
function logoff(callingMenu, formData, extraArgs) {
|
||||||
|
//
|
||||||
|
// Simple logoff. Note that recording of @ logoff properties/stats
|
||||||
|
// occurs elsewhere!
|
||||||
|
//
|
||||||
var client = callingMenu.client;
|
var client = callingMenu.client;
|
||||||
|
|
||||||
// :TODO: record this.
|
|
||||||
|
|
||||||
setTimeout(function timeout() {
|
setTimeout(function timeout() {
|
||||||
client.term.write(ansi.normal() + '\nATH0\n');
|
client.term.write(ansi.normal() + '\n+++ATH0\n');
|
||||||
client.end();
|
client.end();
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
|
@ -52,7 +52,13 @@ function User() {
|
||||||
};
|
};
|
||||||
|
|
||||||
this.getLegacySecurityLevel = function() {
|
this.getLegacySecurityLevel = function() {
|
||||||
return self.isRoot() ? 100 : 30;
|
if(self.isRoot() || self.isGroupMember('sysops')) {
|
||||||
|
return 100;
|
||||||
|
} else if(self.isGroupMember('users')) {
|
||||||
|
return 30;
|
||||||
|
} else {
|
||||||
|
return 10; // :TODO: Is this what we want?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ exports.getModule = AbracadabraModule;
|
||||||
|
|
||||||
var activeDoorNodeInstances = {};
|
var activeDoorNodeInstances = {};
|
||||||
|
|
||||||
|
var doorInstances = {}; // name -> { count : <instCount>, { <nodeNum> : <inst> } }
|
||||||
|
|
||||||
exports.moduleInfo = {
|
exports.moduleInfo = {
|
||||||
name : 'Abracadabra',
|
name : 'Abracadabra',
|
||||||
desc : 'External BBS Door Module',
|
desc : 'External BBS Door Module',
|
||||||
|
@ -56,7 +58,8 @@ function AbracadabraModule(options) {
|
||||||
/*
|
/*
|
||||||
:TODO:
|
:TODO:
|
||||||
* disconnecting wile door is open leaves dosemu
|
* disconnecting wile door is open leaves dosemu
|
||||||
|
* http://bbslink.net/sysop.php support
|
||||||
|
* Font support ala all other menus... or does this just work?
|
||||||
*/
|
*/
|
||||||
|
|
||||||
this.initSequence = function() {
|
this.initSequence = function() {
|
||||||
|
|
|
@ -92,7 +92,7 @@ function submitApplication(callingMenu, formData, extraArgs) {
|
||||||
affiliation : formData.value.affils,
|
affiliation : formData.value.affils,
|
||||||
email_address : formData.value.email,
|
email_address : formData.value.email,
|
||||||
web_address : formData.value.web,
|
web_address : formData.value.web,
|
||||||
timestamp : new Date().toISOString(),
|
account_created : new Date().toISOString(),
|
||||||
|
|
||||||
// :TODO: This is set in User.create() -- proabbly don't need it here:
|
// :TODO: This is set in User.create() -- proabbly don't need it here:
|
||||||
//account_status : Config.users.requireActivation ? user.User.AccountStatus.inactive : user.User.AccountStatus.active,
|
//account_status : Config.users.requireActivation ? user.User.AccountStatus.inactive : user.User.AccountStatus.active,
|
||||||
|
|
Binary file not shown.
|
@ -285,6 +285,14 @@
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
// Mods
|
// Mods
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
"idleLogoff" : {
|
||||||
|
"art" : "IDLELOG",
|
||||||
|
"options" : { "cls" : true },
|
||||||
|
"action" : "@systemMethod:logoff"
|
||||||
|
},
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
// Mods
|
||||||
|
////////////////////////////////////////////////////////////////////////
|
||||||
"lastCallers" :{
|
"lastCallers" :{
|
||||||
"module" : "last_callers",
|
"module" : "last_callers",
|
||||||
"art" : "LASTCALL.ANS",
|
"art" : "LASTCALL.ANS",
|
||||||
|
|
Loading…
Reference in New Issue