diff --git a/config/users.json b/config/users.json index 12c286c..bcc94ac 100644 --- a/config/users.json +++ b/config/users.json @@ -1,3 +1,6 @@ { - "dominic": "test" + "dominic": { + "password": "test", + "roles": [ "admin" ] + } } diff --git a/js/db_interface.js b/js/db_interface.js index 0cd199b..514f5ee 100644 --- a/js/db_interface.js +++ b/js/db_interface.js @@ -89,20 +89,20 @@ function replyHandler(action) { * * @param {String} set the set name how it is stored in the DB * @param {function} funcSingle the function that fetches single entries from the DB - * @param {function} callback the function to be called on success or error, receives + * @param {function} cb the function to be called on success or error, receives * arguments (err, obj) */ -function getSetRecords(set, funcSingle, callback) { +function getSetRecords(set, funcSingle, cb) { if(db) db.smembers(set, function(err, reply) { if(err) log.error('DB', 'fetching ' + set + ': ' + err); else { if(reply.length === 0) { - callback(null, null); + cb(null, null); } else { var semaphore = reply.length, objReplies = {}; setTimeout(function() { if(semaphore > 0) { - callback('Timeout fetching ' + set, null); + cb('Timeout fetching ' + set, null); } }, 1000); for(var i = 0; i < reply.length; i++){ @@ -111,7 +111,7 @@ function getSetRecords(set, funcSingle, callback) { if(err) log.error('DB', ' fetching single element: ' + prop); else { objReplies[prop] = reply; - if(--semaphore === 0) callback(null, objReplies); + if(--semaphore === 0) cb(null, objReplies); } }; }(reply[i])); @@ -143,22 +143,22 @@ exports.storeActionModule = function(id, data) { }; /** - * ### getActionModule(id, callback) + * ### getActionModule(id, cb) * Query the DB for an action module. * @param {String} id the module id - * @param {function} callback the callback to receive the answer (err, obj) + * @param {function} cb the cb to receive the answer (err, obj) */ -exports.getActionModule = function(id, callback) { - if(callback && db) db.get('action_module_' + id, callback); +exports.getActionModule = function(id, cb) { + if(cb && db) db.get('action_module_' + id, cb); }; /** - * ### getActionModules(callback) + * ### getActionModules(cb) * Fetch all action modules. - * @param {function} callback the callback to receive the answer (err, obj) + * @param {function} cb the cb to receive the answer (err, obj) */ -exports.getActionModules = function(callback) { - getSetRecords('action_modules', exports.getActionModule, callback); +exports.getActionModules = function(cb) { + getSetRecords('action_modules', exports.getActionModule, cb); }; /** @@ -175,14 +175,14 @@ exports.storeActionModuleAuth = function(id, data) { }; /** - * ### getActionModuleAuth(id, callback) + * ### getActionModuleAuth(id, cb) * Query the DB for an action module authentication token. * @param {String} id the module id - * @param {function} callback the callback to receive the answer (err, obj) + * @param {function} cb the cb to receive the answer (err, obj) */ -exports.getActionModuleAuth = function(id, callback) { - if(callback && db) db.get('action_module_' + id + '_auth', function(id) { - return function(err, txt) { callback(err, decrypt(txt, 'action_module_' + id + '_auth')); }; +exports.getActionModuleAuth = function(id, cb) { + if(cb && db) db.get('action_module_' + id + '_auth', function(id) { + return function(err, txt) { cb(err, decrypt(txt, 'action_module_' + id + '_auth')); }; }(id)); }; @@ -202,22 +202,22 @@ exports.storeEventModule = function(id, data) { }; /** - * ### getEventModule(id, callback) + * ### getEventModule(id, cb) * Query the DB for an event module. * @param {String} id the module id - * @param {function} callback the callback to receive the answer (err, obj) + * @param {function} cb the cb to receive the answer (err, obj) */ -exports.getEventModule = function(id, callback) { - if(callback && db) db.get('event_module_' + id, callback); +exports.getEventModule = function(id, cb) { + if(cb && db) db.get('event_module_' + id, cb); }; /** - * ### getEventModules(callback) + * ### getEventModules(cb) * Fetch all event modules. - * @param {function} callback the callback that receives the arguments (err, obj) + * @param {function} cb the cb that receives the arguments (err, obj) */ -exports.getEventModules = function(callback) { - getSetRecords('event_modules', exports.getEventModule, callback); +exports.getEventModules = function(cb) { + getSetRecords('event_modules', exports.getEventModule, cb); }; /** @@ -233,14 +233,14 @@ exports.storeEventModuleAuth = function(id, data) { } }; -// @method getEventModuleAuth(id, callback) +// @method getEventModuleAuth(id, cb) // Query the DB for an event module authentication token. // @param {String} id the module id -// @param {function} callback the callback to receive the answer (err, obj) -exports.getEventModuleAuth = function(id, callback) { - if(callback) db.get('event_module_' + id +'_auth', function(id) { - return function(err, txt) { callback(err, decrypt(txt, 'event_module_' + id + '_auth')); }; +// @param {function} cb the cb to receive the answer (err, obj) +exports.getEventModuleAuth = function(id, cb) { + if(cb) db.get('event_module_' + id +'_auth', function(id) { + return function(err, txt) { cb(err, decrypt(txt, 'event_module_' + id + '_auth')); }; }(id)); }; @@ -258,31 +258,40 @@ exports.storeRule = function(id, data) { } }; -// @method getRule(id, callback) +// @method getRule(id, cb) // Query the DB for a rule. // @param {String} id the rule id -// @param {function} callback the callback to receive the answer (err, obj) -exports.getRule = function(id, callback) { - if(db) db.get('rule_' + id, callback); +// @param {function} cb the cb to receive the answer (err, obj) +exports.getRule = function(id, cb) { + if(db) db.get('rule_' + id, cb); }; -// @method getRules(callback) +// @method getRules(cb) // Fetch all rules from the database. -// @param {function} callback the callback to receive the answer (err, obj) -exports.getRules = function(callback) { - getSetRecords('rules', exports.getRule, callback); +// @param {function} cb +exports.getRules = function(cb) { + getSetRecords('rules', exports.getRule, cb); }; /** * - * @param {function} cb * @param {Object} objUser + * @param {function} cb */ -exports.storeUser = function(cb, objUser) { +exports.storeUser = function(objUser, cb) { if(db && objUser && objUser.id) { db.sadd('users', objUser.id, replyHandler('storing user key ' + objUser.id)); db.set('user:' + objUser.id, data, replyHandler('storing user properties ' + objUser.id)); } }; + +/** + * + * @param {Object} objUser + * @param {function} cb + */ +exports.loginUser = function(objUser, cb) { + if(db) db.get('user:' + id, cb); +}; diff --git a/js/engine.js b/js/engine.js index 20036a3..68664e0 100644 --- a/js/engine.js +++ b/js/engine.js @@ -38,21 +38,23 @@ exports.addDBLinkAndLoadActionsAndRules = function(db_link) { log.print('EN', 'No Action Modules found in DB!'); loadRulesFromDB(); } else { - var m, semaphore = 0; + var m; for(var el in obj) { - semaphore++; log.print('EN', 'Loading Action Module from DB: ' + el); - m = ml.requireFromString(obj[el], el); - db.getActionModuleAuth(el, function(mod) { - return function(err, obj) { - if(--semaphore == 0) { - loadRulesFromDB(); - } - if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj)); - }; - }(m)); - listActionModules[el] = m; + try{ + m = ml.requireFromString(obj[el], el); + db.getActionModuleAuth(el, function(mod) { + return function(err, obj) { + if(obj && mod.loadCredentials) mod.loadCredentials(JSON.parse(obj)); + }; + }(m)); + listActionModules[el] = m; + } catch(e) { + e.addInfo = 'error in action module "' + el + '"'; + log.error('EN', e); + } } + loadRulesFromDB(); } } }); diff --git a/js/eventpoller.js b/js/eventpoller.js index c9a5e88..7198bc1 100644 --- a/js/eventpoller.js +++ b/js/eventpoller.js @@ -36,15 +36,20 @@ function loadEventModule(el, cb) { } else { // log.print('EP', 'Loading Event Module: ' + el); - var m = ml.requireFromString(obj, el); - db.getEventModuleAuth(el, function(mod) { - return function(err, objA) { - //TODO authentication needs to be done differently - if(objA && mod.loadCredentials) mod.loadCredentials(JSON.parse(objA)); - }; - }(m)); - listEventModules[el] = m; - if(typeof cb === 'function') cb(null, m); + try { + var m = ml.requireFromString(obj, el); + db.getEventModuleAuth(el, function(mod) { + return function(err, objA) { + //TODO authentication needs to be done differently + if(objA && mod.loadCredentials) mod.loadCredentials(JSON.parse(objA)); + }; + }(m)); + listEventModules[el] = m; + if(typeof cb === 'function') cb(null, m); + } catch(e) { + if(typeof cb === 'function') cb(e); + else log.error(e); + } } }); } diff --git a/js/http_listener.js b/js/http_listener.js index bb93e43..5ec9603 100644 --- a/js/http_listener.js +++ b/js/http_listener.js @@ -63,15 +63,23 @@ exports.addHandlers = function(funcAdminHandler, funcEvtHandler) { // } } // Redirect the requests to the appropriate handler. - app.use('/doc/', express.static(path.resolve(__dirname, '..', 'webpages', 'doc'))); + app.use('/', express.static(path.resolve(__dirname, '..', 'webpages'))); + // app.use('/doc/', express.static(path.resolve(__dirname, '..', 'webpages', 'doc'))); // app.get('/mobile', userHandler.handleRequest); app.get('/rulesforge', userHandler.handleRequest); - app.use('/mobile/', express.static(path.resolve(__dirname, '..', 'webpages', 'mobile'))); + // app.use('/mobile', express.static(path.resolve(__dirname, '..', 'webpages', 'mobile'))); // } app.use('/rulesforge/', express.static(path.resolve(__dirname, '..', 'webpages', 'rulesforge'))); app.get('/admin', userHandler.handleRequest); + app.post('/login', userHandler.handleLogin); app.post('/push_event', onPushEvent); - if(http_port) server = app.listen(http_port); // inbound event channel - else log.error('HL', new Error('No HTTP port found!? Nothing to listen on!...')); + try { + if(http_port) server = app.listen(http_port); // inbound event channel + else log.error('HL', new Error('No HTTP port found!? Nothing to listen on!...')); + } catch(e) { + e.addInfo = 'port unavailable'; + log.error(e); + funcAdminHandler({cmd: 'shutdown'}); + } }; /** @@ -86,12 +94,10 @@ function onPushEvent(req, resp) { if(obj && obj.event && obj.eventid){ resp.writeHead(200, { "Content-Type": "text/plain" }); resp.write('Thank you for the event (' + obj.event + '[' + obj.eventid + '])!'); - resp.end(); eventHandler(obj); } else { resp.writeHead(400, { "Content-Type": "text/plain" }); resp.write('Your event was missing important parameters!'); - resp.end(); } resp.end(); }); diff --git a/js/logging.js b/js/logging.js index f0aa56e..d4ee683 100644 --- a/js/logging.js +++ b/js/logging.js @@ -11,12 +11,14 @@ */ var fs = require('fs'), logTypes = [ flushToConsole, flushToFile, null], - logType = 0, logFile = './server.log'; + logFile = require('path').resolve(__dirname, '..', 'server.log'), + logType = 0; exports = module.exports = function(args) { args = args || {}; if(args.logType) logType = parseInt(args.logType) || 0; if(logType == 1) fs.truncateSync(logFile, 0); + if(logType > logTypes.length - 1) logType = 0; return module.exports; }; diff --git a/js/module_loader.js b/js/module_loader.js index e336fc7..d2cb0d8 100644 --- a/js/module_loader.js +++ b/js/module_loader.js @@ -12,8 +12,16 @@ exports = module.exports = function(args) { exports.requireFromString = function(src, name, dir) { if(!dir) dir = __dirname; - //FIXME load modules only into a safe environment with given modules, no access to whole application - var id = path.resolve(dir, name, name + '.js'); + var id = path.resolve(dir, name, name + '.vm'); + //FIXME load modules only into a safe environment with given modules, no access to whole application, + var vm = require('vm'), + sandbox = { + log: log, + needle: require('needle') + }; + + var mod = vm.runInNewContext(src, sandbox, 'myfile.vm'); + console.log(mod); var m = new module.constructor(id, module); m.paths = module.paths; try { diff --git a/js/server.js b/js/server.js index 9eb698a..9ae26ab 100644 --- a/js/server.js +++ b/js/server.js @@ -1,58 +1,82 @@ -/** @module rules_server */ -'use strict'; /* -A First Level Header -==================== + * Rules Server + * ============ + * This is the main module that is used to run the whole server: + * + * node server [log_type http_port] + * + * Valid `log_type`'s are: + * + * - `0`: standard I/O output (default) + * - `1`: log file (server.log) + * - `2`: silent + * + * `http_port` can be set to use another port, than defined in the + * [config](config.html) file, to listen to, e.g. used by the test suite. + */ +'use strict'; -A Second Level Header ---------------------- - -Now is the time for all good men to come to -the aid of their country. This is just a -regular paragraph. - -The quick brown fox jumped over the lazy -dog's back. - -### Header 3 - -> This is a blockquote. -> -> This is the second paragraph in the blockquote. -> -> ## This is an H2 in a blockquote -*/ - -//FIXME server should be started via command line arguments http_port and logging level to allow proper testing var path = require('path'), log = require('./logging'), - procCmds = { - 'die': function() { shutDown(); } - }, semaphore = 0, args = {}, - http_listener, mm, db, engine, objCmds; + procCmds = {}, + adminCmds, http_listener, mm, db, engine; +/* + * Error handling of the express port listener requires special attention, + * thus we have to catch the process error, which is issued if + * the port is already in use. + */ +process.on('uncaughtException', function(err) { + switch(err.errno) { + case 'EADDRINUSE': + err.addInfo = 'http_port already in use, shutting down!'; + log.error('RS', err); + shutDown(); + break; + default: log.error(err); + } +}); + +/** + * ### Initialize the Server + * This function is invoked right after the module is loaded and starts the server. + */ function init() { log.print('RS', 'STARTING SERVER'); + // Check whether the config file is ready, which is required to start the server. if(!require('./config').isReady()) { log.error('RS', 'Config file not ready!'); return; - } - if(process.argv.length > 2) { - args.logType = parseInt(process.argv[2]) || 0 ; - log(args); - } else log.print('RS', 'No log method passed, using stdI/O'); + } + // Fetch the `log_type` argument and post a log about which log type is used. + if(process.argv.length > 2) { + args.logType = parseInt(process.argv[2]) || 0; + if(args.logType === 0) log.print('RS', 'Log type set to standard I/O output'); + else if(args.logType === 1) log.print('RS', 'Log type set to file output'); + else if(args.logType === 2) log.print('RS', 'Log type set to silent'); + else log.print('RS', 'Unknown log type, using standard I/O'); + log(args); + } else log.print('RS', 'No log method passed, using standard I/O'); + + // Fetch the `http_port` argument if(process.argv.length > 3) args.http_port = parseInt(process.argv[3]); else log.print('RS', 'No HTTP port passed, using standard port from config file'); + // Initialize all required modules with the args object. + log.print('RS', 'Initialzing engine'); engine = require('./engine')(args); + log.print('RS', 'Initialzing http listener'); http_listener = require('./http_listener')(args); + log.print('RS', 'Initialzing module manager'); mm = require('./module_manager')(args); log.print('RS', 'Initialzing DB'); db = require('./db_interface')(args); - objCmds = { + + // Load the admin commands that are issued via HTTP requests. + adminCmds = { 'loadrules': mm.loadRulesFromFS, 'loadaction': mm.loadActionModuleFromFS, 'loadactions': mm.loadActionModulesFromFS, @@ -61,20 +85,22 @@ function init() { 'loadusers': http_listener.loadUsers, 'shutdown': shutDown }; - log.print('RS', 'Initialzing engine'); + + // Distribute handlers between modules to link the application. + log.print('RS', 'Passing handlers to engine'); engine.addDBLinkAndLoadActionsAndRules(db); - log.print('RS', 'Initialzing http listener'); + log.print('RS', 'Passing handlers to http listener'); http_listener.addHandlers(db, handleAdminCommands, engine.pushEvent); - log.print('RS', 'Initialzing module manager'); + log.print('RS', 'Passing handlers to module manager'); mm.addHandlers(db, engine.loadActionModule, engine.addRule); - //FIXME load actions and events, then rules, do this here, visible for everybody on the first glance - //TODO for such events we should forge the architecture more into an event driven one } - +/** + * + */ function handleAdminCommands(args, answHandler) { if(args && args.cmd) { - var func = objCmds[args.cmd]; + var func = adminCmds[args.cmd]; if(func) func(args, answHandler); } else log.print('RS', 'No command in request'); setTimeout(function(ah) { @@ -85,40 +111,32 @@ function handleAdminCommands(args, answHandler) { }, 2000); } - +/** + * Shuts down the server. + * @param {Object} args + * @param {Object} answHandler + */ function shutDown(args, answHandler) { if(answHandler) answHandler.answerSuccess('Goodbye!'); log.print('RS', 'Received shut down command!'); - if(engine && typeof engine.shutDown === 'function') engine.shutDown(); - else { - console.error(typeof engine.shutDown); - console.error('no function!'); - } + if(engine) engine.shutDown(); if(http_listener) http_listener.shutDown(); } -// Send message +/* + * ### Process Commands + * + * When the server is run as a child process, this function handles messages + * from the parent process (e.g. the testing suite) + */ process.on('message', function(cmd) { if(typeof procCmds[cmd] === 'function') procCmds[cmd](); else console.error('err with command'); }); -//FIXME initialization of all modules should depend on one after the other -// in a transaction style manner - -/* - * FIXME - * - new consequent error and callback handling starts to manifest in the code, - * still a lot of work required! - * - unit testing seems a bit more complex because of the dependencies, this - * has to be started before solving above point because it will give hints to - * better loose coupling - */ - -/* - * FIXME ALL MODULES NEED TO FOLLOW CONVENTION TO ALLOW PROPER MODULE HANDLING: - * - init(args, cb) - * - die() +/** + * The die command redirects to the shutDown function. */ +procCmds.die = shutDown; init(); diff --git a/js/user_handler.js b/js/user_handler.js index 183fe3d..f46f2e3 100644 --- a/js/user_handler.js +++ b/js/user_handler.js @@ -1,6 +1,8 @@ - -var log = require('./logging'), - db = require('./db_interface'), adminHandler; +var path = require('path'), + qs = require('querystring'), + log = require('./logging'), + db = require('./db_interface'), + adminHandler; exports = module.exports = function(args) { args = args || {}; @@ -17,8 +19,9 @@ exports.handleRequest = function(req, resp) { req.on('end', function () { resp.end(); }); - if(req.session && req.session.lastPage) resp.send('You visited last: ' + req.session.lastPage); - else resp.send('You are new!'); + if(req.session && req.session.user) { + resp.send('You\'re logged in'); + } else resp.sendfile(path.resolve(__dirname, '..', 'webpages', 'handlers', 'login.html')); // resp.end(); log.print('UH', 'last: '+ req.session.lastPage); req.session.lastPage = req.originalUrl; @@ -27,6 +30,24 @@ exports.handleRequest = function(req, resp) { // console.log(req); }; +exports.handleLogin = function(req, resp) { + var body = ''; + req.on('data', function (data) { body += data; }); + req.on('end', function () { + if(!req.session || !req.session.user) { + var obj = qs.parse(body); + req.session.user = db.loginUser(obj.username, obj.password); + } + if(req.session.user) { + resp.write('Welcome ' + req.session.user.name + '!'); + } else { + resp.writeHead(401, { "Content-Type": "text/plain" }); + resp.write('Login failed!'); + } + resp.end(); + }); +}; + function answerHandler(r) { var response = r, hasBeenAnswered = false; function postAnswer(msg) { diff --git a/mod_actions/probinder/probinder.js b/mod_actions/probinder/probinder.js index 9bcc825..bd4f06e 100644 --- a/mod_actions/probinder/probinder.js +++ b/mod_actions/probinder/probinder.js @@ -7,7 +7,9 @@ var needle = require('needle'); var urlService = 'https://probinder.com/service/', credentials = null; - +log.print('PB', module); +log.print('PB', exports); +log.print('PB', module.exports); function loadCredentials(cred) { if(!cred || !cred.username || !cred.password) { console.error('ERROR: ProBinder AM credentials file corrupt'); diff --git a/mod_events/probinder/probinder.js b/mod_events/probinder/probinder.js index 1e0c3d7..894aeea 100644 --- a/mod_events/probinder/probinder.js +++ b/mod_events/probinder/probinder.js @@ -1,6 +1,6 @@ 'use strict'; -var needle = require('needle'); +// var needle = require('needle'); /* * ProBinder EVENT MODULE diff --git a/webpages/handlers/login.html b/webpages/handlers/login.html new file mode 100644 index 0000000..503c5c1 --- /dev/null +++ b/webpages/handlers/login.html @@ -0,0 +1,26 @@ + + + Login + + + + +

Login

+ + + +
username:
password:
+ + + + \ No newline at end of file diff --git a/webpages/mobile/index.html b/webpages/mobile/index.html index df649b8..05e9116 100644 --- a/webpages/mobile/index.html +++ b/webpages/mobile/index.html @@ -1,14 +1,8 @@ Mobile Page - - + +

Mobile Page

@@ -24,7 +18,7 @@ +lat+","+lon+"&zoom=15&size=400x300&sensor=false&maptype=roadmap&markers=color:orange|label:1|"+lat+","+lon; document.getElementById("mapholder").innerHTML=""; - $.post('push_event', { event: 'geoposition', eventid: 'geoposition_' + position.timestamp }) + $.post('../push_event', { event: 'geoposition', eventid: 'geoposition_' + position.timestamp }) .done(function(data) { $('#info').text("Sent event to engine: " + data); }) diff --git a/webpages/mobile/style.css b/webpages/mobile/style.css new file mode 100644 index 0000000..7bc36cc --- /dev/null +++ b/webpages/mobile/style.css @@ -0,0 +1,4 @@ + +body { + font-family: sans-serif, "Times New Roman", Georgia, Serif; +} \ No newline at end of file diff --git a/webpages/style.css b/webpages/style.css new file mode 100644 index 0000000..7bc36cc --- /dev/null +++ b/webpages/style.css @@ -0,0 +1,4 @@ + +body { + font-family: sans-serif, "Times New Roman", Georgia, Serif; +} \ No newline at end of file