diff --git a/app/config.js b/app/config.js index 88882cb..ce9ed39 100644 --- a/app/config.js +++ b/app/config.js @@ -3,33 +3,60 @@ var , express = require('express') , route = require('./routes') , events = require('events') + , crypto = require('crypto') , util = require('util') , path = require('path') ; module.exports = function(app) { - - var mids = middleware(app); - + events.EventEmitter.call(app); util.inherits(events.EventEmitter, app); app.set('view engine', 'ejs'); app.set('views', path.resolve(__dirname, '..', 'views')); - app.use(express.static(path.resolve(__dirname, '..', 'public'))); + app.use(express.static(path.resolve(__dirname, '..', 'public'))); app.use(express.methodOverride()); + app.use(express.bodyParser()); app.use(express.favicon()); + app.use(express.cookieParser(crypto.randomBytes(7).toString('base64'))); + app.use(express.cookieSession()); + + app.use(function(req, res, next) { + var os = require('os') + if (os.platform() !== 'linux') { + var err = new Error('Wifi Setup is not implemented on this platform.') + return next(err) + } + app.emit('platformOK', true); + next() + }) + /** * We can log everything here */ app.all('*', function(req, res, next) { - // console.log(req.route); // ^ too verbose, we'll log in each route. next(); + }); - return route(app, mids); -}; \ No newline at end of file + + + + route(app, middleware(this, app)); + app.use(function(err, req, res, next) { + if (!err) return next(); + console.error(err, err.stack) + res.status(500); + res.render('error', { + title: 'Sorry', + message: err.message + }) + }) + + return app +}; diff --git a/app/middleware.js b/app/middleware.js index 20521b4..1515515 100644 --- a/app/middleware.js +++ b/app/middleware.js @@ -6,7 +6,7 @@ var , state ; -module.exports = function(app) { +module.exports = function(wifi, app) { /** * When device comes up, try to bring the @@ -124,6 +124,38 @@ module.exports = function(app) { } next(); } + , hasSerial : function(req, res, next) { + + if(!wifi.serial) { + + return res.redirect('/serial'); + } + next(); + } + , userAuth : function(req, res, next) { + + if((req.session) && req.session.block) { // session already checked + + if(req.session.block == wifi.serial) { + + wifi.log.debug("User has proper credentials in session"); + return next(); + } + wifi.log.info("User has invalid credential in session"); + } + if((req.query) && req.query.block) { // check new session + + if(req.query.block == wifi.serial) { + + req.session.block = req.query.block; + wifi.log.info("User provided proper credentials"); + return next(); + } + wifi.log.info("User provided invalid credentials"); + } + wifi.log.debug("Redirecting unauthed user to auth screen"); + res.redirect('/auth'); + } }; /** @@ -131,14 +163,16 @@ module.exports = function(app) { */ mids.ready = [ - mids.hasDevice + mids.hasSerial + , mids.hasDevice , mids.hasIface , mids.notCycling ]; mids.online = [ - mids.hasDevice + mids.hasSerial + , mids.hasDevice , mids.hasIface , mids.isConnected ]; diff --git a/app/routes/get-auth.js b/app/routes/get-auth.js new file mode 100644 index 0000000..6bb7270 --- /dev/null +++ b/app/routes/get-auth.js @@ -0,0 +1,9 @@ +module.exports = function(app) { + + app.get('/auth', function(req, res, next) { + + app.log.info("Rendering auth screen."); + res.render('auth'); + + }); +}; diff --git a/app/routes/get-connected.js b/app/routes/get-connected.js index 96ee845..876f962 100644 --- a/app/routes/get-connected.js +++ b/app/routes/get-connected.js @@ -10,7 +10,7 @@ app.get('/connected', function(req, res, next) { - console.log("Rendering connected screen."); + app.log.info("Rendering connected screen."); res.render('connected'); setTimeout(reset, 5000); }); diff --git a/app/routes/get-index.js b/app/routes/get-index.js index 8423d32..8492208 100644 --- a/app/routes/get-index.js +++ b/app/routes/get-index.js @@ -4,7 +4,7 @@ app.get('/', mids.ready, function(req, res, next) { - console.log("Rendering index screen."); + app.log.info("Rendering index screen."); res.render("index"); }); diff --git a/app/routes/get-plugin.js b/app/routes/get-plugin.js index a954eb8..ae21619 100644 --- a/app/routes/get-plugin.js +++ b/app/routes/get-plugin.js @@ -4,7 +4,7 @@ app.get('/plugin', function(req, res, next) { - console.log("Rendering plugin screen."); + app.log.info("Rendering plugin screen."); res.render("plugin"); }); }; diff --git a/app/routes/get-serial.js b/app/routes/get-serial.js new file mode 100644 index 0000000..fed62d3 --- /dev/null +++ b/app/routes/get-serial.js @@ -0,0 +1,9 @@ +module.exports = function(app) { + + app.get('/serial', function(req, res, next) { + + app.log.info("Rendering serial screen."); + res.render('serial'); + + }); +}; diff --git a/app/routes/index.js b/app/routes/index.js index 49f8c0a..86cd65d 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -1,27 +1,24 @@ -;(function() { - - var - routes = { - - connected : require('./get-connected') - , networks : require('./xhr-networks') - , connect : require('./post-connect') - , device : require('./xhr-device') - , status : require('./get-status') - , plugin : require('./get-plugin') - , index : require('./get-index') - } - , router = function(app, mids) { +var + routes = { - Object.keys(routes).forEach(function(route) { - - module.exports[route] = routes[route](app, mids); - }); + connected : require('./get-connected') + , networks : require('./xhr-networks') + , connect : require('./post-connect') + , device : require('./xhr-device') + , status : require('./get-status') + , plugin : require('./get-plugin') + , serial : require('./get-serial') + , index : require('./get-index') + } + , router = function(app, mids) { - return app; - } - ; + Object.keys(routes).forEach(function(route) { + + module.exports[route] = routes[route](app, mids); + }); - module.exports = router; + return app; + } +; -})(); +module.exports = router; diff --git a/app/routes/post-connect.js b/app/routes/post-connect.js index 0f711d6..9171dae 100644 --- a/app/routes/post-connect.js +++ b/app/routes/post-connect.js @@ -33,7 +33,7 @@ if(params.ssid && params.ssid.length > 0) { // non-broadcast - console.log("Client submitting non-broadcast network..."); + app.log.info("Client submitting non-broadcast network..."); if(!params.security) { return error(res, "Invalid parameters"); @@ -60,7 +60,7 @@ var record = cells[params.network] || undefined; - console.log("Client submitting pre-scanned network..."); + app.log.info("Client submitting pre-scanned network..."); if(!record) { return error(res, "Network not found"); } if(params.password && record.encryption) { @@ -79,14 +79,11 @@ delete params.network; - console.log( + app.log.info( - util.format( - - "Requesting wpa_supplicant config for %s (%s)." - , params.ssid || "Unknown Network" - , params.encType || "OPEN" - ) + "Requesting wpa_supplicant config for %s (%s)." + , params.ssid || "Unknown Network" + , params.encType || "OPEN" ); app.send("writeConfig", params); res.json({ 'connected' : true }); diff --git a/app/routes/xhr-networks.js b/app/routes/xhr-networks.js index e778e3c..06a97c2 100644 --- a/app/routes/xhr-networks.js +++ b/app/routes/xhr-networks.js @@ -14,7 +14,7 @@ app.get('/networks', function(req, res, next) { app.send('wifiScan', true); - console.log("Client requested network list..."); + app.log.info("Client requested network list..."); console.log(cells); setTimeout(function() { diff --git a/index.js b/index.js old mode 100644 new mode 100755 index f8f5322..abac25c --- a/index.js +++ b/index.js @@ -10,113 +10,137 @@ * Emily Rose */ - ;(function() { - - var - argv = require('optimist').argv - , config = require('./app/config') - , fork = require('child_process').fork - , express = require('express') - , port = argv.port || 80 - , path = require('path') - , util = require('util') - , app = express() - ; - - function exit(reason) { - - process.stderr.write( - - util.format( - - "Exiting: Could not listen on port %s. %s\n" - , port - , reason - ) - ); - }; - - /** - * Messages from the monitor process - */ - function message(msg) { + +var + argv = require('optimist').argv + , express = require('express') + , config = require('./app/config') + , logger = require('./lib/logger') + , creds = require('./lib/credentials') + , fork = require('child_process').fork + , port = argv.port || 80 + , path = require('path') + , util = require('util') + , app = express() +; + + +function exit(reason) { + + app.log.error( + + "Exiting: Could not listen on port %s. (%s)" + , port + , reason + ); +}; - if(!msg.action || typeof msg.data == 'undefined' || msg.error) { +/** + * Messages from the monitor process + */ +function message(msg) { - var - stack = new Error().stack - , error - ; + if(!msg.action || typeof msg.data == 'undefined' || msg.error) { - if(msg.error && msg.error == "unknownAction" && msg.action) { + var + stack = new Error().stack + , error + ; - error = util.format( - 'Unknown monitor action: %s' - , msg.action - ); - } - else { + if(msg.error && msg.error == "unknownAction" && msg.action) { - error = util.format( - 'Error from monitor: %s' - , msg.error || "UNKNOWN" - ); - } + error = util.format( + 'Unknown monitor action: %s' + , msg.action + ); + } + else { - console.log(error); - console.log(stack); - return; + error = util.format( + 'Error from monitor: %s' + , msg.error || "UNKNOWN" + ); } - app.emit(msg.action, msg.data); - }; + console.log(stack); + return; + } - var monitor = function monitor() { + app.emit(msg.action, msg.data); +}; - monitor.process = fork(path.resolve(__dirname, 'monitor')); - - monitor.process - .on('message', message) - .on('exit', monitor) - .send({ action : "init" }) - ; - }; +var monitor = function monitor() { - /** - * Messages _to_ the monitor process - */ - app.send = function send(action, data) { + monitor.process = fork(path.resolve(__dirname, 'monitor')); + + monitor.process + .on('message', message) + .on('exit', monitor) + .send({ action : "init" }) + ; + // kill child if parent dies + process.once('exit', function() { + monitor.process.kill() + }) +}; +/** + * Messages _to_ the monitor process + */ +app.send = function send(action, data) { + if (!monitor.process) { + // enqueue message + app.once('monitor', function() { + deliver() + }) + } else deliver() + + function deliver() { monitor.process.send({ - 'action' : action , 'data' : data || null }); - }; - - /** - * Exit if we have problems. Upstart should handle restarts. - */ - process.on("uncaughtException", function(err) { + } +}; - if(err.code == "EACCES") { +/** + * Exit if we have problems. Upstart should handle restarts. + */ +process.on("uncaughtException", function(err) { - exit("Access denied."); + if(err.code == "EACCES") { - } - else if(err.code == "EADDRINUSE") { + exit("Access denied."); - exit("Address in use."); - } - else { + } + else if(err.code == "EADDRINUSE") { - process.stderr.write(util.format("Exiting: %s.\n", err)); - } - process.exit(1); - }); + exit("Address in use."); + } + else { + + process.stderr.write(err.stack); + process.stderr.write("\n\n"); + } + process.exit(1); +}); + +this.log = new logger({ + + env : 'production' + , logFile : '/var/log/wifisetup.log' +}); + +app.log = this.log; + +creds.call(this, { - config(app).listen(port); + serialFile : '/etc/opt/ninja/serial.conf' +}); - monitor(); +app.once('platformOK', function() { + app.log.log('platform ok, starting monitoring') + monitor(); +}) -})(); +config.call(this, app).listen(port); diff --git a/lib/credentials.js b/lib/credentials.js new file mode 100644 index 0000000..6e97e02 --- /dev/null +++ b/lib/credentials.js @@ -0,0 +1,103 @@ +var + fs = require('fs') + , path = require('path') + , existsSync = fs.existsSync || path.existsSync +; + +/** + * Hacked from Ninja Blocks credential provider + */ +function credentials(opts) { + + this.opts = opts; + this.token = undefined; + this.serial = undefined; + + this.loadSerial = loadCred.bind(this, 'serial'); + + var serial = this.loadSerial(); + if(!serial) { + + this.log.error("Unable to load serial from file"); + } + + return this; +}; + +function bindMethod(method) { + + var + cred = method.substr(4).toLowerCase() + , action = method.substr(0, 4) + ; + + this[method] = credManager.bind(this, action, cred); +}; + +function credManager(action, cred) { + + if(action == 'save') { + + saveCred.call(this, cred); + } + else if(action == 'load') { + + loadCred.call(this, cred); + } +}; + +function saveCred(cred) { + + var cFile = credFile.call(this, cred); + if(!cFile) { + + this.log.error('Unable to save %s to file (no path specified)', cred); + return false; + } + this.log.debug('Attempting to save %s to file...', cred); + try { + + fs.writeFileSync(cFile, this[cred]); + } + catch(e) { + + this.log.error('Unable to save %s file (%s)', cred, e); + return false; + } + this.log.info('Successfully saved %s to file', cred); + return true; +}; + +function loadCred(cred) { + + var contents = '' + , cFile = credFile.call(this, cred); + + if(!cFile) { + + this.log.error('Unable to load %s from file (no path specified)', cred); + return false; + } + try { + + if (existsSync(cFile)) { + + contents = fs.readFileSync(cFile, 'utf8'); + } + } + catch(e) { + + this.log.error('Unable to load %s from file (%s)', cred, e); + return false; + } + this[cred] = contents.replace(/\n/g, ''); + this.log.info('Successfully loaded %s from file: %s', cred, this[cred]); + return true; +}; + +function credFile(cred) { + + return this.opts[cred + 'File'] || undefined; +}; + +module.exports = credentials; diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 0000000..77f29c6 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,142 @@ +var + colors = require('colors') + , util = require('util') + , fs = require('fs') +; + +// TODO: add sprintf support to log messages. + +function logger(opts) { + + this.opts = opts || { }; + this.lastLogged = undefined; + this.stream = undefined; + + this.createStream(); + this.access = true; + return this; +}; + + +logger.prototype.streamError = function streamError(err) { + + if(err.code == 'EACCES' && this.access) { + + this.access = false; + this.stream = undefined; + logError(err); + } + if(!this.access) { return; } + function logError() { + + console.log( + + [ + "%s (" + , "ERROR".red + , ") logger: %s" + ].join("") + , now() + , err + ); + }; + logError(err); +}; + +logger.prototype.createStream = function createStream() { + + /** + * Only supporting local logging for now. + */ + if(!this.opts.logFile) { + + return false; + } + + this.stream = fs.createWriteStream(this.opts.logFile, { + + flags : 'a' + , encoding : 'utf8' + , mode : this.opts.logPerms || 0600 + }); + + var myLogger = this; + this.stream.on('error', function(err) { + + myLogger.streamError(err); + setTimeout(function reStream() { + + myLogger.createStream.call(myLogger); + }, 5000); + }); + + return this.stream || undefined; +}; + +logger.prototype.log = function log() { + + /** + * Log something with a timestamp + */ + var args = Array.prototype.slice.call(arguments); + var type = args.shift(); + var l = util.format.apply(null, args); + if(l == this.lastLogged) { return; } + var str = [ now(), type, l ].join(' '); + console.log(str); + this.lastLogged = l; + if(this.stream && this.stream.writable) { + + this.stream.write(str + "\n"); + } +}; + +logger.prototype.debug = function debug() { + + if(this.opts.env != "development" + && this.opts.env != "hacking") { return; } + + this.msg.call(this, 'DEBUG'.cyan, arguments); +}; + +logger.prototype.info = function info() { + + this.msg.call(this, 'info'.green, arguments); +}; + +logger.prototype.error = function error() { + + var args = Array.prototype.slice.call(arguments); + if(args[0] instanceof Error && process.env.NODE_ENV == "development") { + + var str = [ + + now() + , '(' + 'ERROR'.magenta + ')' + , args[0].stack + ].join(' '); + + this.stream.write(str + "\n"); + return console.log(str); + } + this.msg.call(this, 'ERROR'.red, arguments); +}; + +logger.prototype.warn = function warn() { + + this.msg.call(this, 'warn'.yellow, arguments); +}; + +logger.prototype.msg = function msg(type, args) { + + var args = Array.prototype.slice.call(args); + args.unshift('('+(type || 'info')+')'); + this.log.apply(this, args); +}; + +function now() { + + return '['.white + (new Date()).toUTCString().grey + ']'.white; +} + +module.exports = logger; diff --git a/monitor.js b/monitor.js index 6c2b43b..94f0935 100644 --- a/monitor.js +++ b/monitor.js @@ -1,6 +1,6 @@ var exec = require('child_process').exec - , opts = { timout : 10000 } + , opts = { timeout : 10000 } , actions = { "wifiScan" : require('./bin/wifi_scan') diff --git a/package.json b/package.json index eaa4eec..99bee8c 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,13 @@ { - "name": "ninja-wifi", - "version": "0.0.0", + "name": "block-wifi", + "version": "0.1.0", "description": "Ninja Blocks WiFi Utility", "main": "index.js", "scripts": { - "test": "" + "start": "./index.js" + }, + "bin": { + "block-wifi": "./index.js" }, "repository": { "type": "git", @@ -19,7 +22,8 @@ "license": "MIT", "dependencies": { "optimist": "~0.3.4", - "express": "~3.0.0rc4", - "ejs": "~0.8.3" + "express": "~3.2.6", + "ejs": "~0.8.3", + "colors": "~0.6.0-1" } } diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..2625fcf --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,4 @@ +html body { + padding-top: 10%; + background: transparent; +} diff --git a/views/auth.ejs b/views/auth.ejs new file mode 100644 index 0000000..ff4f546 --- /dev/null +++ b/views/auth.ejs @@ -0,0 +1,22 @@ + + + Ninja Blocks WiFi Setup + + + + + + + + +
+ +
+ +

You Need Authorization

+

If you landed here from your dashboard, please try again.

+
+ +
+ + diff --git a/views/connected.ejs b/views/connected.ejs index 86ad331..df0d505 100644 --- a/views/connected.ejs +++ b/views/connected.ejs @@ -4,8 +4,9 @@ - + + diff --git a/views/error.ejs b/views/error.ejs new file mode 100644 index 0000000..1343526 --- /dev/null +++ b/views/error.ejs @@ -0,0 +1,22 @@ + + + Ninja Blocks WiFi Setup – <%- title %> + + + + + + + + +
+ +
+ +

<%- title %>

+

<%- message %>

+
+ +
+ + diff --git a/views/index.ejs b/views/index.ejs index f6baee4..7c3a796 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -6,6 +6,7 @@ + diff --git a/views/plugin.ejs b/views/plugin.ejs index b42f8b4..21762df 100644 --- a/views/plugin.ejs +++ b/views/plugin.ejs @@ -4,8 +4,9 @@ - + + diff --git a/views/serial.ejs b/views/serial.ejs new file mode 100644 index 0000000..bb33566 --- /dev/null +++ b/views/serial.ejs @@ -0,0 +1,26 @@ + + + Ninja Blocks WiFi Setup + + + + + + + + + +
+ +
+ +

Ninja Serial Unavailable

+

The WiFi utility was unable to detect the serial number of this + ninja block. If this problem persists, please restart your ninja + block or consult the forums. This is a security precaution + to prevent unauthorized changes to your ninja block.

+
+ +
+ +