login feature starts to manifest

This commit is contained in:
Dominic Bosch 2013-11-18 22:53:09 +01:00
parent 89fc5d26a1
commit d385ef6163
15 changed files with 256 additions and 152 deletions

View file

@ -1,3 +1,6 @@
{
"dominic": "test"
"dominic": {
"password": "test",
"roles": [ "admin" ]
}
}

View file

@ -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);
};

View file

@ -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();
}
}
});

View file

@ -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);
}
}
});
}

View file

@ -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();
});

View file

@ -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;
};

View file

@ -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 {

View file

@ -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();

View file

@ -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) {

View file

@ -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');

View file

@ -1,6 +1,6 @@
'use strict';
var needle = require('needle');
// var needle = require('needle');
/*
* ProBinder EVENT MODULE

View file

@ -0,0 +1,26 @@
<html>
<head>
<title>Login</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src='//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' type='text/javascript'></script>
</head>
<body>
<h1>Login</h1>
<table>
<tr><td>username: </td><td><input type="text" id="username" /></td></tr>
<tr><td>password: </td><td><input type="password" id="password" /></td></tr>
</table>
<button id="but_submit">login</button>
<script>
$('#but_submit').click(function() {
$.post('../login', { username: $('#username').val(), password: $('#password').val() })
.done(function(data) {
window.location.href = document.URL;
})
.fail(function(err) {
alert('Authentication not successful!');
});
});
</script>
</body>
</html>

View file

@ -1,14 +1,8 @@
<html>
<head>
<title>Mobile Page</title>
<script type="text/javascript">
document.write("\<script src='//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' type='text/javascript'>\<\/script>");
</script>
<style>
body {
font-family: sans-serif, "Times New Roman", Georgia, Serif;
}
</style>
<script src='//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' type='text/javascript'></script>
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h1>Mobile Page</h1>
@ -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="<img src='"+img_url+"'>";
$.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);
})

View file

@ -0,0 +1,4 @@
body {
font-family: sans-serif, "Times New Roman", Georgia, Serif;
}

4
webpages/style.css Normal file
View file

@ -0,0 +1,4 @@
body {
font-family: sans-serif, "Times New Roman", Georgia, Serif;
}