diff options
-rw-r--r-- | notes.txt | 10 | ||||
-rw-r--r-- | protoype/app.js | 39 | ||||
-rw-r--r-- | protoype/auth.js | 76 | ||||
-rw-r--r-- | protoype/router/driver.js | 3 | ||||
-rw-r--r-- | protoype/router/user.js | 111 | ||||
-rw-r--r-- | protoype/views/login.jade | 14 |
6 files changed, 208 insertions, 45 deletions
@@ -1,7 +1,13 @@ [express] $ express -do '# npm install -g express' beforehand, express command -generates nice template dir structure to get started +- do '# npm install -g express' beforehand, express command + generates nice template dir structure to get started +- last param in functions usually implies a callback +- app.param + map/assosiate some logic to a 'route parameter(s)', so when you enter such route + you can expect to have a variable filled for you, e.g. route: /sys/:id, let's say + id is 'listdb', then a db would get preloaded by the time we stepped into the route logic +- doing next(new Error('foo')) will stall and end middleware chain [code analysis] # npm install -g jslint diff --git a/protoype/app.js b/protoype/app.js index 0a698c2..82f2ec2 100644 --- a/protoype/app.js +++ b/protoype/app.js @@ -8,6 +8,7 @@ * - redis for active users * - load from db once, and refetch when necessary * - if (verbose) log; can choose to use process.env and/or app.settings.env + * - jsdoc? * */ @@ -19,7 +20,6 @@ var express = require('express'); var RedisStore = require('connect-redis')(express); var db = require('./mydb.js'); -var driver = require('./router/driver.js'); var myplatform = require('./router/myplatform.js'); var user = require('./router/user.js'); var index = require('./router/index.js'); @@ -27,15 +27,30 @@ var index = require('./router/index.js'); var app = express(); function deadend(req, res, next) { - util.log('[deadend] couldn\'t serve'); + util.log('[deadend] couldn\'t serve, requested path: ' + req.url); /* collect possible info here */ /* if (critical_wrong) then; throw new Error('da fuck this entity is doing!'); */ - res.send(404, 'page not found'); + res.send(404, 'page not found\n'); } function error_handler(err, req, res, next) { /* error handling, arity of 4 */ console.error(err.stack); - res.send(500, 'something broke!'); + res.send(500, 'something broke!\n'); +} + +/* delete req.session.user on close connection? */ +function restrict(req, res, next) { + if (req.session.user) + { + util.log('[restrict] granted ' + req.session.user); + next(); + } + else + { + util.log('[restrict] blocked access'); + res.send(401, 'access restricted\n'); + /* res.redirect(/login); */ + } } app.configure(function() { @@ -47,22 +62,22 @@ app.configure(function() { app.use(express.favicon()); app.use(express.compress()); /* gzip */ app.use(express.bodyParser()); /* creates req.body which req.param() uses */ - app.use(express.cookieParser()); /* req.session can be populated with user defined vars */ - app.use(express.session({ secret: "keyboard cat", store: new RedisStore() })); + app.use(express.cookieParser()); /* req.session.* can be populated with user defined vars */ + app.use(express.session({ secret: "keyboard cat", store: new RedisStore() })); /* populates req.session */ app.use(app.router); /* when there's no match, we go static file handling below */ app.use(require('stylus').middleware(__dirname + '/public')); app.use(express.static(path.join(__dirname, 'public'))); /* GET /stylesheets/style.css */ app.use(deadend); /* we get here if we couldn't serve */ + app.use(error_handler); /* is this correct? */ }); +app.get('/', index.root); +app.get('/create', user.create_get); app.post('/create', user.create_post); +app.get('/login', user.login_get); app.post('/login', user.login_post); -app.get('/sys/:id([a-z]+)', myplatform.system); - -/* routing to handlers that can drive the server's functionality */ -app.get('/create', driver.create_get); - -app.get('/', index.root); +//app.all('*', auth.check); /* not applicable, I want router list to hit the end in case auth fails */ +app.get('/sys/:id([a-z]+)', restrict, myplatform.system); app.listen(8081, function() { util.log(util.format('[server] listening on port %d in %s mode', this.address().port, app.settings.env)); diff --git a/protoype/auth.js b/protoype/auth.js new file mode 100644 index 0000000..57b0abb --- /dev/null +++ b/protoype/auth.js @@ -0,0 +1,76 @@ +/* + * auth.js + * + * authentication + * + */ + +/* helpful links: + * http://stackoverflow.com/questions/7990890/how-to-implement-login-auth-in-node-js/8003291#8003291 + * + */ + +var util = require('util'); +var crypto = require('crypto'); + +var db = require('./mydb.js'); + +var len = 128; +var iterations = 12000; + +/** + * Hashes a password with optional `salt`, otherwise + * generate a salt for `pass` and invoke `fn(err, salt, hash)`. + * + * @param {String} password to hash + * @param {String} optional salt + * @param {Function} callback + * @api public + */ + +function hash(pwd, salt, fn) { + if (3 == arguments.length) { + crypto.pbkdf2(pwd, salt, iterations, len, fn); + } else { + fn = salt; + crypto.randomBytes(len, function(err, salt) { + if (err) return fn(err); + salt = salt.toString('base64'); + crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash) { + if (err) return fn(err); + fn(null, salt, hash); + }); + }); + } +}; + +/* pull out a user record from db along with http status code */ +function auth_user(tag, id, fn) { + db.users.find({tag: tag}, function(err, user_record) { + if (err) + { + fn(err, null, -1); + } + else if (!user_record || user_record.length == 0) + { + fn(null, null, 403); + } + else + { + /* util.log('[auth] retrived user: ' + util.inspect(user_record)); */ + hash(id, user_record[0].salt, function(err, hash) { + if (err) + return fn(err, null, -1); + + if (hash == user_record[0].hash) + return fn(null, user_record[0], 200); /* granted */ + + fn(null, null, 401); + }); + } + }); +} + +exports.hash = hash; +exports.auth_user = auth_user; + diff --git a/protoype/router/driver.js b/protoype/router/driver.js index 5e79856..99c101a 100644 --- a/protoype/router/driver.js +++ b/protoype/router/driver.js @@ -2,7 +2,7 @@ * */ -function create_get(req, res) { +function create_get(req, res, next) { res.send('<!DOCTYPE html>\n' + '<html>\n' + '<body>\n' + @@ -15,6 +15,7 @@ function create_get(req, res) { ' Desc: <input type="text" name="desc" /><br />\n' + ' Signature: <input type="text" name="sig" /><br />\n' + ' Location: <input type="text" name="loc" /><br />\n' + + ' Secret: <input type="text" name="secret" /><br />\n' + ' <input type="submit" value="add" />\n' + '</form>\n' + '</body>\n' + diff --git a/protoype/router/user.js b/protoype/router/user.js index 4215054..5df1469 100644 --- a/protoype/router/user.js +++ b/protoype/router/user.js @@ -6,28 +6,70 @@ */ var util = require('util'); +var crypto = require('crypto') + var db = require('../mydb.js'); +var driver = require('./driver.js'); +var auth = require('../auth.js'); var connected_clients = {}; function create_get(req, res, next) { + driver.create_get(req, res, next); } +/* how much input sanitazion do we want? */ function create_post(req, res, next) { - db.users.save({tag: req.param('tag'), id: req.param('id'), status: "offline", - vehicle: {make: req.param('make'), model: req.param('model'), year: req.param('year'), - desc: req.param('desc')}, userinfo: {sig: req.param('sig')}, location: {loc: req.param('loc')}, - stats: {matches: 0, won: 0, lost: 0}}, - function(err, thing) { - if (err || !thing) - util.log('[create] error saving'); - else - util.log('[create] successfully saved'); + /* would need to be in the if statement */ + //crypto.createHash('md5').update(req.param('secret') + req.param('id') + '0xdeadbeef').digest('hex') + var secret_hash = '0xdeadbeef'; /* grant creation */ + if (req.param('secret') == undefined || req.param('secret').length < 1 || !(req.param('secret') === secret_hash)) { + util.log('[create] unauthorized creation attempt'); + return res.send(403, 'creation aborted, invalid secret\n'); /* return could be after this statement for clarity */ + } else if (req.param('tag') == undefined || req.param('tag').length < 2 || req.param('tag').length > 16 || /* tag */ + req.param('id') == undefined || req.param('id').length < 8 || req.param('id').length > 30) /* pass */ { + util.log('[create] client input sanitazation failed'); + return res.send(400, 'invalid registration attributes\n'); + } + + var user_record = { + tag: req.param('tag'), /* really an id */ + //id: req.param('id'), /* this is evil */ + salt: '', + hash: '', + status: "offline", + vehicle: {make: req.param('make'), model: req.param('model'), year: req.param('year'), desc: req.param('desc')}, + userinfo: {sig: req.param('sig')}, + location: {loc: req.param('loc')}, + stats: {matches: 0, won: 0, lost: 0} + }; + + /* not sure how this fits the async routine */ + /* yep, had to move code into the callback */ + auth.hash(req.param('id'), function(err, salt, hash) { + if (err) { + next(new Error('failed to compute hash')); + } else { + user_record.salt = salt; + user_record.hash = hash; + + db.users.save(user_record, function(err, thing) { + if (err || !thing) + util.log('[create] error saving'); + else + util.log('[create] successfully saved'); + }); + res.redirect('/'); + } }); - res.redirect('/'); } function login_get(req, res, next) { + /* todo: jade page for this? */ + /* res.send('just use curl to login'); */ + res.render('login', { + title: 'Challenger 2.0', /* might use app.locals */ + }); } /* @@ -35,27 +77,36 @@ function login_get(req, res, next) { * */ function login_post(req, res, next) { - db.users.find({tag: req.param('tag')}, function(err, thing) { - if (err || !thing || thing.length == 0) { - util.log('[login] user does not exist'); - res.send('user does not exist\n', 403); - } - else { - /* util.log('[login] retrived user: ' + util.inspect(thing)); */ - if (req.param('id') === thing[0].id) { /* insert md5 hashing here */ - util.log('[login] ' + thing[0].tag + ' authenticated'); - db.users.update({tag: req.param('tag')}, {$set: {status: 'online'}}, function(err, updated) { - if (err || !updated) - util.log('[login] failed to set status to online'); - }); + auth.auth_user(req.param('tag'), req.param('id'), function(err, user, code) { + if (err) + return next(err); + switch (code) { + case 200: + { + /* util.log('[login] retrived user: ' + util.inspect(user)); */ + util.log('[login] ' + user.tag + ' authenticated'); + db.users.update({tag: user.tag}, {$set: {status: 'online'}}, function(err, updated) { + if (err || !updated) + { + util.log('[login] failed to set status to online'); + return next(new Error('failed to set status to online')); + } /* real deal? */ - connected_clients[thing[0].tag] = {ip: res.connection.myip, port: res.connection.myport}; - res.send('successfully logged in\n', 200); - } - else { - util.log('[login] could not authenticate'); - res.send('could not authenticate\n', 401); - } + connected_clients[user.tag] = {ip: res.connection.myip, port: res.connection.myport}; + /* req.session.regenerate(function() { */ + req.session.user = user.tag; /* keep track of auth'ed user */ + res.send(200, 'successfully logged in\n'); + }); + break; + } + case 401: + util.log('[login] could not authenticate'); + res.send(401, 'could not authenticate\n'); + break; + case 403: + util.log('[login] user does not exist'); + res.send(403, 'user does not exist\n'); + break; } }); } diff --git a/protoype/views/login.jade b/protoype/views/login.jade new file mode 100644 index 0000000..834b71c --- /dev/null +++ b/protoype/views/login.jade @@ -0,0 +1,14 @@ +extends layout + +block content + h2(style='border-bottom: dashed #FF9900; letter-spacing: -2px') Login + form(name="input", action="/login", method="post") + | Username: + input(type="text", name="tag") + | <br /> + + | Password: + input(type="text", name="id") + | <br /> + input(type="submit", value="Submit") + |