webapi-eca/coffee/db_interface.coffee

352 lines
No EOL
10 KiB
CoffeeScript

###
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).
>
###
'use strict'
### Grab all required modules ###
redis = require 'redis'
crypto = require 'crypto'
log = require './logging'
crypto_key = null
db = null
###
##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) ->
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) ->
err.addInfo = 'message from DB'
log.error 'DB', err
###
Checks whether the db is connected and calls the callback function if successful,
or an error after ten attempts within five seconds.
@public isConnected( *cb* )
@param {function} cb
###
#}TODO check if timeout works with func in func
exports.isConnected = (cb) ->
if db.connected then cb()
else
numAttempts = 0
fCheckConnection = ->
if db.connected
log.print 'DB', 'Successfully connected to DB!'
cb()
else if numAttempts++ < 10
setTimeout fCheckConnection, 500
else
e = new Error 'Connection to DB failed!'
log.error 'DB', e
cb e
setTimeout fCheckConnection, 500
###
Encrypts a string using the crypto key from the config file, based on aes-256-cbc.
@private encrypt( *plainText* )
@param {String} plainText
###
encrypt = (plainText) ->
if !plainText? then return null
try
enciph = crypto.createCipher 'aes-256-cbc', crypto_key
et = enciph.update plainText, 'utf8', 'base64'
et + enciph.final 'base64'
catch err
err.addInfo = 'during encryption'
log.error 'DB', err
null
###
Decrypts an encrypted string and hands it back on success or null.
@private decrypt( *crypticText* )
@param {String} crypticText
###
decrypt = (crypticText) ->
if !crypticText? then return null;
try
deciph = crypto.createDecipher 'aes-256-cbc', crypto_key
dt = deciph.update crypticText, 'base64', 'utf8'
dt + deciph.final 'utf8'
catch err
err.addInfo = 'during decryption'
log.error 'DB', err
null
###
Abstracts logging for simple action replies from the DB.
@private replyHandler( *action* )
@param {String} action
###
replyHandler = (action) ->
(err, reply) ->
if err
err.addInfo = 'during "' + action + '"'
log.error 'DB', err
else
log.print 'DB', action + ': ' + reply
###
Fetches all linked data set keys from a linking set, fetches the single data objects
via the provided function and returns the results to the callback function.
@private getSetRecords( *set, fSingle, cb* )
@param {String} set the set name how it is stored in the DB
@param {function} fSingle a function to retrieve a single data element per set entry
@param {function} cb the callback function that receives all the retrieved data or an error
###
getSetRecords = (set, funcSingle, cb) ->
db?.smembers set, (err, arrReply) ->
if err
err.addInfo = 'fetching ' + set
log.error 'DB', err
else if arrReply.length == 0
cb()
else
semaphore = arrReply.length
objReplies = {}
# } TODO What if the DB needs longer than two seconds to respond?...
setTimeout ->
if semaphore > 0
cb new Error('Timeout fetching ' + set)
, 2000
fCallback = (prop) ->
(err, data) ->
if err
err.addInfo = 'fetching single element: ' + prop
log.error 'DB', err
else
objReplies[prop] = data
if --semaphore == 0
cb null, objReplies
fSingle reply, fCallback(reply) for reply in arrReply
###
@Function shutDown()
Shuts down the db link.
###
###exports.shutDown = function() { if(db) db.quit(); };
###
###
## Action Modules
@Function 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));
}
};
###
###
@Function 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);
};
###
###
@Function 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);
};
###
###
@Function 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));
}
};
###
###
@Function 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
@Function 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));
}
};
###
###
@Function 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);
};
###
###
@Function 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);
};
###
###
@Function 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));
}
};
###
###
@Function 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
@Function 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));
}
};
###
###
@Function 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);
};
###
###
@Function getRules(cb)
Fetch all rules from the database.
@param {function} cb
###
###exports.getRules = function(cb) {
getSetRecords('rules', exports.getRule, cb);
};
###
###
@Function storeUser
@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!'));
};
###