diff --git a/coffee/config.coffee b/coffee/config.coffee index 76422ab..8272926 100644 --- a/coffee/config.coffee +++ b/coffee/config.coffee @@ -2,15 +2,16 @@ Config ====== +> Loads the configuration file and acts as an interface to it. -Loads the configuration file and acts as an interface to it. ### -'use strict' -path = require 'path' -log = require './logging' fs = require 'fs' -config = null +path = require 'path' +# Requires: + +# - The [Logging](logging.html) module +log = require './logging' ### ##Module call @@ -19,7 +20,7 @@ Calling the module as a function will make it look for the `relPath` property in args object and then try to load a config file from that relative path. @param {Object} args ### -exports = module.exports = (args) -> +exports = module.exports = ( args ) -> args = args ? {} log args if typeof args.relPath is 'string' @@ -33,11 +34,11 @@ Reads the config file synchronously from the file system and try to parse it. @private loadConfigFile @param {String} relPath ### -loadConfigFile = (relPath) -> +loadConfigFile = ( relPath ) => try - config = JSON.parse fs.readFileSync path.resolve __dirname, '..', relPath - if config and config.http_port and config.db_port and - config.crypto_key and config.session_secret + @config = JSON.parse fs.readFileSync path.resolve __dirname, '..', relPath + if @config and @config.http_port and @config.db_port and + @config.crypto_key and @config.session_secret log.print 'CF', 'config file loaded successfully' else log.error 'CF', new Error("""Missing property in config file, requires: @@ -57,14 +58,14 @@ Fetch a property from the configuration @private fetchProp( *prop* ) @param {String} prop ### -fetchProp = (prop) -> config?[prop] +fetchProp = ( prop ) => @config?[prop] ### Answer true if the config file is ready, else false @public isReady() ### -exports.isReady = -> config? +exports.isReady = => @config? ### Returns the HTTP port diff --git a/coffee/db_interface.coffee b/coffee/db_interface.coffee index fbed5e3..6d7ce89 100644 --- a/coffee/db_interface.coffee +++ b/coffee/db_interface.coffee @@ -2,48 +2,48 @@ DB Interface ============ ->Handles the connection to the database and provides functionalities for ->event/action modules, rules and the encrypted storing of authentication tokens. ->General functionality as a wrapper for the module holds initialization, ->encryption/decryption, the retrieval of modules and shut down. -> ->The general structure for linked data is that the key is stored in a set. ->By fetching all set entries we can then fetch all elements, which is ->automated in this function. ->For example modules of the same group, e.g. action modules are registered in an ->unordered set in the database, from where they can be retrieved again. For example ->a new action module has its ID (e.g 'probinder') first registered in the set ->'action_modules' and then stored in the db with the key 'action\_module\_' + ID ->(e.g. action\_module\_probinder). +> Handles the connection to the database and provides functionalities for +> event/action modules, rules and the encrypted storing of authentication tokens. +> General functionality as a wrapper for the module holds initialization, +> encryption/decryption, the retrieval of modules and shut down. +> +> The general structure for linked data is that the key is stored in a set. +> By fetching all set entries we can then fetch all elements, which is +> automated in this function. +> For example modules of the same group, e.g. action modules are registered in an +> unordered set in the database, from where they can be retrieved again. For example +> a new action module has its ID (e.g 'probinder') first registered in the set +> 'action_modules' and then stored in the db with the key 'action\_module\_' + ID +> (e.g. action\_module\_probinder). > ### -'use strict' -### Grab all required modules ### redis = require 'redis' crypto = require 'crypto' # TODO change to Google's "crypto-js"" -log = require './logging' -crypto_key = null -db = null +# Requires: + +# - The [Logging](logging.html) module +log = require './logging' ### -##Module call - +Module call +----------- Initializes the DB connection. Requires a valid configuration file which contains a db port and a crypto key. + @param {Object} args ### -exports = module.exports = (args) -> +exports = module.exports = ( args ) => args = args ? {} log args config = require './config' config args - crypto_key = config.getCryptoKey() - db = redis.createClient config.getDBPort(), 'localhost', { connect_timeout: 2000 } - db.on "error", (err) -> + @crypto_key = config.getCryptoKey() + @db = redis.createClient config.getDBPort(), 'localhost', { connect_timeout: 2000 } + @db.on "error", ( err ) -> err.addInfo = 'message from DB' log.error 'DB', err @@ -55,12 +55,12 @@ ten attempts within five seconds, or nothing on success to the callback(err). @param {function} cb ### #}TODO check if timeout works with func in func -exports.isConnected = (cb) -> - if db.connected then cb() +exports.isConnected = ( cb ) => + if @db.connected then cb() else numAttempts = 0 - fCheckConnection = -> - if db.connected + fCheckConnection = => + if @db.connected log.print 'DB', 'Successfully connected to DB!' cb() else if numAttempts++ < 10 @@ -78,10 +78,10 @@ Encrypts a string using the crypto key from the config file, based on aes-256-cb @private encrypt( *plainText* ) @param {String} plainText ### -encrypt = (plainText) -> +encrypt = ( plainText ) => if !plainText? then return null try - enciph = crypto.createCipher 'aes-256-cbc', crypto_key + enciph = crypto.createCipher 'aes-256-cbc', @crypto_key et = enciph.update plainText, 'utf8', 'base64' et + enciph.final 'base64' catch err @@ -95,10 +95,10 @@ Decrypts an encrypted string and hands it back on success or null. @private decrypt( *crypticText* ) @param {String} crypticText ### -decrypt = (crypticText) -> +decrypt = ( crypticText ) => if !crypticText? then return null; try - deciph = crypto.createDecipher 'aes-256-cbc', crypto_key + deciph = crypto.createDecipher 'aes-256-cbc', @crypto_key dt = deciph.update crypticText, 'base64', 'utf8' dt + deciph.final 'utf8' catch err @@ -112,8 +112,8 @@ Abstracts logging for simple action replies from the DB. @private replyHandler( *action* ) @param {String} action ### -replyHandler = (action) -> - (err, reply) -> +replyHandler = ( action ) -> + ( err, reply ) -> if err err.addInfo = 'during "' + action + '"' log.error 'DB', err @@ -129,9 +129,9 @@ via the provided function and returns the results to the callback(err, obj) func @param {function} fSingle a function to retrieve a single data element per set entry @param {function} cb the callback(err, obj) function that receives all the retrieved data or an error ### -getSetRecords = (set, fSingle, cb) -> +getSetRecords = ( set, fSingle, cb ) => log.print 'DB', 'Fetching set records: ' + set - db?.smembers set, (err, arrReply) -> + @db.smembers set, ( err, arrReply ) -> if err err.addInfo = 'fetching ' + set log.error 'DB', err @@ -145,8 +145,8 @@ getSetRecords = (set, fSingle, cb) -> if semaphore > 0 cb new Error('Timeout fetching ' + set) , 2000 - fCallback = (prop) -> - (err, data) -> + fCallback = ( prop ) -> + ( err, data ) -> --semaphore if err err.addInfo = 'fetching single element: ' + prop @@ -171,10 +171,10 @@ Store a string representation of an action module in the DB. @param {String} data ### # FIXME can the data be an object? -exports.storeActionModule = (id, data) -> +exports.storeActionModule = ( id, data ) => log.print 'DB', 'storeActionModule: ' + id - db?.sadd 'action-modules', id, replyHandler 'storing action module key ' + id - db?.set 'action-module:' + id, data, replyHandler 'storing action module ' + id + @db.sadd 'action-modules', id, replyHandler 'storing action module key ' + id + @db.set 'action-module:' + id, data, replyHandler 'storing action module ' + id ### Query the DB for an action module and pass it to the callback(err, obj) function. @@ -183,9 +183,9 @@ Query the DB for an action module and pass it to the callback(err, obj) function @param {String} id @param {function} cb ### -exports.getActionModule = (id, cb) -> +exports.getActionModule = ( id, cb ) => log.print 'DB', 'getActionModule: ' + id - db?.get 'action-module:' + id, cb + @db.get 'action-module:' + id, cb ### Fetch all action modules and hand them to the callback(err, obj) function. @@ -193,7 +193,7 @@ Fetch all action modules and hand them to the callback(err, obj) function. @public getActionModules( *cb* ) @param {function} cb ### -exports.getActionModules = (cb) -> +exports.getActionModules = ( cb ) -> getSetRecords 'action-modules', exports.getActionModule, cb ### @@ -204,9 +204,9 @@ Store a string representation of the authentication parameters for an action mod @param {String} moduleId @param {String} data ### -exports.storeActionAuth = (userId, moduleId, data) -> +exports.storeActionAuth = ( userId, moduleId, data ) => log.print 'DB', 'storeActionAuth: ' + userId + ':' + moduleId - db?.set 'action-auth:' + userId + ':' + moduleId, encrypt(data), + @db.set 'action-auth:' + userId + ':' + moduleId, encrypt(data), replyHandler 'storing action auth ' + userId + ':' + moduleId ### @@ -218,9 +218,9 @@ and pass it to the callback(err, obj) function. @param {String} moduleId @param {function} cb ### -exports.getActionAuth = (userId, moduleId, cb) -> +exports.getActionAuth = ( userId, moduleId, cb ) => log.print 'DB', 'getActionAuth: ' + userId + ':' + moduleId - db?.get 'action-auth:' + userId + ':' + moduleId, (err, data) -> + @db.get 'action-auth:' + userId + ':' + moduleId, ( err, data ) -> cb err, decrypt data @@ -235,10 +235,10 @@ Store a string representation of an event module in the DB. @param {String} id @param {String} data ### -exports.storeEventModule = (id, data) -> +exports.storeEventModule = ( id, data ) => log.print 'DB', 'storeEventModule: ' + id - db?.sadd 'event-modules', id, replyHandler 'storing event module key ' + id - db?.set 'event-module:' + id, data, replyHandler 'storing event module ' + id + @db.sadd 'event-modules', id, replyHandler 'storing event module key ' + id + @db.set 'event-module:' + id, data, replyHandler 'storing event module ' + id ### Query the DB for an event module and pass it to the callback(err, obj) function. @@ -247,9 +247,9 @@ Query the DB for an event module and pass it to the callback(err, obj) function. @param {String} id @param {function} cb ### -exports.getEventModule = (id, cb) -> +exports.getEventModule = ( id, cb ) => log.print 'DB', 'getEventModule: ' + id - db?.get 'event_module:' + id, cb + @db.get 'event_module:' + id, cb ### Fetch all event modules and pass them to the callback(err, obj) function. @@ -257,31 +257,33 @@ Fetch all event modules and pass them to the callback(err, obj) function. @public getEventModules( *cb* ) @param {function} cb ### -exports.getEventModules = (cb) -> +exports.getEventModules = ( cb ) -> getSetRecords 'event_modules', exports.getEventModule, cb ### Store a string representation of he authentication parameters for an event module. @public storeEventAuth( *userId, moduleId, data* ) -@param {String} id -@param {String} data +@param {String} userId +@param {String} moduleId +@param {Object} data ### -exports.storeEventAuth = (userId, moduleId, data) -> +exports.storeEventAuth = ( userId, moduleId, data ) => log.print 'DB', 'storeEventAuth: ' + userId + ':' + moduleId - db?.set 'event-auth:' + userId + ':' + moduleId, encrypt(data), + @db.set 'event-auth:' + userId + ':' + moduleId, encrypt(data), replyHandler 'storing event auth ' + userId + ':' + moduleId ### Query the DB for an action module authentication token, associated with a user. -@public getEventAuth( *id, cb* ) -@param {String} id +@public getEventAuth( *userId, moduleId, data* ) +@param {String} userId +@param {String} moduleId @param {function} cb ### -exports.getEventAuth = (userId, moduleId, cb) -> +exports.getEventAuth = ( userId, moduleId, cb ) => log.print 'DB', 'getEventAuth: ' + userId + ':' + moduleId - db?.get 'event-auth:' + userId + ':' + moduleId, (err, data) -> + @db.get 'event-auth:' + userId + ':' + moduleId, ( err, data ) -> cb err, decrypt data @@ -296,10 +298,10 @@ Store a string representation of a rule in the DB. @param {String} id @param {String} data ### -exports.storeRule = (id, data) -> +exports.storeRule = ( id, data ) => log.print 'DB', 'storeRule: ' + id - db?.sadd 'rules', id, replyHandler 'storing rule key ' + id - db?.set 'rule:' + id, data, replyHandler 'storing rule ' + id + @db.sadd 'rules', id, replyHandler 'storing rule key ' + id + @db.set 'rule:' + id, data, replyHandler 'storing rule ' + id ### Query the DB for a rule and pass it to the callback(err, obj) function. @@ -308,9 +310,9 @@ Query the DB for a rule and pass it to the callback(err, obj) function. @param {String} id @param {function} cb ### -exports.getRule = (id, cb) -> +exports.getRule = ( id, cb ) => log.print 'DB', 'getRule: ' + id - db?.get 'rule:' + id, cb + @db.get 'rule:' + id, cb ### Fetch all rules from the database and pass them to the callback function. @@ -318,7 +320,7 @@ Fetch all rules from the database and pass them to the callback function. @public getRules( *cb* ) @param {function} cb ### -exports.getRules = (cb) -> +exports.getRules = ( cb ) -> log.print 'DB', 'Fetching all Rules' getSetRecords 'rules', exports.getRule, cb @@ -328,12 +330,14 @@ Store a user object (needs to be a flat structure). @public storeUser( *objUser* ) @param {Object} objUser ### -exports.storeUser = (objUser) -> +exports.storeUser = ( objUser ) => + # TODO Only store user if not already existing, or at least only then add a private key + # for his encryption. we would want to have one private key per user, right? log.print 'DB', 'storeUser: ' + objUser.username if objUser and objUser.username and objUser.password - db?.sadd 'users', objUser.username, replyHandler 'storing user key ' + objUser.username + @db.sadd 'users', objUser.username, replyHandler 'storing user key ' + objUser.username objUser.password = encrypt objUser.password - db?.hmset 'user:' + objUser.username, objUser, replyHandler 'storing user properties ' + objUser.username + @db.hmset 'user:' + objUser.username, objUser, replyHandler 'storing user properties ' + objUser.username else log.error 'DB', new Error 'username or password was missing' @@ -344,10 +348,10 @@ Associate a role with a user. @param {String} username @param {String} role ### -exports.storeUserRole = (username, role) -> +exports.storeUserRole = ( username, role ) => log.print 'DB', 'storeUserRole: ' + username + ':' + role - db?.sadd 'user-roles:' + username, role, replyHandler 'adding role ' + role + ' to user ' + username - db?.sadd 'role-users:' + role, username, replyHandler 'adding user ' + username + ' to role ' + role + @db.sadd 'user-roles:' + username, role, replyHandler 'adding role ' + role + ' to user ' + username + @db.sadd 'role-users:' + role, username, replyHandler 'adding user ' + username + ' to role ' + role ### Fetch all roles of a user and pass them to the callback(err, obj) @@ -355,9 +359,9 @@ Fetch all roles of a user and pass them to the callback(err, obj) @public getUserRoles( *username* ) @param {String} username ### -exports.getUserRoles = (username) -> +exports.getUserRoles = ( username ) => log.print 'DB', 'getUserRole: ' + username - db?.get 'user-roles:' + username, cb + @db.get 'user-roles:' + username, cb ### Fetch all users of a role and pass them to the callback(err, obj) @@ -365,9 +369,9 @@ Fetch all users of a role and pass them to the callback(err, obj) @public getUserRoles( *role* ) @param {String} role ### -exports.getRoleUsers = (role) -> +exports.getRoleUsers = ( role ) => log.print 'DB', 'getRoleUsers: ' + role - db?.get 'role-users:' + role, cb + @db.get 'role-users:' + role, cb ### Checks the credentials and on success returns the user object to the callback(err, obj) function. @@ -378,10 +382,10 @@ Checks the credentials and on success returns the user object to the callback(er @param {function} cb ### # TODO verify and test whole function -exports.loginUser = (username, password, cb) -> +exports.loginUser = ( username, password, cb ) => log.print 'DB', 'User "' + username + '" tries to log in' - fCheck = (pw) -> - (err, obj) -> + fCheck = ( pw ) -> + ( err, obj ) -> if err cb err else if obj and obj.password @@ -392,7 +396,7 @@ exports.loginUser = (username, password, cb) -> cb new Error 'Wrong credentials!' else cb new Error 'Empty arguments!' - db?.hgetall 'user:' + username, fCheck password + @db.hgetall 'user:' + username, fCheck password # TODO implement functions required for user sessions and the rule activation @@ -401,4 +405,4 @@ Shuts down the db link. @public shutDown() ### -exports.shutDown = -> db?.quit() +exports.shutDown = => @db.quit() diff --git a/coffee/http_listener.coffee b/coffee/http_listener.coffee new file mode 100644 index 0000000..bd743eb --- /dev/null +++ b/coffee/http_listener.coffee @@ -0,0 +1,85 @@ +### + +HTTP Listener +============= +> Handles the HTTP requests to the server at the port specified by the +> [config](config.html) file. + +### + +path = require 'path' +express = require 'express' +app = express() +# } RedisStore = require('connect-redis')(express), # TODO use RedisStore for persistent sessions +qs = require 'querystring' + +# Requires: + +# - The [Logging](logging.html) module +log = require './logging' +# - The [Config](config.html) module +config = require './config' +# - The [User Handler](user_handler.html) module +userHandler = require './user_handler' +sess_sec = '#C[>;j`@".TXm2TA;A2Tg)' + + +# The module needs to be called as a function to initialize it. +# After that it fetches the http\_port, db\_port & sess\_sec properties +# from the configuration file. +exports = module.exports = ( args ) -> + args = args ? {} + log args + config args + userHandler args + # TODO check whether this really does what it's supposed to do (fetch wrong sess property) + sess_sec = config.getSessionSecret() || sess_sec + module.exports + +exports.addHandlers = ( fEvtHandler, fShutDown ) => + userHandler.addShutdownHandler fShutDown + @eventHandler = fEvtHandler + # Add cookie support for session handling. + app.use express.cookieParser() + app.use express.session { secret: sess_sec } + log.print 'HL', 'no session backbone' + + # Redirect the requests to the appropriate handler. + app.use '/', express.static path.resolve __dirname, '..', 'webpages' + app.get '/rulesforge', userHandler.handleRequest + app.get '/admin', userHandler.handleRequest + app.post '/login', userHandler.handleLogin + app.post '/push_event', onPushEvent + try + http_port = config.getHttpPort() + if http_port + 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 = 'opening port' + log.error e + +# +# If a post request reaches the server, this function handles it and treats the request as a possible event. +# +onPushEvent = ( req, resp ) => + body = '' + req.on 'data', ( data ) -> + body += data + req.on 'end', => + obj = qs.parse body + # If required event properties are present we process the event # + if obj and obj.event and obj.eventid + resp.write 'Thank you for the event (' + obj.event + '[' + obj.eventid + '])!' + @eventHandler obj + else + resp.writeHead 400, { "Content-Type": "text/plain" } + resp.write 'Your event was missing important parameters!' + resp.end() + + +exports.shutDown = () -> + log.print 'HL', 'Shutting down HTTP listener' + process.exit() # This is a bit brute force... + diff --git a/coffee/server.coffee b/coffee/server.coffee index 2b96121..b650436 100644 --- a/coffee/server.coffee +++ b/coffee/server.coffee @@ -1,6 +1,8 @@ ### + Rules Server ============ + >This is the main module that is used to run the whole server: > > node server [log_type http_port] @@ -14,46 +16,38 @@ Rules Server >`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' -### Grab all required modules ### -path = require 'path' +# Requires: + +# - The [Logging](logging.html) module log = require './logging' +# - The [Config](config.html) module conf = require './config' +# - The [DB Interface](db_interface.html) module db = require './db_interface' +# - The [Engine](engine.html) module engine = require './engine' +# - The [HTTP Listener](http_listener.html) module http_listener = require './http_listener' -mm = require './module_manager' args = {} procCmds = {} -### Prepare the admin commands that are issued via HTTP requests. ### -adminCmds = - 'loadrules': mm.loadRulesFromFS, - 'loadaction': mm.loadActionModuleFromFS, - 'loadactions': mm.loadActionModulesFromFS, - 'loadevent': mm.loadEventModuleFromFS, - 'loadevents': mm.loadEventModulesFromFS, - 'loadusers': http_listener.loadUsers, - 'shutdown': shutDown - ### 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', (err) -> +process.on 'uncaughtException', ( err ) -> switch err.errno when 'EADDRINUSE' err.addInfo = 'http_port already in use, shutting down!' log.error 'RS', err shutDown() - else log.error err - null + else throw err ### This function is invoked right after the module is loaded and starts the server. @@ -85,9 +79,10 @@ init = -> if process.argv.length > 3 then args.http_port = parseInt process.argv[3] else log.print 'RS', 'No HTTP port passed, using standard port from config file' + log.print 'RS', 'Initialzing DB' db args ### We only proceed with the initialization if the DB is ready ### - db.isConnected (err, result) -> + db.isConnected ( err, result ) -> if !err ### Initialize all required modules with the args object.### @@ -95,57 +90,23 @@ init = -> engine args log.print 'RS', 'Initialzing http listener' http_listener args - log.print 'RS', 'Initialzing module manager' - mm args - log.print 'RS', 'Initialzing DB' ### Distribute handlers between modules to link the application. ### log.print 'RS', 'Passing handlers to engine' engine.addDBLinkAndLoadActionsAndRules db log.print 'RS', 'Passing handlers to http listener' - http_listener.addHandlers db, fAdminCommands, engine.pushEvent - log.print 'RS', 'Passing handlers to module manager' - mm.addHandlers db, engine.loadActionModule, engine.addRule - -### -admin commands handler receives all command arguments and an answerHandler -object that eases response handling to the HTTP request issuer. - -@private fAdminCommands( *args, answHandler* ) -### -fAdminCommands = (args, answHandler) -> - if args and args.cmd - adminCmds[args.cmd]? args, answHandler - else - log.print 'RS', 'No command in request' - - ### - The fAnsw function receives an answerHandler object as an argument when called - and returns an anonymous function - ### - fAnsw = (ah) -> - ### - The anonymous function checks whether the answerHandler was already used to - issue an answer, if no answer was provided we answer with an error message - ### - () -> - if not ah.isAnswered() - ah.answerError 'Not handled...' - - ### - Delayed function call of the anonymous function that checks the answer handler - ### - setTimeout fAnsw(answHandler), 2000 + # TODO engine pushEvent needs to go into redis queue + http_listener.addHandlers db, engine.pushEvent, shutDown + # log.print 'RS', 'Passing handlers to module manager' + # TODO loadAction and addRule will be removed + # mm.addHandlers db, engine.loadActionModule, engine.addRule ### Shuts down the server. -@private shutDown( *args, answHandler* ) -@param {Object} args -@param {Object} answHandler +@private shutDown() ### -shutDown = (args, answHandler) -> - answHandler?.answerSuccess 'Goodbye!' +shutDown = -> log.print 'RS', 'Received shut down command!' engine?.shutDown() http_listener?.shutDown() @@ -157,7 +118,7 @@ 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', (cmd) -> procCmds[cmd]?() +process.on 'message', ( cmd ) -> procCmds[cmd]?() ### The die command redirects to the shutDown function. diff --git a/coffee/test.coffee b/coffee/test.coffee deleted file mode 100644 index 2441715..0000000 --- a/coffee/test.coffee +++ /dev/null @@ -1,10 +0,0 @@ -efew = require 'fs' - -### -root = exports ? this -root.foo = -> 'Hello World' -console.log root.foo() - -My comments will show up here - -### \ No newline at end of file diff --git a/coffee/user_handler.coffee b/coffee/user_handler.coffee new file mode 100644 index 0000000..bed90da --- /dev/null +++ b/coffee/user_handler.coffee @@ -0,0 +1,138 @@ +### + +User Handler +============ +> TODO Add documentation + +### + +fs = require 'fs' +path = require 'path' +qs = require 'querystring' + +# Requires: + +# - The [Logging](logging.html) module +log = require './logging' +# - The [DB Interface](db_interface.html) module +db = require './db_interface' +# - The [Module Manager](module_manager.html) module +mm = require './module_manager' + +### Prepare the admin command handlers that are issued via HTTP requests. ### +objAdminCmds = + 'loadrules': mm.loadRulesFromFS, + 'loadaction': mm.loadActionModuleFromFS, + 'loadactions': mm.loadActionModulesFromFS, + 'loadevent': mm.loadEventModuleFromFS, + 'loadevents': mm.loadEventModulesFromFS + + +exports = module.exports = ( args ) -> + args = args ? {} + log args + db args + mm args + mm.addDBLink db + users = JSON.parse fs.readFileSync path.resolve __dirname, '..', 'config', 'users.json' + db.storeUser user for user in users + module.exports + + +exports.addShutdownHandler = ( fShutdown ) -> + objAdminCmds.shutdown = fShutdown + + +exports.handleRequest = ( req, resp ) -> + req.on 'end', -> resp.end() + if req.session and req.session.user + resp.send 'You\'re logged in' + else + resp.sendfile path.resolve __dirname, '..', 'webpages', 'handlers', 'login.html' + req.session.lastPage = req.originalUrl + + +exports.handleLogin = ( req, resp ) -> + body = '' + req.on 'data', ( data ) -> body += data + req.on 'end', -> + if not req.session or not req.session.user + obj = qs.parse body + db.loginUser obj.username, obj.password, ( err, obj ) -> + if not err + req.session.user = obj + 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() + else + resp.write 'Welcome ' + req.session.user.name + '!' + resp.end() + + +answerHandler = ( resp ) -> + hasBeenAnswered = false + postAnswer( msg ) -> + if not hasBeenAnswered + resp.write msg + resp.end() + hasBeenAnswered = true + { + answerSuccess: ( msg ) -> + if not hasBeenAnswered + postAnswer msg, + answerError: ( msg ) -> + if not hasBeenAnswered + resp.writeHead 400, { "Content-Type": "text/plain" } + postAnswer msg, + isAnswered: -> hasBeenAnswered + } + +# TODO add loadUsers as directive to admin commands +# exports.loadUsers = -> + # var users = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'users.json'))); + # for(var name in users) { + # db.storeUser(users[name]); + # } +# }; + +onAdminCommand = ( req, response ) -> + q = req.query; + log.print 'HL', 'Received admin request: ' + req.originalUrl + if q.cmd + fAdminCommands q, answerHandler response + #answerSuccess(response, 'Thank you, we try our best!'); + else answerError response, 'I\'m not sure about what you want from me...' + + +### +admin commands handler receives all command arguments and an answerHandler +object that eases response handling to the HTTP request issuer. + +@private fAdminCommands( *args, answHandler* ) +### +fAdminCommands = ( args, answHandler ) -> + if args and args.cmd + adminCmds[args.cmd]? args, answHandler + else + log.print 'RS', 'No command in request' + + ### + The fAnsw function receives an answerHandler object as an argument when called + and returns an anonymous function + ### + fAnsw = ( ah ) -> + ### + The anonymous function checks whether the answerHandler was already used to + issue an answer, if no answer was provided we answer with an error message + ### + () -> + if not ah.isAnswered() + ah.answerError 'Not handled...' + + ### + Delayed function call of the anonymous function that checks the answer handler + ### + setTimeout fAnsw(answHandler), 2000 diff --git a/js-coffee/config.js b/js-coffee/config.js index 70dc870..2330f6b 100644 --- a/js-coffee/config.js +++ b/js-coffee/config.js @@ -3,23 +3,20 @@ Config ====== - -Loads the configuration file and acts as an interface to it. +> Loads the configuration file and acts as an interface to it. */ (function() { - 'use strict'; - var config, exports, fetchProp, fs, loadConfigFile, log, path; + var exports, fetchProp, fs, loadConfigFile, log, path, + _this = this; + + fs = require('fs'); path = require('path'); log = require('./logging'); - fs = require('fs'); - - config = null; - /* ##Module call @@ -50,8 +47,8 @@ Loads the configuration file and acts as an interface to it. loadConfigFile = function(relPath) { var e; try { - config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', relPath))); - if (config && config.http_port && config.db_port && config.crypto_key && config.session_secret) { + _this.config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', relPath))); + if (_this.config && _this.config.http_port && _this.config.db_port && _this.config.crypto_key && _this.config.session_secret) { return log.print('CF', 'config file loaded successfully'); } else { return log.error('CF', new Error("Missing property in config file, requires:\n- http_port\n- db_port\n- crypto_key\n- session_secret")); @@ -74,7 +71,8 @@ Loads the configuration file and acts as an interface to it. fetchProp = function(prop) { - return config != null ? config[prop] : void 0; + var _ref; + return (_ref = _this.config) != null ? _ref[prop] : void 0; }; /* @@ -85,7 +83,7 @@ Loads the configuration file and acts as an interface to it. exports.isReady = function() { - return config != null; + return _this.config != null; }; /* diff --git a/js-coffee/db_interface.js b/js-coffee/db_interface.js index 77a9afc..015879d 100644 --- a/js-coffee/db_interface.js +++ b/js-coffee/db_interface.js @@ -3,28 +3,26 @@ DB Interface ============ ->Handles the connection to the database and provides functionalities for ->event/action modules, rules and the encrypted storing of authentication tokens. ->General functionality as a wrapper for the module holds initialization, ->encryption/decryption, the retrieval of modules and shut down. -> ->The general structure for linked data is that the key is stored in a set. ->By fetching all set entries we can then fetch all elements, which is ->automated in this function. ->For example modules of the same group, e.g. action modules are registered in an ->unordered set in the database, from where they can be retrieved again. For example ->a new action module has its ID (e.g 'probinder') first registered in the set ->'action_modules' and then stored in the db with the key 'action\_module\_' + ID ->(e.g. action\_module\_probinder). +> Handles the connection to the database and provides functionalities for +> event/action modules, rules and the encrypted storing of authentication tokens. +> General functionality as a wrapper for the module holds initialization, +> encryption/decryption, the retrieval of modules and shut down. +> +> The general structure for linked data is that the key is stored in a set. +> By fetching all set entries we can then fetch all elements, which is +> automated in this function. +> For example modules of the same group, e.g. action modules are registered in an +> unordered set in the database, from where they can be retrieved again. For example +> a new action module has its ID (e.g 'probinder') first registered in the set +> 'action_modules' and then stored in the db with the key 'action\_module\_' + ID +> (e.g. action\_module\_probinder). > */ (function() { - 'use strict'; - /* Grab all required modules*/ - - var crypto, crypto_key, db, decrypt, encrypt, exports, getSetRecords, log, redis, replyHandler; + var crypto, decrypt, encrypt, exports, getSetRecords, log, redis, replyHandler, + _this = this; redis = require('redis'); @@ -32,15 +30,12 @@ DB Interface log = require('./logging'); - crypto_key = null; - - db = null; - /* - ##Module call - + Module call + ----------- Initializes the DB connection. Requires a valid configuration file which contains a db port and a crypto key. + @param {Object} args */ @@ -51,11 +46,11 @@ DB Interface log(args); config = require('./config'); config(args); - crypto_key = config.getCryptoKey(); - db = redis.createClient(config.getDBPort(), 'localhost', { + _this.crypto_key = config.getCryptoKey(); + _this.db = redis.createClient(config.getDBPort(), 'localhost', { connect_timeout: 2000 }); - return db.on("error", function(err) { + return _this.db.on("error", function(err) { err.addInfo = 'message from DB'; return log.error('DB', err); }); @@ -72,13 +67,13 @@ DB Interface exports.isConnected = function(cb) { var fCheckConnection, numAttempts; - if (db.connected) { + if (_this.db.connected) { return cb(); } else { numAttempts = 0; fCheckConnection = function() { var e; - if (db.connected) { + if (_this.db.connected) { log.print('DB', 'Successfully connected to DB!'); return cb(); } else if (numAttempts++ < 10) { @@ -107,7 +102,7 @@ DB Interface return null; } try { - enciph = crypto.createCipher('aes-256-cbc', crypto_key); + enciph = crypto.createCipher('aes-256-cbc', _this.crypto_key); et = enciph.update(plainText, 'utf8', 'base64'); return et + enciph.final('base64'); } catch (_error) { @@ -132,7 +127,7 @@ DB Interface return null; } try { - deciph = crypto.createDecipher('aes-256-cbc', crypto_key); + deciph = crypto.createDecipher('aes-256-cbc', _this.crypto_key); dt = deciph.update(crypticText, 'base64', 'utf8'); return dt + deciph.final('utf8'); } catch (_error) { @@ -175,7 +170,7 @@ DB Interface getSetRecords = function(set, fSingle, cb) { log.print('DB', 'Fetching set records: ' + set); - return db != null ? db.smembers(set, function(err, arrReply) { + return _this.db.smembers(set, function(err, arrReply) { var fCallback, objReplies, reply, semaphore, _i, _len, _results; if (err) { err.addInfo = 'fetching ' + set; @@ -213,7 +208,7 @@ DB Interface } return _results; } - }) : void 0; + }); }; /* @@ -232,10 +227,8 @@ DB Interface exports.storeActionModule = function(id, data) { log.print('DB', 'storeActionModule: ' + id); - if (db != null) { - db.sadd('action-modules', id, replyHandler('storing action module key ' + id)); - } - return db != null ? db.set('action-module:' + id, data, replyHandler('storing action module ' + id)) : void 0; + _this.db.sadd('action-modules', id, replyHandler('storing action module key ' + id)); + return _this.db.set('action-module:' + id, data, replyHandler('storing action module ' + id)); }; /* @@ -249,7 +242,7 @@ DB Interface exports.getActionModule = function(id, cb) { log.print('DB', 'getActionModule: ' + id); - return db != null ? db.get('action-module:' + id, cb) : void 0; + return _this.db.get('action-module:' + id, cb); }; /* @@ -276,7 +269,7 @@ DB Interface exports.storeActionAuth = function(userId, moduleId, data) { log.print('DB', 'storeActionAuth: ' + userId + ':' + moduleId); - return db != null ? db.set('action-auth:' + userId + ':' + moduleId, encrypt(data), replyHandler('storing action auth ' + userId + ':' + moduleId)) : void 0; + return _this.db.set('action-auth:' + userId + ':' + moduleId, encrypt(data), replyHandler('storing action auth ' + userId + ':' + moduleId)); }; /* @@ -292,9 +285,9 @@ DB Interface exports.getActionAuth = function(userId, moduleId, cb) { log.print('DB', 'getActionAuth: ' + userId + ':' + moduleId); - return db != null ? db.get('action-auth:' + userId + ':' + moduleId, function(err, data) { + return _this.db.get('action-auth:' + userId + ':' + moduleId, function(err, data) { return cb(err, decrypt(data)); - }) : void 0; + }); }; /* @@ -313,10 +306,8 @@ DB Interface exports.storeEventModule = function(id, data) { log.print('DB', 'storeEventModule: ' + id); - if (db != null) { - db.sadd('event-modules', id, replyHandler('storing event module key ' + id)); - } - return db != null ? db.set('event-module:' + id, data, replyHandler('storing event module ' + id)) : void 0; + _this.db.sadd('event-modules', id, replyHandler('storing event module key ' + id)); + return _this.db.set('event-module:' + id, data, replyHandler('storing event module ' + id)); }; /* @@ -330,7 +321,7 @@ DB Interface exports.getEventModule = function(id, cb) { log.print('DB', 'getEventModule: ' + id); - return db != null ? db.get('event_module:' + id, cb) : void 0; + return _this.db.get('event_module:' + id, cb); }; /* @@ -349,30 +340,32 @@ DB Interface Store a string representation of he authentication parameters for an event module. @public storeEventAuth( *userId, moduleId, data* ) - @param {String} id - @param {String} data + @param {String} userId + @param {String} moduleId + @param {Object} data */ exports.storeEventAuth = function(userId, moduleId, data) { log.print('DB', 'storeEventAuth: ' + userId + ':' + moduleId); - return db != null ? db.set('event-auth:' + userId + ':' + moduleId, encrypt(data), replyHandler('storing event auth ' + userId + ':' + moduleId)) : void 0; + return _this.db.set('event-auth:' + userId + ':' + moduleId, encrypt(data), replyHandler('storing event auth ' + userId + ':' + moduleId)); }; /* Query the DB for an action module authentication token, associated with a user. - @public getEventAuth( *id, cb* ) - @param {String} id + @public getEventAuth( *userId, moduleId, data* ) + @param {String} userId + @param {String} moduleId @param {function} cb */ exports.getEventAuth = function(userId, moduleId, cb) { log.print('DB', 'getEventAuth: ' + userId + ':' + moduleId); - return db != null ? db.get('event-auth:' + userId + ':' + moduleId, function(err, data) { + return _this.db.get('event-auth:' + userId + ':' + moduleId, function(err, data) { return cb(err, decrypt(data)); - }) : void 0; + }); }; /* @@ -391,10 +384,8 @@ DB Interface exports.storeRule = function(id, data) { log.print('DB', 'storeRule: ' + id); - if (db != null) { - db.sadd('rules', id, replyHandler('storing rule key ' + id)); - } - return db != null ? db.set('rule:' + id, data, replyHandler('storing rule ' + id)) : void 0; + _this.db.sadd('rules', id, replyHandler('storing rule key ' + id)); + return _this.db.set('rule:' + id, data, replyHandler('storing rule ' + id)); }; /* @@ -408,7 +399,7 @@ DB Interface exports.getRule = function(id, cb) { log.print('DB', 'getRule: ' + id); - return db != null ? db.get('rule:' + id, cb) : void 0; + return _this.db.get('rule:' + id, cb); }; /* @@ -435,11 +426,9 @@ DB Interface exports.storeUser = function(objUser) { log.print('DB', 'storeUser: ' + objUser.username); if (objUser && objUser.username && objUser.password) { - if (db != null) { - db.sadd('users', objUser.username, replyHandler('storing user key ' + objUser.username)); - } + _this.db.sadd('users', objUser.username, replyHandler('storing user key ' + objUser.username)); objUser.password = encrypt(objUser.password); - return db != null ? db.hmset('user:' + objUser.username, objUser, replyHandler('storing user properties ' + objUser.username)) : void 0; + return _this.db.hmset('user:' + objUser.username, objUser, replyHandler('storing user properties ' + objUser.username)); } else { return log.error('DB', new Error('username or password was missing')); } @@ -456,10 +445,8 @@ DB Interface exports.storeUserRole = function(username, role) { log.print('DB', 'storeUserRole: ' + username + ':' + role); - if (db != null) { - db.sadd('user-roles:' + username, role, replyHandler('adding role ' + role + ' to user ' + username)); - } - return db != null ? db.sadd('role-users:' + role, username, replyHandler('adding user ' + username + ' to role ' + role)) : void 0; + _this.db.sadd('user-roles:' + username, role, replyHandler('adding role ' + role + ' to user ' + username)); + return _this.db.sadd('role-users:' + role, username, replyHandler('adding user ' + username + ' to role ' + role)); }; /* @@ -472,7 +459,7 @@ DB Interface exports.getUserRoles = function(username) { log.print('DB', 'getUserRole: ' + username); - return db != null ? db.get('user-roles:' + username, cb) : void 0; + return _this.db.get('user-roles:' + username, cb); }; /* @@ -485,7 +472,7 @@ DB Interface exports.getRoleUsers = function(role) { log.print('DB', 'getRoleUsers: ' + role); - return db != null ? db.get('role-users:' + role, cb) : void 0; + return _this.db.get('role-users:' + role, cb); }; /* @@ -517,7 +504,7 @@ DB Interface } }; }; - return db != null ? db.hgetall('user:' + username, fCheck(password)) : void 0; + return _this.db.hgetall('user:' + username, fCheck(password)); }; /* @@ -528,7 +515,7 @@ DB Interface exports.shutDown = function() { - return db != null ? db.quit() : void 0; + return _this.db.quit(); }; }).call(this); diff --git a/js-coffee/http_listener.js b/js-coffee/http_listener.js index 5ec9603..a30ba6d 100644 --- a/js-coffee/http_listener.js +++ b/js-coffee/http_listener.js @@ -1,116 +1,95 @@ -// HTTP Listener -// ============= -// -// Handles the HTTP requests to the server at the port specified by the [config](config.html) file. - -'use strict'; - -var path = require('path'), - express = require('express'), - app = express(), - RedisStore = require('connect-redis')(express), - qs = require('querystring'), - log = require('./logging'), - sess_sec = '#C[>;j`@".TXm2TA;A2Tg)', - db_port, http_port, server, - eventHandler, userHandler; - +// Generated by CoffeeScript 1.6.3 /* - * The module needs to be called as a function to initialize it. - * After that it fetches the http\_port, db\_port & sess\_sec properties - * from the configuration file. - */ -exports = module.exports = function(args) { - args = args || {}; - log(args); - var config = require('./config')(args); - userHandler = require('./user_handler')(args); - db_port = config.getDBPort(), - sess_sec = config.getSessionSecret(), - http_port = config.getHttpPort(); - return module.exports; -}; -exports.addHandlers = function(funcAdminHandler, funcEvtHandler) { - if(!funcAdminHandler || !funcEvtHandler) { - log.error('HL', 'ERROR: either adminHandler or eventHandler function not defined!'); - return; - } - userHandler.addHandler(funcAdminHandler); - eventHandler = funcEvtHandler; - // Add cookie support for session handling. - app.use(express.cookieParser()); - app.use(express.session({secret: sess_sec})); - log.print('HL', 'no session backbone'); - - // ^ TODO figure out why redis backbone doesn't work. eventually the db pass has to be set in the DB? - // } session information seems to be stored in DB but not retrieved correctly - // } if(db_port) { - // } app.use(express.session({ - // } store: new RedisStore({ - // } host: 'localhost', - // } port: db_port, - // } db: 2 - // } , - // } pass: null - // } }), - // } secret: sess_sec - // } })); - // } log.print('HL', 'Added redis DB as session backbone'); - // } } else { - // } app.use(express.session({secret: sess_sec})); - // } log.print('HL', 'no session backbone'); - // } } +HTTP Listener +============= +> Handles the HTTP requests to the server at the port specified by the +> [config](config.html) file. +*/ - // Redirect the requests to the appropriate handler. - 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('/rulesforge/', express.static(path.resolve(__dirname, '..', 'webpages', 'rulesforge'))); - app.get('/admin', userHandler.handleRequest); - app.post('/login', userHandler.handleLogin); - app.post('/push_event', onPushEvent); - 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'}); - } -}; -/** - * If a post request reaches the server, this function handles it and treats the request as a possible event. - */ -function onPushEvent(req, resp) { - var body = ''; - req.on('data', function (data) { body += data; }); - req.on('end', function () { - var obj = qs.parse(body); - /* If required event properties are present we process the event */ - if(obj && obj.event && obj.eventid){ - resp.writeHead(200, { "Content-Type": "text/plain" }); - resp.write('Thank you for the event (' + obj.event + '[' + obj.eventid + '])!'); - eventHandler(obj); - } else { - resp.writeHead(400, { "Content-Type": "text/plain" }); - resp.write('Your event was missing important parameters!'); +(function() { + var app, config, exports, express, log, onPushEvent, path, qs, sess_sec, userHandler, + _this = this; + + path = require('path'); + + express = require('express'); + + app = express(); + + qs = require('querystring'); + + log = require('./logging'); + + config = require('./config'); + + userHandler = require('./user_handler'); + + sess_sec = '#C[>;j`@".TXm2TA;A2Tg)'; + + exports = module.exports = function(args) { + args = args != null ? args : {}; + log(args); + config(args); + userHandler(args); + sess_sec = config.getSessionSecret() || sess_sec; + return module.exports; + }; + + exports.addHandlers = function(fEvtHandler, fShutDown) { + var e, http_port; + userHandler.addShutdownHandler(fShutDown); + _this.eventHandler = fEvtHandler; + app.use(express.cookieParser()); + app.use(express.session({ + secret: sess_sec + })); + log.print('HL', 'no session backbone'); + app.use('/', express["static"](path.resolve(__dirname, '..', 'webpages'))); + app.get('/rulesforge', userHandler.handleRequest); + app.get('/admin', userHandler.handleRequest); + app.post('/login', userHandler.handleLogin); + app.post('/push_event', onPushEvent); + try { + http_port = config.getHttpPort(); + if (http_port) { + return app.listen(http_port); + } else { + return log.error('HL', new Error('No HTTP port found!? Nothing to listen on!...')); + } + } catch (_error) { + e = _error; + e.addInfo = 'opening port'; + return log.error(e); } - resp.end(); - }); -} + }; -exports.loadUsers = function() { - var users = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', relPath))); - for(var name in users) { - - } -}; + onPushEvent = function(req, resp) { + var body; + body = ''; + req.on('data', function(data) { + return body += data; + }); + return req.on('end', function() { + var obj; + obj = qs.parse(body); + if (obj && obj.event && obj.eventid) { + resp.write('Thank you for the event (' + obj.event + '[' + obj.eventid + '])!'); + _this.eventHandler(obj); + } else { + resp.writeHead(400, { + "Content-Type": "text/plain" + }); + resp.write('Your event was missing important parameters!'); + } + return resp.end(); + }); + }; -exports.shutDown = function() { - log.print('HL', 'Shutting down HTTP listener'); - process.exit(); // This is a bit brute force... -}; + exports.shutDown = function() { + log.print('HL', 'Shutting down HTTP listener'); + return process.exit(); + }; + +}).call(this); diff --git a/js-coffee/module_manager.js b/js-coffee/module_manager.js index e41d995..ecac592 100644 --- a/js-coffee/module_manager.js +++ b/js-coffee/module_manager.js @@ -21,10 +21,10 @@ exports = module.exports = function(args) { return module.exports; }; -exports.addHandlers = function(db_link, fLoadAction, fLoadRule) { +exports.addDBLink = function(db_link) { db = db_link; - funcLoadAction = fLoadAction; - funcLoadRule = fLoadRule; + // funcLoadAction = fLoadAction; + // funcLoadRule = fLoadRule; }; /* @@ -47,7 +47,7 @@ exports.loadRulesFromFS = function(args, answHandler) { for(var i = 0; i < arr.length; i++) { txt += arr[i].id + ', '; db.storeRule(arr[i].id, JSON.stringify(arr[i])); - funcLoadRule(arr[i]); + // funcLoadRule(arr[i]); } answHandler.answerSuccess('Yep, loaded rules: ' + txt); } catch (e) { @@ -71,7 +71,7 @@ exports.loadRulesFromFS = function(args, answHandler) { */ function loadActionCallback(name, data, mod, auth) { db.storeActionModule(name, data); // store module in db - funcLoadAction(name, mod); // hand back compiled module + // funcLoadAction(name, mod); // hand back compiled module if(auth) db.storeActionModuleAuth(name, auth); } diff --git a/js-coffee/server.js b/js-coffee/server.js index 777b0be..7afe549 100644 --- a/js-coffee/server.js +++ b/js-coffee/server.js @@ -1,7 +1,9 @@ // Generated by CoffeeScript 1.6.3 /* + Rules Server ============ + >This is the main module that is used to run the whole server: > > node server [log_type http_port] @@ -15,17 +17,12 @@ Rules Server >`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. > ->--- +> */ (function() { - 'use strict'; - /* Grab all required modules*/ - - var adminCmds, args, conf, db, engine, fAdminCommands, http_listener, init, log, mm, path, procCmds, shutDown; - - path = require('path'); + var args, conf, db, engine, http_listener, init, log, procCmds, shutDown; log = require('./logging'); @@ -37,25 +34,10 @@ Rules Server http_listener = require('./http_listener'); - mm = require('./module_manager'); - args = {}; procCmds = {}; - /* Prepare the admin commands that are issued via HTTP requests.*/ - - - adminCmds = { - 'loadrules': mm.loadRulesFromFS, - 'loadaction': mm.loadActionModuleFromFS, - 'loadactions': mm.loadActionModulesFromFS, - 'loadevent': mm.loadEventModuleFromFS, - 'loadevents': mm.loadEventModulesFromFS, - 'loadusers': http_listener.loadUsers, - 'shutdown': shutDown - }; - /* Error handling of the express port listener requires special attention, thus we have to catch the process error, which is issued if @@ -68,12 +50,10 @@ Rules Server case 'EADDRINUSE': err.addInfo = 'http_port already in use, shutting down!'; log.error('RS', err); - shutDown(); - break; + return shutDown(); default: - log.error(err); + throw err; } - return null; }); /* @@ -119,6 +99,7 @@ Rules Server } else { log.print('RS', 'No HTTP port passed, using standard port from config file'); } + log.print('RS', 'Initialzing DB'); db(args); /* We only proceed with the initialization if the DB is ready*/ @@ -130,75 +111,24 @@ Rules Server engine(args); log.print('RS', 'Initialzing http listener'); http_listener(args); - log.print('RS', 'Initialzing module manager'); - mm(args); - log.print('RS', 'Initialzing DB'); /* Distribute handlers between modules to link the application.*/ log.print('RS', 'Passing handlers to engine'); engine.addDBLinkAndLoadActionsAndRules(db); log.print('RS', 'Passing handlers to http listener'); - http_listener.addHandlers(db, fAdminCommands, engine.pushEvent); - log.print('RS', 'Passing handlers to module manager'); - return mm.addHandlers(db, engine.loadActionModule, engine.addRule); + return http_listener.addHandlers(db, engine.pushEvent, shutDown); } }); }; - /* - admin commands handler receives all command arguments and an answerHandler - object that eases response handling to the HTTP request issuer. - - @private fAdminCommands( *args, answHandler* ) - */ - - - fAdminCommands = function(args, answHandler) { - var fAnsw, _name; - if (args && args.cmd) { - if (typeof adminCmds[_name = args.cmd] === "function") { - adminCmds[_name](args, answHandler); - } - } else { - log.print('RS', 'No command in request'); - } - /* - The fAnsw function receives an answerHandler object as an argument when called - and returns an anonymous function - */ - - fAnsw = function(ah) { - /* - The anonymous function checks whether the answerHandler was already used to - issue an answer, if no answer was provided we answer with an error message - */ - - return function() { - if (!ah.isAnswered()) { - return ah.answerError('Not handled...'); - } - }; - }; - /* - Delayed function call of the anonymous function that checks the answer handler - */ - - return setTimeout(fAnsw(answHandler), 2000); - }; - /* Shuts down the server. - @private shutDown( *args, answHandler* ) - @param {Object} args - @param {Object} answHandler + @private shutDown() */ - shutDown = function(args, answHandler) { - if (answHandler != null) { - answHandler.answerSuccess('Goodbye!'); - } + shutDown = function() { log.print('RS', 'Received shut down command!'); if (engine != null) { engine.shutDown(); diff --git a/js-coffee/test.js b/js-coffee/test.js deleted file mode 100644 index 90c0465..0000000 --- a/js-coffee/test.js +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by CoffeeScript 1.6.3 -(function() { - var efew; - - efew = require('fs'); - - /* - root = exports ? this - root.foo = -> 'Hello World' - console.log root.foo() - - My comments will show up here - */ - - -}).call(this); diff --git a/js-coffee/user_handler.js b/js-coffee/user_handler.js index 0eed5b8..5a1de67 100644 --- a/js-coffee/user_handler.js +++ b/js-coffee/user_handler.js @@ -1,96 +1,180 @@ -var path = require('path'), - qs = require('querystring'), - log = require('./logging'), - db = require('./db_interface'), - adminHandler; - -exports = module.exports = function(args) { - args = args || {}; - log(args); - db(args); - var users = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', 'config', 'users.json'))); - for(var i = 0; i < users.length; i++) { - log.print('UH', 'Found user ' + users[i].username + ' in user file, storing him in the DB'); - db.storeUser(users[i]); - } - - return module.exports; -}; +// Generated by CoffeeScript 1.6.3 +/* -exports.addHandler = function(adminHandl) { - adminHandler = adminHandl; -}; +User Handler +============ +> TODO Add documentation +*/ -exports.handleRequest = function(req, resp) { - req.on('end', function () { - resp.end(); - }); - 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; - log.print('UH', 'last: '+ req.session.lastPage); - log.print('UH', 'retrieved req: '+ req.originalUrl); - // 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); - db.loginUser(obj.username, obj.password, function(err, obj) { - if(err) { - log.error('UH', err); - resp.writeHead(401, { "Content-Type": "text/plain" }); - resp.write('Login failed!'); - } - else { - req.session.user = obj; - if(req.session.user) { +(function() { + var answerHandler, db, exports, fAdminCommands, fs, log, mm, objAdminCmds, onAdminCommand, path, qs; + + fs = require('fs'); + + path = require('path'); + + qs = require('querystring'); + + log = require('./logging'); + + db = require('./db_interface'); + + mm = require('./module_manager'); + + /* Prepare the admin command handlers that are issued via HTTP requests.*/ + + + objAdminCmds = { + 'loadrules': mm.loadRulesFromFS, + 'loadaction': mm.loadActionModuleFromFS, + 'loadactions': mm.loadActionModulesFromFS, + 'loadevent': mm.loadEventModuleFromFS, + 'loadevents': mm.loadEventModulesFromFS + }; + + exports = module.exports = function(args) { + var user, users, _i, _len; + args = args != null ? args : {}; + log(args); + db(args); + mm(args); + mm.addDBLink(db); + users = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'config', 'users.json'))); + for (_i = 0, _len = users.length; _i < _len; _i++) { + user = users[_i]; + db.storeUser(user); + } + return module.exports; + }; + + exports.addShutdownHandler = function(fShutdown) { + return objAdminCmds.shutdown = fShutdown; + }; + + exports.handleRequest = function(req, resp) { + req.on('end', function() { + return resp.end(); + }); + if (req.session && req.session.user) { + resp.send('You\'re logged in'); + } else { + resp.sendfile(path.resolve(__dirname, '..', 'webpages', 'handlers', 'login.html')); + } + return req.session.lastPage = req.originalUrl; + }; + + exports.handleLogin = function(req, resp) { + var body; + body = ''; + req.on('data', function(data) { + return body += data; + }); + return req.on('end', function() { + var obj; + if (!req.session || !req.session.user) { + obj = qs.parse(body); + return db.loginUser(obj.username, obj.password, function(err, obj) { + if (!err) { + req.session.user = obj; + } + if (req.session.user) { resp.write('Welcome ' + req.session.user.name + '!'); } else { - resp.writeHead(401, { "Content-Type": "text/plain" }); + resp.writeHead(401, { + "Content-Type": "text/plain" + }); resp.write('Login failed!'); } - } - resp.end(); - }); - } - }); -}; - -function answerHandler(r) { - var response = r, hasBeenAnswered = false; - function postAnswer(msg) { - if(!hasBeenAnswered) { - response.write(msg); - response.end(); - hasBeenAnswered = true; - } - } - return { - answerSuccess: function(msg) { - if(!hasBeenAnswered) response.writeHead(200, { "Content-Type": "text/plain" }); - postAnswer(msg); - }, - answerError: function(msg) { - if(!hasBeenAnswered) response.writeHead(400, { "Content-Type": "text/plain" }); - postAnswer(msg); - }, - isAnswered: function() { return hasBeenAnswered; } + return resp.end(); + }); + } else { + resp.write('Welcome ' + req.session.user.name + '!'); + return resp.end(); + } + }); }; -}; -function onAdminCommand(request, response) { - var q = request.query; - log.print('HL', 'Received admin request: ' + request.originalUrl); - if(q.cmd) { - adminHandler(q, answerHandler(response)); - // answerSuccess(response, 'Thank you, we try our best!'); - } else answerError(response, 'I\'m not sure about what you want from me...'); -} + answerHandler = function(resp) { + var hasBeenAnswered; + hasBeenAnswered = false; + postAnswer(msg)(function() { + if (!hasBeenAnswered) { + resp.write(msg); + resp.end(); + return hasBeenAnswered = true; + } + }); + return { + answerSuccess: function(msg) { + if (!hasBeenAnswered) { + return postAnswer(msg); + } + }, + answerError: function(msg) { + if (!hasBeenAnswered) { + resp.writeHead(400, { + "Content-Type": "text/plain" + }); + } + return postAnswer(msg); + }, + isAnswered: function() { + return hasBeenAnswered; + } + }; + }; + + onAdminCommand = function(req, response) { + var q; + q = req.query; + log.print('HL', 'Received admin request: ' + req.originalUrl); + if (q.cmd) { + return fAdminCommands(q, answerHandler(response)); + } else { + return answerError(response, 'I\'m not sure about what you want from me...'); + } + }; + + /* + admin commands handler receives all command arguments and an answerHandler + object that eases response handling to the HTTP request issuer. + @private fAdminCommands( *args, answHandler* ) + */ + + + fAdminCommands = function(args, answHandler) { + var fAnsw, _name; + if (args && args.cmd) { + if (typeof adminCmds[_name = args.cmd] === "function") { + adminCmds[_name](args, answHandler); + } + } else { + log.print('RS', 'No command in request'); + } + /* + The fAnsw function receives an answerHandler object as an argument when called + and returns an anonymous function + */ + + fAnsw = function(ah) { + /* + The anonymous function checks whether the answerHandler was already used to + issue an answer, if no answer was provided we answer with an error message + */ + + return function() { + if (!ah.isAnswered()) { + return ah.answerError('Not handled...'); + } + }; + }; + /* + Delayed function call of the anonymous function that checks the answer handler + */ + + return setTimeout(fAnsw(answHandler), 2000); + }; + +}).call(this); diff --git a/js/module_manager.js b/js/module_manager.js index e41d995..ce0ba41 100644 --- a/js/module_manager.js +++ b/js/module_manager.js @@ -21,10 +21,11 @@ exports = module.exports = function(args) { return module.exports; }; -exports.addHandlers = function(db_link, fLoadAction, fLoadRule) { +exports.addDBLink = function(db_link) { db = db_link; - funcLoadAction = fLoadAction; - funcLoadRule = fLoadRule; + //TODO Remove fLoadAction and fLoadRule and replace them with user commands + // funcLoadAction = fLoadAction; + // funcLoadRule = fLoadRule; }; /* @@ -34,8 +35,8 @@ exports.addHandlers = function(db_link, fLoadAction, fLoadRule) { exports.loadRulesFromFS = function(args, answHandler) { if(!args) args = {}; if(!args.name) args.name = 'rules'; - if(!funcLoadRule) log.error('ML', 'no rule loader function available'); - else { + // if(!funcLoadRule) log.error('ML', 'no rule loader function available'); + // else { fs.readFile(path.resolve(__dirname, '..', 'rules', args.name + '.json'), 'utf8', function (err, data) { if (err) { log.error('ML', 'Loading rules file: ' + args.name + '.json'); @@ -47,14 +48,14 @@ exports.loadRulesFromFS = function(args, answHandler) { for(var i = 0; i < arr.length; i++) { txt += arr[i].id + ', '; db.storeRule(arr[i].id, JSON.stringify(arr[i])); - funcLoadRule(arr[i]); + // funcLoadRule(arr[i]); } answHandler.answerSuccess('Yep, loaded rules: ' + txt); } catch (e) { log.error('ML', 'rules file was corrupt! (' + args.name + '.json)'); } }); - } + // } }; /* @@ -71,7 +72,7 @@ exports.loadRulesFromFS = function(args, answHandler) { */ function loadActionCallback(name, data, mod, auth) { db.storeActionModule(name, data); // store module in db - funcLoadAction(name, mod); // hand back compiled module + // funcLoadAction(name, mod); // hand back compiled module if(auth) db.storeActionModuleAuth(name, auth); } diff --git a/js/user_handler.js b/js/user_handler.js index 7511ed8..3e45386 100644 --- a/js/user_handler.js +++ b/js/user_handler.js @@ -2,22 +2,33 @@ var path = require('path'), qs = require('querystring'), log = require('./logging'), db = require('./db_interface'), - adminHandler; + mm = require('./module_manager'), + // ### Prepare the admin command handlers that are issued via HTTP requests. ### + objAdminCmds = { + 'loadrules': mm.loadRulesFromFS, + 'loadaction': mm.loadActionModuleFromFS, + 'loadactions': mm.loadActionModulesFromFS, + 'loadevent': mm.loadEventModuleFromFS, + 'loadevents': mm.loadEventModulesFromFS + }; exports = module.exports = function(args) { args = args || {}; log(args); db(args); + mm(args); + mm.addDBLink(db); var users = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', 'config', 'users.json'))); for(var name in users) { db.storeUser(users[name]); } + log.print('RS', 'Initialzing module manager'); return module.exports; }; -exports.addHandler = function(adminHandl) { - adminHandler = adminHandl; +exports.addHandler = function(fShutdown) { + objAdminCmds.shutdown = fShutdown; }; exports.handleRequest = function(req, resp) { @@ -77,12 +88,95 @@ function answerHandler(r) { }; }; +//TODO add loadUsers as directive to admin commands +exports.loadUsers = function () { + var users = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', 'users.json'))); + for(var name in users) { + db.storeUser(users[name]); + } +}; + function onAdminCommand(request, response) { var q = request.query; log.print('HL', 'Received admin request: ' + request.originalUrl); if(q.cmd) { - adminHandler(q, answerHandler(response)); + fAdminCommands(q, answerHandler(response)); // answerSuccess(response, 'Thank you, we try our best!'); } else answerError(response, 'I\'m not sure about what you want from me...'); } + + + + /* + admin commands handler receives all command arguments and an answerHandler + object that eases response handling to the HTTP request issuer. + @private fAdminCommands( *args, answHandler* ) + */ + + +function fAdminCommands(args, answHandler) { + var fAnsw, _name; + if (args && args.cmd) { + if (typeof objAdminCmds[_name = args.cmd] === "function") { + objAdminCmds[_name](args, answHandler); + } + } else { + log.print('RS', 'No command in request'); + } + /* + The fAnsw function receives an answerHandler object as an argument when called + and returns an anonymous function + */ + + fAnsw = function(ah) { + /* + The anonymous function checks whether the answerHandler was already used to + issue an answer, if no answer was provided we answer with an error message + */ + + return function() { + if (!ah.isAnswered()) { + return ah.answerError('Not handled...'); + } + }; + }; + /* + Delayed function call of the anonymous function that checks the answer handler + */ + + return setTimeout(fAnsw(answHandler), 2000); +}; + + +/* +### +admin commands handler receives all command arguments and an answerHandler +object that eases response handling to the HTTP request issuer. + +@private fAdminCommands( *args, answHandler* ) +### +fAdminCommands = (args, answHandler) -> + if args and args.cmd + adminCmds[args.cmd]? args, answHandler + else + log.print 'RS', 'No command in request' + + ### + The fAnsw function receives an answerHandler object as an argument when called + and returns an anonymous function + ### + fAnsw = (ah) -> + ### + The anonymous function checks whether the answerHandler was already used to + issue an answer, if no answer was provided we answer with an error message + ### + () -> + if not ah.isAnswered() + ah.answerError 'Not handled...' + + ### + Delayed function call of the anonymous function that checks the answer handler + ### + setTimeout fAnsw(answHandler), 2000 +*/