diff --git a/core/bbs.js b/core/bbs.js index 23a9a18f..db64ddc6 100644 --- a/core/bbs.js +++ b/core/bbs.js @@ -6,6 +6,7 @@ var conf = require('./config.js'); var modules = require('./modules.js'); var logger = require('./logger.js'); var miscUtil = require('./misc_util.js'); +var database = require('./database.js'); var iconv = require('iconv-lite'); var paths = require('path'); @@ -49,10 +50,6 @@ exports.bbsMain = function() { logger.init(); - preServingInit(); - - startListening(); - process.on('SIGINT', function onSigInt() { // :TODO: for any client in |clientConnections|, if 'ready', send a "Server Disconnecting" + semi-gracefull hangup // e.g. client.disconnectNow() @@ -60,6 +57,12 @@ exports.bbsMain = function() { logger.log.info('Process interrupted, shutting down'); process.exit(); }); + + database.initializeDatabases(); + + preServingInit(); + + startListening(); }; function parseArgs() { diff --git a/core/config.js b/core/config.js index 6614c833..ad29126f 100644 --- a/core/config.js +++ b/core/config.js @@ -6,6 +6,7 @@ var paths = require('path'); var miscUtil = require('./misc_util.js'); module.exports = { + // :TODO: remove this ... anti-pattern! config : undefined, defaultPath : function() { @@ -31,6 +32,7 @@ module.exports = { servers : paths.join(__dirname, './servers/'), art : paths.join(__dirname, './../mods/art/'), logs : paths.join(__dirname, './../logs/'), // :TODO: set up based on system, e.g. /var/logs/enigmabbs or such + db : paths.join(__dirname, './../db/'), }, servers : { diff --git a/core/database.js b/core/database.js new file mode 100644 index 00000000..eb00dc6f --- /dev/null +++ b/core/database.js @@ -0,0 +1,44 @@ +/* jslint node: true */ +'use strict'; + +var conf = require('./config.js'); +var sqlite3 = require('sqlite3'); +var paths = require('path'); + +exports.initializeDatabases = initializeDatabases; +exports.user = user; + +// db handles +var user; + +function getDatabasePath(name) { + return paths.join(conf.config.paths.db, name + '.sqlite3'); +} + +function initializeDatabases() { + // :TODO: this will need to change if more DB's are added + user = new sqlite3.Database(getDatabasePath('user')); + + user.serialize(function onSerialized() { + createUserTables(); + }); +} + +function createUserTables() { + user.run( + 'CREATE TABLE IF NOT EXISTS user (' + + ' id INTEGER PRIMARY KEY,' + + ' user_name VARCHAR NOT NULL,' + + ' UNIQUE(user_name)' + + ');' + ); + + user.run( + 'CREATE TABLE IF NOT EXISTS user_property (' + + ' user_id INTEGER NOT NULL,' + + ' prop_name VARCHAR NOT NULL,' + + ' prop_value VARCHAR,' + + ' UNIQUE(user_id, prop_name)' + + ');' + ); +} \ No newline at end of file diff --git a/core/string_util.js b/core/string_util.js index c5ba5685..d44b43b2 100644 --- a/core/string_util.js +++ b/core/string_util.js @@ -29,7 +29,11 @@ function stylizeString(s, style) { var i; var stylized = ''; - switch(style) { + switch(style) { + // None/normal + case 'normal' : + case 'N' : return s; + // UPPERCASE case 'upper' : case 'U' : return s.toUpperCase(); @@ -38,9 +42,9 @@ function stylizeString(s, style) { case 'lower' : case 'l' : return s.toLowerCase(); - // Proper Case - case 'proper' : - case 'P' : + // Title Case + case 'title' : + case 'T' : return s.replace(/\w\S*/g, function onProperCaseChar(t) { return t.charAt(0).toUpperCase() + t.substr(1).toLowerCase(); }); diff --git a/core/user.js b/core/user.js new file mode 100644 index 00000000..6e4b42b1 --- /dev/null +++ b/core/user.js @@ -0,0 +1,93 @@ +/* jslint node: true */ +'use strict'; + +var crypto = require('crypto'); +var database = require('./database.js'); + +exports.User = User; + +var PBKDF2_OPTIONS = { + iterations : 1000, + keyLen : 128, + saltLen : 32, +}; + +function User() { + var self = this; + + this.id = 0; + this.userName = ''; + this.groups = []; + this.permissions = []; + this.properties = {}; + +/* + this.load = function(userName, cb) { + database.user.get('SELECT id FROM user WHERE user_name = $un LIMIT 1;', { un : userName }, function onUser(err, row) { + if(err) { + cb(err); + return; + } + + var user = new User(); + user.id = row.id; + + // :TODO: load the rest. + + database.user.serialize(function loadUserSerialized() { + database.user.each('SELECT prop_name, prop_value FROM user_property WHERE user_id = $uid;', { uid : user.id }, function onUserPropRow(err, propRow) { + user.properties[propRow.prop_name] = propRow.prop_value; + }); + }); + + cb(null, user); + }); + };*/ +} + +User.load = function(userName, cb) { + database.user.get('SELECT id FROM user WHERE user_name = $un LIMIT 1;', { un : userName }, function onUser(err, row) { + if(err) { + cb(err); + return; + } + + var user = new User(); + user.id = row.id; + + // :TODO: load the rest. + + database.user.serialize(function loadUserSerialized() { + database.user.each('SELECT prop_name, prop_value FROM user_property WHERE user_id = $uid;', { uid : user.id }, function onUserPropRow(err, propRow) { + user.properties[propRow.prop_name] = propRow.prop_value; + }); + }); + + cb(null, user); + }); +}; + +User.prototype.setPassword = function(password, cb) { + // :TODO: validate min len, etc. here? + + crypto.randomBytes(PBKDF2_OPTIONS.saltLen, function onRandomSalt(err, salt) { + if(err) { + cb(err); + return; + } + + password = Buffer.isBuffer(password) ? password : new Buffer(password, 'base64'); + + crypto.pbkdf2(password, salt, PBKDF2_OPTIONS.iterations, PBKDF2_OPTIONS.keyLen, function onPbkdf2Generated(err, dk) { + if(err) { + cb(err); + return; + } + + cb(null, dk); + + this.properties['pw.pbkdf2.salt'] = salt; + this.properties['pw.pbkdf2.dk'] = dk; + }); + }); +}; \ No newline at end of file diff --git a/core/view.js b/core/view.js index b3293b5e..0269519a 100644 --- a/core/view.js +++ b/core/view.js @@ -140,11 +140,10 @@ InteractiveView.prototype.setNextView = function(id) { var TEXT_EDIT_INPUT_TYPES = [ - 'none', // :TODO: TextEditView -> TextView (optional focus/editable) - 'text', - 'password', - 'upper', - 'lower', + 'normal', 'N', + 'password', 'P', + 'upper', 'U', + 'lower', 'l', ]; @@ -155,14 +154,16 @@ function TextEditView(client, options) { this.options.multiLine = false; } - this.options.inputType = miscUtil.valueWithDefault(this.options.inputType, 'text'); + this.options.inputType = miscUtil.valueWithDefault(this.options.inputType, 'normal'); assert(TEXT_EDIT_INPUT_TYPES.indexOf(this.options.inputType) > -1); - if('password' === this.options.inputType) { + if('password' === this.options.inputType || 'P' === this.options.inputType) { this.options.inputMaskChar = miscUtil.valueWithDefault(this.options.inputMaskChar, '*').substr(0,1); } this.value = miscUtil.valueWithDefault(options.defaultValue, ''); + + // :TODO: hilight, text, etc., should come from options or default for theme if not provided // focus=fg + bg @@ -211,7 +212,7 @@ TextEditView.prototype.onKeyPressed = function(k, isSpecial) { this.value += k; - if('password' === this.options.inputType) { + if('P' === this.options.inputType.charAt(0).toUpperCase()) { this.client.term.write(this.options.inputMaskChar); } else { this.client.term.write(k); @@ -272,14 +273,16 @@ function ViewsController(client) { }); this.onViewAction = function(action) { - console.log(action + '@ ' + this.id); - - self.emit('action', { id : this.id, action : action }); + console.log(action + ' @ ' + this.id); - if('accepted' === action) { - self.nextFocus(); - } else if('next' === action) { - self.nextFocus(); + if(self.submitViewId == this.id) { + self.emit('action', { view : this, action : 'submit' }); + } else { + self.emit('action', { view : this, action : action }); + + if('accepted' === action || 'next' === action) { + self.nextFocus(); + } } }; @@ -335,6 +338,10 @@ ViewsController.prototype.nextFocus = function() { } }; +ViewsController.prototype.setSubmitView = function(id) { + this.submitViewId = id; +}; + ViewsController.prototype.loadFromMCIMap = function(mciMap) { var factory = new MCIViewFactory(this.client); var view; @@ -399,7 +406,7 @@ MCIViewFactory.prototype.createFromMCI = function(mci) { } if(mci.args.length > 1) { - + options.inputType = mci.args[1]; } options.color = mci.color; diff --git a/mods/matrix.js b/mods/matrix.js index 90ce7e74..d85187e9 100644 --- a/mods/matrix.js +++ b/mods/matrix.js @@ -5,6 +5,7 @@ var ansi = require('../core/ansi_term.js'); var lineEditor = require('../core/line_editor.js'); var art = require('../core/art.js'); +var user = require('../core/user.js'); var view = require('../core/view.js'); @@ -20,23 +21,7 @@ function entryPoint(client) { var term = client.term; term.write(ansi.resetScreen()); - - //------------- - /* - client.on('position', function onPos(pos) { - console.log(pos); - }); - - term.write('Hello, world!'); - term.write(ansi.queryPos()); - term.write(ansi.goto(5,5)); - term.write('Yehawww a bunch of text incoming.... maybe that is what breaks it... hrm... who knows.\nHave to do more testing ;(\n'); - term.write(ansi.queryPos()); - return; - */ - - //------------- - + // :TODO: types, random, and others? could come from conf.mods.matrix or such //art.getArt('SO-CC1.ANS'/* 'MATRIX'*/, { types: ['.ans'], random: true}, function onArt(err, theArt) { @@ -52,6 +37,23 @@ function entryPoint(client) { vc.loadFromMCIMap(mci); vc.setViewOrder(); vc.switchFocus(1); + vc.setSubmitView(2); + + vc.on('action', function onAction(act) { + if('submit' === act.action) { + console.log('userName=' + vc.getView(1).value); + console.log('password: ' + act.view.value); + + user.User.load(vc.getView(1).value, function onUser(err, user) { + if(err) { + console.log(err); + return; + } + + console.log(user.id); + }); + } + }); }); } });