### Request Handler ============ > The request handler (surprisingly) handles requests made through HTTP to > the [HTTP Listener](http-listener.html). It will handle user requests for > pages as well as POST requests such as user login, module storing, event > invocation and also admin commands. ### # **Loads Modules:** # - [Persistence](persistence.html) db = require './persistence' # - Node.js Modules: [fs](http://nodejs.org/api/fs.html), # [path](http://nodejs.org/api/path.html) and # [querystring](http://nodejs.org/api/querystring.html) fs = require 'fs' path = require 'path' qs = require 'querystring' # - External Modules: [mustache](https://github.com/janl/mustache.js) and # [crypto-js](https://github.com/evanvosberg/crypto-js) mustache = require 'mustache' crypto = require 'crypto-js' pathUsers = path.resolve __dirname, '..', 'config', 'users.json' # Prepare the user command handlers which are invoked via HTTP requests. dirHandlers = path.resolve __dirname, '..', 'webpages', 'handlers' exports = module.exports = ( args ) => @log = args.logger # Register the request service @userRequestHandler = args[ 'request-service' ] # Register the shutdown handler to the admin command. @objAdminCmds = shutdown: ( obj, cb ) -> data = code: 200 message: 'Shutting down... BYE!' setTimeout args[ 'shutdown-function' ], 500 cb null, data newuser: ( obj, cb ) -> data = code: 200 message: 'User stored thank you!' if obj.username and obj.password if obj.roles try roles = JSON.parse obj.roles catch err @log 'RH | error parsing newuser roles: ' + err.message roles = [] else roles = [] oUser = username: obj.username password: obj.password roles: roles db.storeUser oUser fPersistNewUser = ( username, password, roles ) -> ( err, data ) -> users = JSON.parse data users[ username ] = password: password roles: roles fs.writeFile pathUsers, JSON.stringify( users, undefined, 2 ), 'utf8', ( err ) -> if err @log.error "RH | Unable to write new user file! " @log.error err # { # "admin": { # "password": "7407946a7a90b037ba5e825040f184a142161e4c61d81feb83ec8c7f011a99b0d77f39c9170c3231e1003c5cf859c69bd93043b095feff5cce6f6d45ec513764", # "roles": ["admin"] # } # } # {"admin":{"password":"7407946a7a90b037ba5e825040f184a142161e4c61d81feb83ec8c7f011a99b0d77f39c9170c3231e1003c5cf859c69bd93043b095feff5cce6f6d45ec513764","roles":["admin"]},"dominic":{"password":"2d51496fbe5b6d3e98e22d68140609eaedd64de457b2f75c346a4a98f87928eac11ea2be747709ae7a2f5b177af09a60a8dbf14bf703e0cb9b147fc0a3e3a064","roles":[]}} fs.readFile pathUsers, 'utf8', fPersistNewUser obj.username, obj.password, roles else data.code = 401 data.message = 'Missing parameter for this command' cb null, data db args # Load the standard users from the user config file users = JSON.parse fs.readFileSync pathUsers, 'utf8' fStoreUser = ( username, oUser ) -> oUser.username = username db.storeUser oUser fStoreUser user, oUser for user, oUser of users module.exports ### Handles possible events that were posted to this server and pushes them into the event queue. *Requires the [request](http://nodejs.org/api/http.html#http_class_http_clientrequest) and [response](http://nodejs.org/api/http.html#http_class_http_serverresponse) objects.* @public handleEvent( *req, resp* ) ### exports.handleEvent = ( req, resp ) -> body = '' req.on 'data', ( data ) -> body += data req.on 'end', -> #if req.session and req.session.user try obj = JSON.parse body catch err resp.send 400, 'Badly formed event!' # If required event properties are present we process the event # if obj and obj.event and not err timestamp = ( new Date() ).toISOString() rand = ( Math.floor Math.random() * 10e9 ).toString( 16 ).toUpperCase() obj.eventid = "#{ obj.event }_UTC|#{ timestamp }_#{ rand }" answ = code: 200 message: "Thank you for the event: #{ obj.eventid }" resp.send answ.code, answ db.pushEvent obj else resp.send 400, 'Your event was missing important parameters!' # else # resp.send 401, 'Please login!' ### Associates the user object with the session if login is successful. *Requires the [request](http://nodejs.org/api/http.html#http_class_http_clientrequest) and [response](http://nodejs.org/api/http.html#http_class_http_serverresponse) objects.* @public handleLogin( *req, resp* ) ### exports.handleLogin = ( req, resp ) => body = '' req.on 'data', ( data ) -> body += data req.on 'end', => obj = JSON.parse body db.loginUser obj.username, obj.password, ( err, usr ) => if err # Tapping on fingers, at least in log... @log.warn "RH | AUTH-UH-OH ( #{ obj.username } ): #{ err.message }" else # no error, so we can associate the user object from the DB to the session req.session.user = usr if req.session.user resp.send 'OK!' else resp.send 401, 'NO!' ### A post request retrieved on this handler causes the user object to be purged from the session, thus the user will be logged out. *Requires the [request](http://nodejs.org/api/http.html#http_class_http_clientrequest) and [response](http://nodejs.org/api/http.html#http_class_http_serverresponse) objects.* @public handleLogout( *req, resp* ) ### exports.handleLogout = ( req, resp ) -> if req.session req.session.user = null resp.send 'Bye!' ### Resolves the path to a handler webpage. @private getHandlerPath( *name* ) @param {String} name ### getHandlerPath = ( name ) -> path.join dirHandlers, name + '.html' ### Fetches a template. @private getTemplate( *name* ) @param {String} name ### getTemplate = ( name ) -> pth = path.join dirHandlers, 'templates', name + '.html' fs.readFileSync pth, 'utf8' ### Fetches a script. @private getScript( *name* ) @param {String} name ### getScript = ( name ) -> pth = path.join dirHandlers, 'js', name + '.js' fs.readFileSync pth, 'utf8' ### Fetches remote scripts snippets. @private getRemoteScripts( *name* ) @param {String} name ### getRemoteScripts = ( name ) -> pth = path.join dirHandlers, 'remote-scripts', name + '.html' fs.readFileSync pth, 'utf8' ### Renders a page, with helps of mustache, depending on the user session and returns it. @private renderPage( *name, sess, msg* ) @param {String} name @param {Object} sess @param {Object} msg ### renderPage = ( name, req, resp, msg ) -> # Grab the skeleton pathSkel = path.join dirHandlers, 'skeleton.html' skeleton = fs.readFileSync pathSkel, 'utf8' code = 200 data = message: msg user: req.session.user # Try to grab the script belonging to this page. But don't bother if it's not existing try script = getScript name # Try to grab the remote scripts belonging to this page. But don't bother if it's not existing try remote_scripts = getRemoteScripts name # Now try to find the page the user requested. try content = getTemplate name catch err # If the page doesn't exist we return the error page, load the error script into it # and render the error page with some additional data content = getTemplate 'error' script = getScript 'error' code = 404 data.message = 'Invalid Page!' if req.session.user menubar = getTemplate 'menubar' pageElements = content: content script: script remote_scripts: remote_scripts menubar: menubar # First we render the page by including all page elements into the skeleton page = mustache.render skeleton, pageElements # Then we complete the rendering by adding the data, and send the result to the user resp.send code, mustache.render page, data ### Present the desired forge page to the user. *Requires the [request](http://nodejs.org/api/http.html#http_class_http_clientrequest) and [response](http://nodejs.org/api/http.html#http_class_http_serverresponse) objects.* @public handleForge( *req, resp* ) ### exports.handleForge = ( req, resp ) -> page = req.query.page if not req.session.user page = 'login' renderPage page, req, resp ### Handles the user command requests. *Requires the [request](http://nodejs.org/api/http.html#http_class_http_clientrequest) and [response](http://nodejs.org/api/http.html#http_class_http_serverresponse) objects.* @public handleUser( *req, resp* ) ### exports.handleUserCommand = ( req, resp ) => if req.session and req.session.user body = '' #Append data to body while receiving fragments req.on 'data', ( data ) -> body += data req.on 'end', => obj = qs.parse body # Let the user request handler service answer the request @userRequestHandler req.session.user, obj, ( obj ) -> resp.send obj.code, obj else resp.send 401, 'Login first!' ### Present the admin console to the user if he's allowed to see it. *Requires the [request](http://nodejs.org/api/http.html#http_class_http_clientrequest) and [response](http://nodejs.org/api/http.html#http_class_http_serverresponse) objects.* @public handleForge( *req, resp* ) ### exports.handleAdmin = ( req, resp ) -> if not req.session.user page = 'login' #TODO isAdmin should come from the db role else if req.session.user.roles.indexOf( "admin" ) is -1 page = 'login' msg = 'You need to be admin for this page!' else page = 'admin' renderPage page, req, resp, msg ### Handles the admin command requests. *Requires the [request](http://nodejs.org/api/http.html#http_class_http_clientrequest) and [response](http://nodejs.org/api/http.html#http_class_http_serverresponse) objects.* @public handleAdminCommand( *req, resp* ) ### exports.handleAdminCommand = ( req, resp ) => if req.session and req.session.user and req.session.user.roles.indexOf( "admin" ) > -1 body = '' req.on 'data', ( data ) -> body += data req.on 'end', => obj = qs.parse body @log.info 'RH | Received admin request: ' + obj.command arrCmd = obj.command.split( ' ' ) if not arrCmd[ 0 ] or not @objAdminCmds[ arrCmd[ 0 ] ] resp.send 404, 'Command unknown!' else arrParams = arrCmd.slice 1 oParams = {} for keyVal in arrParams arrKV = keyVal.split ":" if arrKV.length is 2 oParams[ arrKV[ 0 ] ] = arrKV[ 1 ] @objAdminCmds[ arrCmd[ 0 ] ] oParams, ( err, obj ) -> resp.send obj.code, obj else resp.send 401, 'You need to be logged in as admin!'