/** * # DB Interface * Handles the connection to the database and provides functionalities for * event/action modules, rules and the encrypted storing of authentication tokens. * * ## General * General functionality as a wrapper for the module holds initialization, * encryption/decryption, the retrieval of modules and shut down. * 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'; var redis = require('redis'), crypto = require('crypto'), log = require('./logging'), crypto_key, db; /** * Initializes the DB connection. Requires a valid configuration file which contains * a db port and a crypto key. * */ exports = module.exports = function(args) { args = args || {}; log(args); var config = require('./config')(args); crypto_key = config.getCryptoKey(); db = redis.createClient(config.getDBPort(), 'localhost', { connect_timeout: 2000 }); db.on("error", function (err) { err.addInfo = 'message from DB'; log.error('DB', err); }); return module.exports; }; exports.isConnected = function(cb) { if(db.connected) cb(null); else setTimeout(function() { if(db.connected) { log.print('DB', 'Successfully connected to DB!'); cb(null); } else { var e = new Error('Connection to DB failed!'); log.error('DB', e); cb(e); } }, 3000); }; /** * ### encrypt * this is used to decrypt * @param {String} plainText */ function encrypt(plainText) { if(!plainText) return null; try { var enciph = crypto.createCipher('aes-256-cbc', crypto_key); var et = enciph.update(plainText, 'utf8', 'base64') + enciph.final('base64'); log.print('DB', 'Encrypted credentials into: ' + et); return et; } catch (err) { log.error('DB', 'in encrypting: ' + err); return null; } } /** * ### decrypt */ function decrypt(crypticText, id) { if(!crypticText) return null; try { var deciph = crypto.createDecipher('aes-256-cbc', crypto_key); return deciph.update(crypticText, 'base64', 'utf8') + deciph.final('utf8'); } catch (err) { log.error('DB', 'in decrypting "' + id + '": ' + err); return null; } } /** * ### replyHandler * Abstraction answer handling for simple information replies from the DB. * @param {String} action the action to be displayed in the output string. */ function replyHandler(action) { return function(err, reply) { if(err) log.error('DB', ' during "' + action + '": ' + err); else log.print('DB', action + ': ' + reply); }; } /** * ### getSetRecords * The general structure for modules is that the key is stored in a set. * By fetching all set entries we can then fetch all modules, which is * automated in this function. * * @param {String} set the set name how it is stored in the DB * @param {function} funcSingle the function that fetches single entries from the DB * @param {function} cb the function to be called on success or error, receives * arguments (err, obj) */ function getSetRecords(set, funcSingle, cb) { if(db) db.smembers(set, function(err, reply) { if(err) log.error('DB', 'fetching ' + set + ': ' + err); else { if(reply.length === 0) { cb(null, null); } else { var semaphore = reply.length, objReplies = {}; setTimeout(function() { if(semaphore > 0) { cb('Timeout fetching ' + set, null); } }, 1000); for(var i = 0; i < reply.length; i++){ funcSingle(reply[i], function(prop) { return function(err, reply) { if(err) log.error('DB', ' fetching single element: ' + prop); else { objReplies[prop] = reply; if(--semaphore === 0) cb(null, objReplies); } }; }(reply[i])); } } } }); } // @method shutDown() // Shuts down the db link. exports.shutDown = function() { if(db) db.quit(); }; // ## Action Modules /** * ### storeActionModule * Store a string representation of an action module in the DB. * @param {String} id the unique identifier of the module * @param {String} data the string representation */ exports.storeActionModule = function(id, data) { if(db) { db.sadd('action_modules', id, replyHandler('storing action module key ' + id)); db.set('action_module_' + id, data, replyHandler('storing action module ' + id)); } }; /** * ### getActionModule(id, cb) * Query the DB for an action module. * @param {String} id the module id * @param {function} cb the cb to receive the answer (err, obj) */ exports.getActionModule = function(id, cb) { if(cb && db) db.get('action_module_' + id, cb); }; /** * ### getActionModules(cb) * Fetch all action modules. * @param {function} cb the cb to receive the answer (err, obj) */ exports.getActionModules = function(cb) { getSetRecords('action_modules', exports.getActionModule, cb); }; /** * storeActionModuleAuth(id, data) * Store a string representation of the authentication parameters for an action module. * @param {String} id the unique identifier of the module * @param {String} data the string representation */ exports.storeActionModuleAuth = function(id, data) { if(data && db) { db.sadd('action_modules_auth', id, replyHandler('storing action module auth key ' + id)); db.set('action_module_' + id +'_auth', encrypt(data), replyHandler('storing action module auth ' + id)); } }; /** * ### getActionModuleAuth(id, cb) * Query the DB for an action module authentication token. * @param {String} id the module id * @param {function} cb the cb to receive the answer (err, obj) */ exports.getActionModuleAuth = function(id, cb) { if(cb && db) db.get('action_module_' + id + '_auth', function(id) { return function(err, txt) { cb(err, decrypt(txt, 'action_module_' + id + '_auth')); }; }(id)); }; // ## Event Modules /** * ### storeEventModule(id, data) * Store a string representation of an event module in the DB. * @param {String} id the unique identifier of the module * @param {String} data the string representation */ exports.storeEventModule = function(id, data) { if(db) { db.sadd('event_modules', id, replyHandler('storing event module key ' + id)); db.set('event_module_' + id, data, replyHandler('storing event module ' + id)); } }; /** * ### getEventModule(id, cb) * Query the DB for an event module. * @param {String} id the module id * @param {function} cb the cb to receive the answer (err, obj) */ exports.getEventModule = function(id, cb) { if(cb && db) db.get('event_module_' + id, cb); }; /** * ### getEventModules(cb) * Fetch all event modules. * @param {function} cb the cb that receives the arguments (err, obj) */ exports.getEventModules = function(cb) { getSetRecords('event_modules', exports.getEventModule, cb); }; /** * ### storeEventModuleAuth(id, data) * Store a string representation of he authentication parameters for an event module. * @param {String} id the unique identifier of the module * @param {String} data the string representation */ exports.storeEventModuleAuth = function(id, data) { if(data && db) { db.sadd('event_modules_auth', id, replyHandler('storing event module auth key ' + id)); db.set('event_module_' + id +'_auth', encrypt(data), replyHandler('storing event module auth ' + id)); } }; // @method getEventModuleAuth(id, cb) // Query the DB for an event module authentication token. // @param {String} id the module id // @param {function} cb the cb to receive the answer (err, obj) exports.getEventModuleAuth = function(id, cb) { if(cb) db.get('event_module_' + id +'_auth', function(id) { return function(err, txt) { cb(err, decrypt(txt, 'event_module_' + id + '_auth')); }; }(id)); }; // ## Rules // @method storeRule(id, data) // Store a string representation of a rule in the DB. // @param {String} id the unique identifier of the rule // @param {String} data the string representation exports.storeRule = function(id, data) { if(db) { db.sadd('rules', id, replyHandler('storing rule key ' + id)); db.set('rule_' + id, data, replyHandler('storing rule ' + id)); } }; // @method getRule(id, cb) // Query the DB for a rule. // @param {String} id the rule id // @param {function} cb the cb to receive the answer (err, obj) exports.getRule = function(id, cb) { if(db) db.get('rule_' + id, cb); }; // @method getRules(cb) // Fetch all rules from the database. // @param {function} cb exports.getRules = function(cb) { getSetRecords('rules', exports.getRule, cb); }; /** * * @param {Object} objUser * @param {function} cb */ exports.storeUser = function(objUser, cb) { if(db && objUser && objUser.username && objUser.password) { db.sadd('users', objUser.username, replyHandler('storing user key ' + objUser.username)); objUser.password = encrypt(objUser.password); db.set('user:' + objUser.username, objUser, replyHandler('storing user properties ' + objUser.username)); } }; /** * Checks the credentials and on success returns the user object. * @param {Object} objUser * @param {function} cb */ exports.loginUser = function(username, password, cb) { if(typeof cb !== 'function') return; if(db) db.get('user:' + username, function(p) { return function(err, obj) { if(err) cb(err); else if(encrypt(obj.password) === p) cb(null, obj); else cb(new Error('Wrong credentials!')); }; }(password)); else cb(new Error('No database link available!')); };