From ba106fda38d9c34faa174670ff25607f538a4edb Mon Sep 17 00:00:00 2001
From: Felipe Cecagno <fcecagno@gmail.com>
Date: Tue, 26 Mar 2019 17:03:05 -0300
Subject: [PATCH] start refactoring bbb-webhooks to use a proper config library

---
 bbb-webhooks/application.js                   |  19 +--
 bbb-webhooks/callback_emitter.js              |  12 +-
 bbb-webhooks/config.js                        | 115 ------------------
 bbb-webhooks/config/bbb-webhooks.logrotate    |   8 --
 .../config/custom-environment-variables.yml   |  11 ++
 bbb-webhooks/config/default.example.yml       |  67 ++++++++++
 bbb-webhooks/config/webhooks.nginx            |   9 --
 bbb-webhooks/config_local.js.example          |  30 -----
 bbb-webhooks/extra/events.js                  |   8 +-
 bbb-webhooks/hook.js                          |  50 ++++----
 bbb-webhooks/id_mapping.js                    |  22 ++--
 bbb-webhooks/messageMapping.js                |   1 -
 bbb-webhooks/package-lock.json                |  48 +++++++-
 bbb-webhooks/package.json                     |   6 +-
 bbb-webhooks/responses.js                     |  46 +++++++
 bbb-webhooks/test/test.js                     |  40 +++---
 bbb-webhooks/userMapping.js                   |  18 +--
 bbb-webhooks/utils.js                         |   4 +-
 bbb-webhooks/web_hooks.js                     |  10 +-
 bbb-webhooks/web_server.js                    |  27 ++--
 20 files changed, 281 insertions(+), 270 deletions(-)
 delete mode 100644 bbb-webhooks/config.js
 delete mode 100644 bbb-webhooks/config/bbb-webhooks.logrotate
 create mode 100644 bbb-webhooks/config/custom-environment-variables.yml
 create mode 100644 bbb-webhooks/config/default.example.yml
 delete mode 100644 bbb-webhooks/config/webhooks.nginx
 delete mode 100644 bbb-webhooks/config_local.js.example
 create mode 100644 bbb-webhooks/responses.js

diff --git a/bbb-webhooks/application.js b/bbb-webhooks/application.js
index b6e9985b12..e2c0c5424f 100644
--- a/bbb-webhooks/application.js
+++ b/bbb-webhooks/application.js
@@ -1,4 +1,4 @@
-const config = require("./config.js");
+const config = require('config');
 const Hook = require("./hook.js");
 const IDMapping = require("./id_mapping.js");
 const WebHooks = require("./web_hooks.js");
@@ -11,14 +11,8 @@ const async = require("async");
 // process to perform the callback calls.
 // TODO: add port (-p) and log level (-l) to the command line args
 module.exports = class Application {
-
+  
   constructor() {
-    const options = {
-      host : process.env.REDIS_HOST || config.redis.host,
-      port : process.env.REDIS_PORT || config.redis.port
-    };
-    config.redis.pubSubClient = redis.createClient(options);
-    config.redis.client = redis.createClient(options);
     this.webHooks = new WebHooks();
     this.webServer = new WebServer();
   }
@@ -28,7 +22,7 @@ module.exports = class Application {
       UserMapping.initialize(() => {
         IDMapping.initialize(()=> {
           async.parallel([
-            (callback) => { this.webServer.start(config.server.port, callback) },
+            (callback) => { this.webServer.start(config.get("server.port"), callback) },
             (callback) => { this.webServer.createPermanents(callback) },
             (callback) => { this.webHooks.start(callback) }
           ], (err,results) => {
@@ -39,4 +33,11 @@ module.exports = class Application {
       });
     });
   }
+  
+  static redisClient() {
+    if (!Application._redisClient) {
+      Application._redisClient = redis.createClient( { host: config.get("redis.host"), port: config.get("redis.port") } );
+    }
+    return Application._redisClient;
+  }
 };
diff --git a/bbb-webhooks/callback_emitter.js b/bbb-webhooks/callback_emitter.js
index 6ec7d55363..d1502ab374 100644
--- a/bbb-webhooks/callback_emitter.js
+++ b/bbb-webhooks/callback_emitter.js
@@ -3,7 +3,7 @@ const request = require("request");
 const url = require('url');
 const EventEmitter = require('events').EventEmitter;
 
-const config = require("./config.js");
+const config = require('config');
 const Logger = require("./logger.js");
 const Utils = require("./utils.js");
 
@@ -38,7 +38,7 @@ module.exports = class CallbackEmitter extends EventEmitter {
           this.emit("failure", error);
 
           // get the next interval we have to wait and schedule a new try
-          const interval = config.hooks.retryIntervals[this.nextInterval];
+          const interval = config.get("hooks.retryIntervals")[this.nextInterval];
           if (interval != null) {
             Logger.warn(`[Emitter] trying the callback again in ${interval/1000.0} secs`);
             this.nextInterval++;
@@ -46,7 +46,7 @@ module.exports = class CallbackEmitter extends EventEmitter {
 
           // no intervals anymore, time to give up
           } else {
-            this.nextInterval = config.hooks.permanentIntervalReset; // Reset interval to permanent hooks
+            this.nextInterval = config.get("hooks.permanentIntervalReset"); // Reset interval to permanent hooks
             if(this.permanent){
               this._scheduleNext(this.nextInterval);
             }
@@ -62,9 +62,9 @@ module.exports = class CallbackEmitter extends EventEmitter {
 
   _emitMessage(callback) {
     let data,requestOptions;
-    const serverDomain = process.env.SERVER_DOMAIN || config.bbb.serverDomain;
-    const sharedSecret = process.env.SHARED_SECRET || config.bbb.sharedSecret;
-    const bearerAuth = process.env.BEARER_AUTH || config.bbb.auth2_0;
+    const serverDomain = config.get("bbb.serverDomain");
+    const sharedSecret = config.get("bbb.sharedSecret");
+    const bearerAuth = config.get("bbb.auth2_0");
 
     // data to be sent
     // note: keep keys in alphabetical order
diff --git a/bbb-webhooks/config.js b/bbb-webhooks/config.js
deleted file mode 100644
index ca9be42b53..0000000000
--- a/bbb-webhooks/config.js
+++ /dev/null
@@ -1,115 +0,0 @@
-// Global configuration file
-
-// load the local configs
-const config = require("./config_local.js");
-
-// BigBlueButton configs
-if (config.bbb == null) { config.bbb = {}; }
-if (!config.bbb.sharedSecret) { config.bbb.sharedSecret = "sharedSecret"; }
-if (!config.bbb.apiPath) { config.bbb.apiPath = "/bigbluebutton/api"; }
-// Whether to use Auth2.0 or not, Auth2.0 sends the sharedSecret whithin an Authorization header as a bearer
-// and data as JSON
-if (config.bbb.auth2_0 == null) { config.bbb.auth2_0 = false; }
-
-// Web server configs
-if (!config.server) { config.server = {}; }
-if (config.server.port == null) { config.server.port = 3005; }
-
-// Web hooks configs
-if (!config.hooks) { config.hooks = {}; }
-if (!config.hooks.channels) {
-  config.hooks.channels = {
-    mainChannel: 'from-akka-apps-redis-channel',
-    rapChannel: 'from-bbb-web-redis-channel',
-    chatChannel: 'from-akka-apps-chat-redis-channel',
-    compMeetingChannel: 'bigbluebutton:from-bbb-apps:meeting',
-    compUserChannel: 'bigbluebutton:from-bbb-apps:users',
-    compChatChannel: 'bigbluebutton:from-bbb-apps:chat',
-    compRapChannel: 'bigbluebutton:from-rap'
-  }
- }
-// IP where permanent hook will post data (more than 1 URL means more than 1 permanent hook)
-if (!config.hooks.permanentURLs) { config.hooks.permanentURLs = []; }
-// How many messages will be enqueued to be processed at the same time
-if (config.hooks.queueSize  == null) { config.hooks.queueSize = 10000; }
-// Allow permanent hooks to receive raw message, which is the message straight from BBB
-if (config.hooks.getRaw  == null) { config.hooks.getRaw = true; }
-// If set to higher than 1, will send events on the format:
-// "event=[{event1},{event2}],timestamp=000" or "[{event1},{event2}]" (based on using auth2_0 or not)
-// when there are more than 1 event on the queue at the moment of processing the queue.
-if (config.hooks.multiEvent  == null) { config.hooks.multiEvent = 1; }
-
-// Retry intervals for failed attempts for perform callback calls.
-// In ms. Totals to around 5min.
-config.hooks.retryIntervals = [
-  100, 500, 1000, 2000, 4000, 8000, 10000, 30000, 60000, 60000, 60000, 60000
-];
-
-// Reset permanent interval when exceeding maximum attemps
-config.hooks.permanentIntervalReset = 8;
-
-// Mappings of internal to external meeting IDs
-config.mappings = {};
-config.mappings.cleanupInterval = 10000; // 10 secs, in ms
-config.mappings.timeout = 1000*60*60*24; // 24 hours, in ms
-
-// Redis
-config.redis = {};
-config.redis.host = '127.0.0.1';
-config.redis.port = 6379;
-config.redis.keys = {};
-config.redis.keys.hook = id => `bigbluebutton:webhooks:hook:${id}`;
-config.redis.keys.hooks = "bigbluebutton:webhooks:hooks";
-config.redis.keys.mappings = "bigbluebutton:webhooks:mappings";
-config.redis.keys.mapping = id => `bigbluebutton:webhooks:mapping:${id}`;
-config.redis.keys.events = id => `bigbluebutton:webhooks:events:${id}`;
-config.redis.keys.userMaps = "bigbluebutton:webhooks:userMaps";
-config.redis.keys.userMap = id => `bigbluebutton:webhooks:userMap:${id}`;
-
-config.api = {};
-config.api.responses = {};
-config.api.responses.failure = (key, msg) =>
-  `<response> \
-<returncode>FAILED</returncode> \
-<messageKey>${key}</messageKey> \
-<message>${msg}</message> \
-</response>`
-;
-config.api.responses.checksumError =
-  config.api.responses.failure("checksumError", "You did not pass the checksum security check.");
-
-config.api.responses.createSuccess = (id, permanent, getRaw) =>
-  `<response> \
-<returncode>SUCCESS</returncode> \
-<hookID>${id}</hookID> \
-<permanentHook>${permanent}</permanentHook> \
-<rawData>${getRaw}</rawData> \
-</response>`
-;
-config.api.responses.createFailure =
-  config.api.responses.failure("createHookError", "An error happened while creating your hook. Check the logs.");
-config.api.responses.createDuplicated = id =>
-  `<response> \
-<returncode>SUCCESS</returncode> \
-<hookID>${id}</hookID> \
-<messageKey>duplicateWarning</messageKey> \
-<message>There is already a hook for this callback URL.</message> \
-</response>`
-;
-
-config.api.responses.destroySuccess =
-  `<response> \
-<returncode>SUCCESS</returncode> \
-<removed>true</removed> \
-</response>`;
-config.api.responses.destroyFailure =
-  config.api.responses.failure("destroyHookError", "An error happened while removing your hook. Check the logs.");
-config.api.responses.destroyNoHook =
-  config.api.responses.failure("destroyMissingHook", "The hook informed was not found.");
-
-config.api.responses.missingParamCallbackURL =
-  config.api.responses.failure("missingParamCallbackURL", "You must specify a callbackURL in the parameters.");
-config.api.responses.missingParamHookID =
-  config.api.responses.failure("missingParamHookID", "You must specify a hookID in the parameters.");
-
-module.exports = config;
diff --git a/bbb-webhooks/config/bbb-webhooks.logrotate b/bbb-webhooks/config/bbb-webhooks.logrotate
deleted file mode 100644
index c01f43723b..0000000000
--- a/bbb-webhooks/config/bbb-webhooks.logrotate
+++ /dev/null
@@ -1,8 +0,0 @@
-/usr/local/bigbluebutton/bbb-webhooks/log/*.log {
-  size 300M
-  copytruncate
-  rotate 30
-  compress
-  missingok
-  notifempty
-}
diff --git a/bbb-webhooks/config/custom-environment-variables.yml b/bbb-webhooks/config/custom-environment-variables.yml
new file mode 100644
index 0000000000..69733ad4a7
--- /dev/null
+++ b/bbb-webhooks/config/custom-environment-variables.yml
@@ -0,0 +1,11 @@
+bbb:
+  serverDomain: SERVER_DOMAIN
+  sharedSecret: SHARED_SECRET
+  auth2_0: BEARER_AUTH
+hooks:
+  permanentURLs:
+    __name: PERMANENT_HOOKS
+    __format: json
+redis:
+  host: REDIS_HOST
+  port: REDIS_PORT
diff --git a/bbb-webhooks/config/default.example.yml b/bbb-webhooks/config/default.example.yml
new file mode 100644
index 0000000000..037f3b9ad6
--- /dev/null
+++ b/bbb-webhooks/config/default.example.yml
@@ -0,0 +1,67 @@
+# Shared secret of your BigBlueButton server.
+bbb:
+  serverDomain: myserver.com
+  sharedSecret: mysharedsecret
+  # Whether to use Auth2.0 or not, Auth2.0 sends the sharedSecret whithin an Authorization header as a bearer
+  auth2_0: false
+  apiPath: /bigbluebutton/api
+
+# The port in which the API server will run.
+server:
+  port: 3005
+
+# Web hooks configs
+hooks:
+  channels:
+    - from-akka-apps-redis-channel
+    - from-bbb-web-redis-channel
+    - from-akka-apps-chat-redis-channel
+    - bigbluebutton:from-bbb-apps:meeting
+    - bigbluebutton:from-bbb-apps:users
+    - bigbluebutton:from-bbb-apps:chat
+    - bigbluebutton:from-rap
+  # IP where permanent hook will post data (more than 1 URL means more than 1 permanent hook)
+  permanentURLs: []
+  # How many messages will be enqueued to be processed at the same time
+  queueSize: 10000
+  # Allow permanent hooks to receive raw message, which is the message straight from BBB
+  getRaw: true
+  # If set to higher than 1, will send events on the format:
+  # "event=[{event1},{event2}],timestamp=000" or "[{event1},{event2}]" (based on using auth2_0 or not)
+  # when there are more than 1 event on the queue at the moment of processing the queue.
+  multiEvent: 1
+  # Retry intervals for failed attempts for perform callback calls.
+  # In ms. Totals to around 5min.
+  retryIntervals:
+    - 100
+    - 500
+    - 1000
+    - 2000
+    - 4000
+    - 8000
+    - 10000
+    - 30000
+    - 60000
+    - 60000
+    - 60000
+    - 60000
+  # Reset permanent interval when exceeding maximum attemps
+  permanentIntervalReset: 8
+
+# Mappings of internal to external meeting IDs
+mappings:
+  cleanupInterval: 10000 # 10 secs, in ms
+  timeout: 86400000 # 24 hours, in ms
+
+# Redis
+redis:
+  host: 127.0.0.1
+  port: 6379
+  keys:
+    hookPrefix: bigbluebutton:webhooks:hook
+    hooks: bigbluebutton:webhooks:hooks
+    mappings: bigbluebutton:webhooks:mappings
+    mappingPrefix: bigbluebutton:webhooks:mapping
+    eventsPrefix: bigbluebutton:webhooks:events
+    userMaps: bigbluebutton:webhooks:userMaps
+    userMapPrefix: bigbluebutton:webhooks:userMap
\ No newline at end of file
diff --git a/bbb-webhooks/config/webhooks.nginx b/bbb-webhooks/config/webhooks.nginx
deleted file mode 100644
index 22ec6e4e97..0000000000
--- a/bbb-webhooks/config/webhooks.nginx
+++ /dev/null
@@ -1,9 +0,0 @@
-# Pass to the webhooks app all requests made to the webhooks API.
-location /bigbluebutton/api/hooks {
-    proxy_pass         http://127.0.0.1:3005;
-    proxy_redirect     default;
-    proxy_set_header   X-Forwarded-For   $proxy_add_x_forwarded_for;
-    proxy_set_header   X-Real-IP         $remote_addr;
-    proxy_set_header   Host              $http_host;
-    proxy_set_header   X-NginX-Proxy     true;
-}
diff --git a/bbb-webhooks/config_local.js.example b/bbb-webhooks/config_local.js.example
deleted file mode 100644
index 0bebd13924..0000000000
--- a/bbb-webhooks/config_local.js.example
+++ /dev/null
@@ -1,30 +0,0 @@
-// Local configuration file
-
-const config = {};
-
-// Shared secret of your BigBlueButton server.
-config.bbb = {};
-config.bbb.serverDomain = "myserver.com";
-config.bbb.sharedSecret = "mysharedsecret";
-// Whether to use Auth2.0 or not, Auth2.0 sends the sharedSecret whithin an Authorization header as a bearer
-config.bbb.auth2_0 = false;
-
-// The port in which the API server will run.
-config.server = {};
-config.server.port = 3005;
-
-// Callbacks will be triggered for all the events in this list and only for these events.
-//config.hooks = {};
-//config.hooks.channels = {
-//  mainChannel: 'from-akka-apps-redis-channel',
-//  rapChannel: 'bigbluebutton:from-rap',
-//  chatChannel: 'from-akka-apps-chat-redis-channel'
-//}
-
-// IP where permanent hook will post data (more than 1 URL means more than 1 permanent hook)
-//config.hooks.permanentURLs = [ { url: "request.catcher.url", getRaw: false }, { url: "another.request.catcher.url", getRaw: true } ]
-
-// Allow global hook to receive all events with raw data
-//config.hooks.getRaw = false;
-
-module.exports = config;
diff --git a/bbb-webhooks/extra/events.js b/bbb-webhooks/extra/events.js
index c527879ac9..a0997b4b75 100644
--- a/bbb-webhooks/extra/events.js
+++ b/bbb-webhooks/extra/events.js
@@ -3,10 +3,10 @@
 // events, but only the first time they happen.
 
 const redis = require("redis");
-const config = require('../config.js');
+const config = require('config');
 var target_meeting = null;
 var events_printed = [];
-var subscriber = redis.createClient(process.env.REDIS_PORT || config.redis.port, process.env.REDIS_HOST || config.redis.host);
+var subscriber = redis.createClient(config.get(redis.port), config.get(redis.host));
 
 subscriber.on("psubscribe", function(channel, count) {
   console.log("subscribed to " + channel);
@@ -30,8 +30,8 @@ subscriber.on("pmessage", function(pattern, channel, message) {
   }
 });
 
-for (let k in config.hooks.channels) {
-  const channel = config.hooks.channels[k];
+for (i = 0; i < config.get(hooks.channels); ++i) {
+  const channel = config.get(hooks.channels)[i];
   subscriber.psubscribe(channel);
 }
 
diff --git a/bbb-webhooks/hook.js b/bbb-webhooks/hook.js
index ce2a53fc19..f15537bb13 100644
--- a/bbb-webhooks/hook.js
+++ b/bbb-webhooks/hook.js
@@ -2,7 +2,7 @@ const _ = require("lodash");
 const async = require("async");
 const redis = require("redis");
 
-const config = require("./config.js");
+const config = require('config');
 const CallbackEmitter = require("./callback_emitter.js");
 const IDMapping = require("./id_mapping.js");
 const Logger = require("./logger.js");
@@ -36,15 +36,15 @@ module.exports = class Hook {
     this.externalMeetingID = null;
     this.queue = [];
     this.emitter = null;
-    this.redisClient = config.redis.client;
+    this.redisClient = Application.redisClient;
     this.permanent = false;
     this.getRaw = false;
   }
 
   save(callback) {
-    this.redisClient.hmset(config.redis.keys.hook(this.id), this.toRedis(), (error, reply) => {
+    this.redisClient.hmset(config.get("redis.keys.hookPrefix") + ":" + this.id, this.toRedis(), (error, reply) => {
       if (error != null) { Logger.error("[Hook] error saving hook to redis:", error, reply); }
-        this.redisClient.sadd(config.redis.keys.hooks, this.id, (error, reply) => {
+        this.redisClient.sadd(config.get("redis.keys.hooks"), this.id, (error, reply) => {
         if (error != null) { Logger.error("[Hook] error saving hookID to the list of hooks:", error, reply); }
 
         db[this.id] = this;
@@ -54,9 +54,9 @@ module.exports = class Hook {
   }
 
   destroy(callback) {
-    this.redisClient.srem(config.redis.keys.hooks, this.id, (error, reply) => {
+    this.redisClient.srem(config.get("redis.keys.hooks"), this.id, (error, reply) => {
       if (error != null) { Logger.error("[Hook] error removing hookID from the list of hooks:", error, reply); }
-        this.redisClient.del(config.redis.keys.hook(this.id), error => {
+        this.redisClient.del(config.get("redis.keys.hookPrefix") + ":" + this.id, error => {
         if (error != null) { Logger.error("[Hook] error removing hook from redis:", error); }
 
         if (db[this.id]) {
@@ -82,12 +82,12 @@ module.exports = class Hook {
   // Puts a new message in the queue. Will also trigger a processing in the queue so this
   // message might be processed instantly.
   enqueue(message) {
-    this.redisClient.llen(config.redis.keys.events(this.id), (error, reply) => {
+    this.redisClient.llen(config.get("redis.keys.eventsPrefix") + ":" + this.id, (error, reply) => {
       const length = reply;
-      if (length < config.hooks.queueSize && this.queue.length < config.hooks.queueSize) {
+      if (length < config.get("hooks.queueSize") && this.queue.length < config.get("hooks.queueSize")) {
         Logger.info(`[Hook] ${this.callbackURL} enqueueing message:`, JSON.stringify(message));
         // Add message to redis queue
-        this.redisClient.rpush(config.redis.keys.events(this.id), JSON.stringify(message), (error,reply) => {
+        this.redisClient.rpush(config.get("redis.keys.eventsPrefix") + ":" + this.id, JSON.stringify(message), (error,reply) => {
           if (error != null) { Logger.error("[Hook] error pushing event to redis queue:", JSON.stringify(message), error); }
         });
         this.queue.push(JSON.stringify(message));
@@ -124,8 +124,8 @@ module.exports = class Hook {
   // Gets the first message in the queue and start an emitter to send it. Will only do it
   // if there is no emitter running already and if there is a message in the queue.
   _processQueue() {
-    // Will try to send up to a defined number of messages together if they're enqueued (defined on config.hooks.multiEvent)
-    const lengthIn = this.queue.length > config.hooks.multiEvent ? config.hooks.multiEvent : this.queue.length;
+    // Will try to send up to a defined number of messages together if they're enqueued (defined on config.get("hooks.multiEvent"))
+    const lengthIn = this.queue.length > config.get("hooks.multiEvent") ? config.get("hooks.multiEvent") : this.queue.length;
     let num = lengthIn + 1;
     // Concat messages
     let message = this.queue.slice(0,lengthIn);
@@ -140,7 +140,7 @@ module.exports = class Hook {
       delete this.emitter;
       while ((num -= 1)) {
         // Remove the sent message from redis
-        this.redisClient.lpop(config.redis.keys.events(this.id), (error, reply) => {
+        this.redisClient.lpop(config.get("redis.keys.eventsPrefix") + ":" + this.id, (error, reply) => {
           if (error != null) { return Logger.error("[Hook] error removing event from redis queue:", error); }
         });
         this.queue.shift();
@@ -168,21 +168,21 @@ module.exports = class Hook {
       hook.callbackURL = callbackURL;
       hook.externalMeetingID = meetingID;
       hook.getRaw = getRaw;
-      hook.permanent = config.hooks.permanentURLs.some( obj => {
+      hook.permanent = config.get("hooks.permanentURLs").some( obj => {
         return obj.url === callbackURL
       });
       if (hook.permanent) {
-        hook.id = config.hooks.permanentURLs.map(obj => obj.url).indexOf(callbackURL) + 1;
-        nextID = config.hooks.permanentURLs.length + 1;
+        hook.id = config.get("hooks.permanentURLs").map(obj => obj.url).indexOf(callbackURL) + 1;
+        nextID = config.get("hooks.permanentURLs").length + 1;
       } else {
         hook.id = nextID++;
       }
       // Sync permanent queue
       if (hook.permanent) {
-        hook.redisClient.llen(config.redis.keys.events(hook.id), (error, len) => {
+        hook.redisClient.llen(config.get("redis.keys.eventsPrefix") + ":" + hook.id, (error, len) => {
           if (len > 0) {
             const length = len;
-            hook.redisClient.lrange(config.redis.keys.events(hook.id), 0, len, (error, elements) => {
+            hook.redisClient.lrange(config.get("redis.keys.eventsPrefix") + ":" + hook.id, 0, len, (error, elements) => {
               elements.forEach(element => {
                 hook.queue.push(element);
               });
@@ -266,12 +266,12 @@ module.exports = class Hook {
   // Gets all hooks from redis to populate the local database.
   // Calls `callback()` when done.
   static resync(callback) {
-    let client = config.redis.client;
+    let client = Application.redisClient;
     // Remove previous permanent hooks
-    for (let hk = 1; hk <= config.hooks.permanentURLs.length; hk++) {
-      client.srem(config.redis.keys.hooks, hk, (error, reply) => {
+    for (let hk = 1; hk <= config.get("hooks.permanentURLs").length; hk++) {
+      client.srem(config.get("redis.keys.hooks"), hk, (error, reply) => {
         if (error != null) { Logger.error("[Hook] error removing previous permanent hook from list:", error); }
-        client.del(config.redis.keys.hook(hk), error => {
+        client.del(config.get("redis.keys.hookPrefix") + ":" + hk, error => {
           if (error != null) { Logger.error("[Hook] error removing previous permanent hook from redis:", error); }
         });
       });
@@ -279,11 +279,11 @@ module.exports = class Hook {
 
     let tasks = [];
 
-    client.smembers(config.redis.keys.hooks, (error, hooks) => {
+    client.smembers(config.get("redis.keys.hooks"), (error, hooks) => {
       if (error != null) { Logger.error("[Hook] error getting list of hooks from redis:", error); }
       hooks.forEach(id => {
         tasks.push(done => {
-          client.hgetall(config.redis.keys.hook(id), function(error, hookData) {
+          client.hgetall(config.get("redis.keys.hookPrefix") + ":" + id, function(error, hookData) {
             if (error != null) { Logger.error("[Hook] error getting information for a hook from redis:", error); }
 
             if (hookData != null) {
@@ -291,9 +291,9 @@ module.exports = class Hook {
               let hook = new Hook();
               hook.fromRedis(hookData);
               // sync events queue
-              client.llen(config.redis.keys.events(hook.id), (error, len) => {
+              client.llen(config.get("redis.keys.eventsPrefix") + ":" + hook.id, (error, len) => {
                 length = len;
-                client.lrange(config.redis.keys.events(hook.id), 0, len, (error, elements) => {
+                client.lrange(config.get("redis.keys.eventsPrefix") + ":" + hook.id, 0, len, (error, elements) => {
                   elements.forEach(element => {
                     hook.queue.push(element);
                   });
diff --git a/bbb-webhooks/id_mapping.js b/bbb-webhooks/id_mapping.js
index f16d5fc18c..f9285d3189 100644
--- a/bbb-webhooks/id_mapping.js
+++ b/bbb-webhooks/id_mapping.js
@@ -2,7 +2,7 @@ const _ = require("lodash");
 const async = require("async");
 const redis = require("redis");
 
-const config = require("./config.js");
+const config = require('config');
 const Logger = require("./logger.js");
 const UserMapping = require("./userMapping.js");
 
@@ -33,13 +33,13 @@ module.exports = class IDMapping {
     this.externalMeetingID = null;
     this.internalMeetingID = null;
     this.lastActivity = null;
-    this.redisClient = config.redis.client;
+    this.redisClient = Application.redisClient;
   }
 
   save(callback) {
-    this.redisClient.hmset(config.redis.keys.mapping(this.id), this.toRedis(), (error, reply) => {
+    this.redisClient.hmset(config.get("redis.keys.mappingPrefix") + ":" + this.id, this.toRedis(), (error, reply) => {
       if (error != null) { Logger.error("[IDMapping] error saving mapping to redis:", error, reply); }
-      this.redisClient.sadd(config.redis.keys.mappings, this.id, (error, reply) => {
+      this.redisClient.sadd(config.get("redis.keys.mappings"), this.id, (error, reply) => {
         if (error != null) { Logger.error("[IDMapping] error saving mapping ID to the list of mappings:", error, reply); }
 
         db[this.internalMeetingID] = this;
@@ -49,9 +49,9 @@ module.exports = class IDMapping {
   }
 
   destroy(callback) {
-    this.redisClient.srem(config.redis.keys.mappings, this.id, (error, reply) => {
+    this.redisClient.srem(config.get("redis.keys.mappings"), this.id, (error, reply) => {
       if (error != null) { Logger.error("[IDMapping] error removing mapping ID from the list of mappings:", error, reply); }
-      this.redisClient.del(config.redis.keys.mapping(this.id), error => {
+      this.redisClient.del(config.get("redis.keys.mappingPrefix") + ":" + this.id, error => {
         if (error != null) { Logger.error("[IDMapping] error removing mapping from redis:", error); }
 
         if (db[this.internalMeetingID]) {
@@ -161,7 +161,7 @@ module.exports = class IDMapping {
   static cleanup() {
     const now = new Date().getTime();
     const all = IDMapping.allSync();
-    const toRemove = _.filter(all, mapping => mapping.lastActivity < (now - config.mappings.timeout));
+    const toRemove = _.filter(all, mapping => mapping.lastActivity < (now - config.get("mappings.timeout")));
     if (!_.isEmpty(toRemove)) {
       Logger.info("[IDMapping] expiring the mappings:", _.map(toRemove, map => map.print()));
       toRemove.forEach(mapping => {
@@ -174,21 +174,21 @@ module.exports = class IDMapping {
   // Initializes global methods for this model.
   static initialize(callback) {
     IDMapping.resync(callback);
-    IDMapping.cleanupInterval = setInterval(IDMapping.cleanup, config.mappings.cleanupInterval);
+    IDMapping.cleanupInterval = setInterval(IDMapping.cleanup, config.get("mappings.cleanupInterval"));
   }
 
   // Gets all mappings from redis to populate the local database.
   // Calls `callback()` when done.
   static resync(callback) {
-    let client = config.redis.client;
+    let client = Application.redisClient;
     let tasks = [];
 
-    return client.smembers(config.redis.keys.mappings, (error, mappings) => {
+    return client.smembers(config.get("redis.keys.mappings"), (error, mappings) => {
       if (error != null) { Logger.error("[IDMapping] error getting list of mappings from redis:", error); }
 
       mappings.forEach(id => {
         tasks.push(done => {
-          client.hgetall(config.redis.keys.mapping(id), function(error, mappingData) {
+          client.hgetall(config.get("redis.keys.mappingPrefix") + ":" + id, function(error, mappingData) {
             if (error != null) { Logger.error("[IDMapping] error getting information for a mapping from redis:", error); }
 
             if (mappingData != null) {
diff --git a/bbb-webhooks/messageMapping.js b/bbb-webhooks/messageMapping.js
index 2a958ed488..a2c72684b4 100644
--- a/bbb-webhooks/messageMapping.js
+++ b/bbb-webhooks/messageMapping.js
@@ -1,4 +1,3 @@
-const config = require("./config.js");
 const Logger = require("./logger.js");
 const IDMapping = require("./id_mapping.js");
 const UserMapping = require("./userMapping.js");
diff --git a/bbb-webhooks/package-lock.json b/bbb-webhooks/package-lock.json
index a42e64503e..c81cb86eab 100644
--- a/bbb-webhooks/package-lock.json
+++ b/bbb-webhooks/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "bbb-callbacks",
-  "version": "0.9.0",
+  "version": "2.2.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -50,6 +50,14 @@
         "uri-js": "^4.2.2"
       }
     },
+    "argparse": {
+      "version": "1.0.10",
+      "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+      "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+      "requires": {
+        "sprintf-js": "~1.0.2"
+      }
+    },
     "array-flatten": {
       "version": "1.1.1",
       "resolved": "http://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -377,6 +385,15 @@
       "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
       "dev": true
     },
+    "config": {
+      "version": "1.30.0",
+      "resolved": "https://registry.npmjs.org/config/-/config-1.30.0.tgz",
+      "integrity": "sha1-HWCp81NIoTwXV5jThOgaWhbDum4=",
+      "requires": {
+        "json5": "0.4.0",
+        "os-homedir": "1.0.2"
+      }
+    },
     "content-disposition": {
       "version": "0.5.2",
       "resolved": "http://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
@@ -520,6 +537,11 @@
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
       "dev": true
     },
+    "esprima": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+      "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
+    },
     "etag": {
       "version": "1.8.1",
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -767,6 +789,15 @@
       "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
       "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
     },
+    "js-yaml": {
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.0.tgz",
+      "integrity": "sha512-pZZoSxcCYco+DIKBTimr67J6Hy+EYGZDY/HCWC+iAEA9h1ByhMXAIVUXMcMFpOCxQ/xjXmPI2MkDL5HRm5eFrQ==",
+      "requires": {
+        "argparse": "^1.0.7",
+        "esprima": "^4.0.0"
+      }
+    },
     "jsbn": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
@@ -787,6 +818,11 @@
       "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
       "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
     },
+    "json5": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz",
+      "integrity": "sha1-BUNS5MTIDIbAkjh31EneF2pzLI0="
+    },
     "jsprim": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@@ -1034,6 +1070,11 @@
       "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz",
       "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4="
     },
+    "os-homedir": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+      "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M="
+    },
     "parseurl": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
@@ -1249,6 +1290,11 @@
         "supports-color": "^5.5.0"
       }
     },
+    "sprintf-js": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+      "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
+    },
     "sshpk": {
       "version": "1.15.2",
       "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
diff --git a/bbb-webhooks/package.json b/bbb-webhooks/package.json
index 2a33799b1c..d5364d8c13 100755
--- a/bbb-webhooks/package.json
+++ b/bbb-webhooks/package.json
@@ -1,6 +1,6 @@
 {
   "name": "bbb-callbacks",
-  "version": "0.9.0",
+  "version": "2.2.0",
   "description": "A module for BigBlueButton for API callbacks",
   "keywords": [
     "bigbluebutton",
@@ -17,7 +17,9 @@
     "request": "2.88.0",
     "sha1": "1.1.1",
     "sinon": "^7.2.2",
-    "winston": "3.1.0"
+    "winston": "3.1.0",
+    "config": "1.30.0",
+    "js-yaml": "3.13.0"
   },
   "engines": {
     "node": "8.4.0"
diff --git a/bbb-webhooks/responses.js b/bbb-webhooks/responses.js
new file mode 100644
index 0000000000..fd814d0354
--- /dev/null
+++ b/bbb-webhooks/responses.js
@@ -0,0 +1,46 @@
+responses = {};
+responses.failure = (key, msg) =>
+  `<response> \
+<returncode>FAILED</returncode> \
+<messageKey>${key}</messageKey> \
+<message>${msg}</message> \
+</response>`
+;
+responses.checksumError =
+  responses.failure("checksumError", "You did not pass the checksum security check.");
+
+responses.createSuccess = (id, permanent, getRaw) =>
+  `<response> \
+<returncode>SUCCESS</returncode> \
+<hookID>${id}</hookID> \
+<permanentHook>${permanent}</permanentHook> \
+<rawData>${getRaw}</rawData> \
+</response>`
+;
+responses.createFailure =
+  responses.failure("createHookError", "An error happened while creating your hook. Check the logs.");
+responses.createDuplicated = id =>
+  `<response> \
+<returncode>SUCCESS</returncode> \
+<hookID>${id}</hookID> \
+<messageKey>duplicateWarning</messageKey> \
+<message>There is already a hook for this callback URL.</message> \
+</response>`
+;
+
+responses.destroySuccess =
+  `<response> \
+<returncode>SUCCESS</returncode> \
+<removed>true</removed> \
+</response>`;
+responses.destroyFailure =
+  responses.failure("destroyHookError", "An error happened while removing your hook. Check the logs.");
+responses.destroyNoHook =
+  responses.failure("destroyMissingHook", "The hook informed was not found.");
+
+responses.missingParamCallbackURL =
+  responses.failure("missingParamCallbackURL", "You must specify a callbackURL in the parameters.");
+responses.missingParamHookID =
+  responses.failure("missingParamHookID", "You must specify a hookID in the parameters.");
+
+module.exports = responses;
diff --git a/bbb-webhooks/test/test.js b/bbb-webhooks/test/test.js
index 7239b9f16f..0e4dfc2f90 100644
--- a/bbb-webhooks/test/test.js
+++ b/bbb-webhooks/test/test.js
@@ -3,20 +3,20 @@ const nock = require("nock");
 const Application = require('../application.js');
 const Logger = require('../logger.js');
 const utils = require('../utils.js');
-const config = require('../config.js');
+const config = require('config');
 const Hook = require('../hook.js');
 const Helpers = require('./helpers.js')
 const sinon = require('sinon');
 const winston = require('winston');
 
-const sharedSecret = process.env.SHARED_SECRET || sharedSecret;
+const sharedSecret = sharedSecret;
 
 // Block winston from logging
 Logger.remove(winston.transports.Console);
 describe('bbb-webhooks tests', () => {
   before( (done) => {
-    config.hooks.queueSize = 10;
-    config.hooks.permanentURLs = [ { url: "http://wh.requestcatcher.com", getRaw: true } ];
+    config.get(hooks.queueSize) = 10;
+    config.get(hooks.permanentURLs) = [ { url: "http://wh.requestcatcher.com", getRaw: true } ];
     application = new Application();
     application.start( () => {
       done();
@@ -24,7 +24,7 @@ describe('bbb-webhooks tests', () => {
   });
   beforeEach( (done) => {
     hooks = Hook.allGlobalSync();
-    Helpers.flushall(config.redis.client);
+    Helpers.flushall(Application.redisClient);
     hooks.forEach( hook => {
       Helpers.flushredis(hook);
     })
@@ -32,7 +32,7 @@ describe('bbb-webhooks tests', () => {
   })
   after( () => {
     hooks = Hook.allGlobalSync();
-    Helpers.flushall(config.redis.client);
+    Helpers.flushall(Application.redisClient);
     hooks.forEach( hook => {
       Helpers.flushredis(hook);
     })
@@ -112,7 +112,7 @@ describe('bbb-webhooks tests', () => {
       .expect('Content-Type', /text\/xml/)
       .expect(200, (res) => {
         const hooks = Hook.allGlobalSync();
-        if (hooks && hooks[0].callbackURL == config.hooks.permanentURLs[0].url) {
+        if (hooks && hooks[0].callbackURL == config.get(hooks.permanentURLs)[0].url) {
           done();
         }
         else {
@@ -148,7 +148,7 @@ describe('bbb-webhooks tests', () => {
 
   describe('Hook queues', () => {
     before( () => {
-      config.redis.pubSubClient.psubscribe("test-channel");
+      config.get(Application.redisClient.psubscribe)("test-channel");
       Hook.addSubscription(Helpers.callback,null,false, (err,reply) => {
         const hooks = Hook.allGlobalSync();
         const hook = hooks[0];
@@ -164,10 +164,10 @@ describe('bbb-webhooks tests', () => {
       hook._processQueue.restore();
       hook2._processQueue.restore();
       Hook.removeSubscription(hooks[hooks.length-1].id);
-      config.redis.pubSubClient.unsubscribe("test-channel");
+      config.get(Application.redisClient.unsubscribe)("test-channel");
     });
     it('should have different queues for each hook', (done) => {
-      config.redis.client.publish("test-channel", JSON.stringify(Helpers.rawMessage));
+      config.get(Application.redisClient.publish)("test-channel", JSON.stringify(Helpers.rawMessage));
       const hooks = Hook.allGlobalSync();
 
       if (hooks && hooks[0].queue != hooks[hooks.length-1].queue) {
@@ -196,7 +196,7 @@ describe('bbb-webhooks tests', () => {
       hook = hook[0];
       for(i=0;i<=9;i++) { hook.enqueue("message" + i); }
 
-      if (hook && hook.queue.length <= config.hooks.queueSize) {
+      if (hook && hook.queue.length <= config.get(hooks.queueSize)) {
         done();
       }
       else {
@@ -207,7 +207,7 @@ describe('bbb-webhooks tests', () => {
 
   describe('/POST mapped message', () => {
     before( () => {
-      config.redis.pubSubClient.psubscribe("test-channel");
+      config.get(Application.redisClient.psubscribe)("test-channel");
       const hooks = Hook.allGlobalSync();
       const hook = hooks[0];
       hook.queue = [];
@@ -217,13 +217,13 @@ describe('bbb-webhooks tests', () => {
       const hooks = Hook.allGlobalSync();
       const hook = hooks[0];
       Helpers.flushredis(hook);
-      config.redis.pubSubClient.unsubscribe("test-channel");
+      config.get(Application.redisClient.unsubscribe)("test-channel");
     })
     it('should post mapped message ', (done) => {
       const hooks = Hook.allGlobalSync();
       const hook = hooks[0];
 
-      const getpost = nock(config.hooks.permanentURLs[0].url)
+      const getpost = nock(config.get(hooks.permanentURLs)[0].url)
                       .filteringRequestBody( (body) => {
                         let parsed = JSON.parse(body)
                         return parsed[0].data.id ? "mapped" : "not mapped";
@@ -232,13 +232,13 @@ describe('bbb-webhooks tests', () => {
                       .reply(200, (res) => {
                         done();
                       });
-    config.redis.client.publish("test-channel", JSON.stringify(Helpers.rawMessage));
+    config.get(Application.redisClient.publish)("test-channel", JSON.stringify(Helpers.rawMessage));
     })
   });
 
   describe('/POST raw message', () => {
     before( () => {
-      config.redis.pubSubClient.psubscribe("test-channel");
+      config.get(Application.redisClient.psubscribe)("test-channel");
       Hook.addSubscription(Helpers.callback,null,true, (err,hook) => {
         Helpers.flushredis(hook);
       })
@@ -247,7 +247,7 @@ describe('bbb-webhooks tests', () => {
       const hooks = Hook.allGlobalSync();
       Hook.removeSubscription(hooks[hooks.length-1].id);
       Helpers.flushredis(hooks[hooks.length-1]);
-      config.redis.pubSubClient.unsubscribe("test-channel");
+      config.get(Application.redisClient.unsubscribe)("test-channel");
     });
     it('should post raw message ', (done) => {
       const hooks = Hook.allGlobalSync();
@@ -264,10 +264,10 @@ describe('bbb-webhooks tests', () => {
                       .reply(200, () => {
                         done();
                       });
-      const permanent = nock(config.hooks.permanentURLs[0].url)
+      const permanent = nock(config.get(hooks.permanentURLs)[0].url)
                         .post("/")
                         .reply(200)
-      config.redis.client.publish("test-channel", JSON.stringify(Helpers.rawMessage));
+      config.get(Application.redisClient.publish)("test-channel", JSON.stringify(Helpers.rawMessage));
     })
   });
 
@@ -282,7 +282,7 @@ describe('bbb-webhooks tests', () => {
       const hooks = Hook.allGlobalSync();
       const hook = hooks[0];
       hook.enqueue("multiMessage2")
-      const getpost = nock(config.hooks.permanentURLs[0].url)
+      const getpost = nock(config.get(hooks.permanentURLs)[0].url)
                       .filteringPath( (path) => {
                         return path.split('?')[0];
                       })
diff --git a/bbb-webhooks/userMapping.js b/bbb-webhooks/userMapping.js
index bf11b4a031..3571396a52 100644
--- a/bbb-webhooks/userMapping.js
+++ b/bbb-webhooks/userMapping.js
@@ -2,7 +2,7 @@ const _ = require("lodash");
 const async = require("async");
 const redis = require("redis");
 
-const config = require("./config.js");
+const config = require("config");
 const Logger = require("./logger.js");
 
 // The database of mappings. Uses the internal ID as key because it is unique
@@ -33,15 +33,15 @@ module.exports = class UserMapping {
     this.internalUserID = null;
     this.meetingId = null;
     this.user = null;
-    this.redisClient = config.redis.client;
+    this.redisClient = Application.redisClient;
   }
 
   save(callback) {
     db[this.internalUserID] = this;
 
-    this.redisClient.hmset(config.redis.keys.userMap(this.id), this.toRedis(), (error, reply) => {
+    this.redisClient.hmset(config.get("redis.keys.userMapPrefix") + ":" + this.id, this.toRedis(), (error, reply) => {
       if (error != null) { Logger.error("[UserMapping] error saving mapping to redis:", error, reply); }
-      this.redisClient.sadd(config.redis.keys.userMaps, this.id, (error, reply) => {
+      this.redisClient.sadd(config.get("redis.keys.userMaps"), this.id, (error, reply) => {
         if (error != null) { Logger.error("[UserMapping] error saving mapping ID to the list of mappings:", error, reply); }
 
         (typeof callback === 'function' ? callback(error, db[this.internalUserID]) : undefined);
@@ -50,9 +50,9 @@ module.exports = class UserMapping {
   }
 
   destroy(callback) {
-    this.redisClient.srem(config.redis.keys.userMaps, this.id, (error, reply) => {
+    this.redisClient.srem(config.get("redis.keys.userMaps"), this.id, (error, reply) => {
       if (error != null) { Logger.error("[UserMapping] error removing mapping ID from the list of mappings:", error, reply); }
-      this.redisClient.del(config.redis.keys.userMap(this.id), error => {
+      this.redisClient.del(config.get("redis.keys.userMapPrefix") + ":" + this.id, error => {
         if (error != null) { Logger.error("[UserMapping] error removing mapping from redis:", error); }
 
         if (db[this.internalUserID]) {
@@ -165,15 +165,15 @@ module.exports = class UserMapping {
   // Gets all mappings from redis to populate the local database.
   // Calls `callback()` when done.
   static resync(callback) {
-    let client = config.redis.client;
+    let client = Application.redisClient;
     let tasks = [];
 
-    return client.smembers(config.redis.keys.userMaps, (error, mappings) => {
+    return client.smembers(config.get("redis.keys.userMaps"), (error, mappings) => {
       if (error != null) { Logger.error("[UserMapping] error getting list of mappings from redis:", error); }
 
       mappings.forEach(id => {
         tasks.push(done => {
-          client.hgetall(config.redis.keys.userMap(id), function(error, mappingData) {
+          client.hgetall(config.get("redis.keys.userMapPrefix") + ":" + id, function(error, mappingData) {
             if (error != null) { Logger.error("[UserMapping] error getting information for a mapping from redis:", error); }
 
             if (mappingData != null) {
diff --git a/bbb-webhooks/utils.js b/bbb-webhooks/utils.js
index c169e9c7b9..77c844253e 100644
--- a/bbb-webhooks/utils.js
+++ b/bbb-webhooks/utils.js
@@ -1,7 +1,7 @@
 const sha1 = require("sha1");
 const url = require("url");
 
-const config = require("./config");
+const config = require("config");
 
 const Utils = exports;
 
@@ -45,7 +45,7 @@ Utils.queryFromUrl = function(fullUrl) {
 // * returns: `create`
 Utils.methodFromUrl = function(fullUrl) {
   const urlObj = url.parse(fullUrl, true);
-  return urlObj.pathname.substr((config.bbb.apiPath + "/").length);
+  return urlObj.pathname.substr((config.get("bbb.apiPath") + "/").length);
 };
 
 // Returns the IP address of the client that made a request `req`.
diff --git a/bbb-webhooks/web_hooks.js b/bbb-webhooks/web_hooks.js
index 787027787a..9f2531ad1c 100644
--- a/bbb-webhooks/web_hooks.js
+++ b/bbb-webhooks/web_hooks.js
@@ -2,7 +2,7 @@ const _ = require("lodash");
 const async = require("async");
 const redis = require("redis");
 const request = require("request");
-const config = require("./config.js");
+const config = require("config");
 const Hook = require("./hook.js");
 const IDMapping = require("./id_mapping.js");
 const Logger = require("./logger.js");
@@ -14,7 +14,7 @@ const UserMapping = require("./userMapping.js");
 module.exports = class WebHooks {
 
   constructor() {
-    this.subscriberEvents = config.redis.pubSubClient;
+    this.subscriberEvents = Application.redisClient;
   }
 
   start(callback) {
@@ -73,8 +73,8 @@ module.exports = class WebHooks {
       }
     });
 
-    for (let k in config.hooks.channels) {
-      const channel = config.hooks.channels[k];
+    for (i = 0; i < config.get("hooks.channels"); ++i) {
+      const channel = config.get("hooks.channels")[i];
       this.subscriberEvents.psubscribe(channel);
     }
   }
@@ -142,7 +142,7 @@ module.exports = class WebHooks {
     });
 
     const sendRaw = hooks.some(hook => { return hook.getRaw });
-    if (sendRaw && config.hooks.getRaw) {
+    if (sendRaw && config.get("hooks.getRaw")) {
       this._processRaw(raw);
     }
   }
diff --git a/bbb-webhooks/web_server.js b/bbb-webhooks/web_server.js
index be5a005783..c95b43bb6f 100644
--- a/bbb-webhooks/web_server.js
+++ b/bbb-webhooks/web_server.js
@@ -2,10 +2,11 @@ const _ = require("lodash");
 const express = require("express");
 const url = require("url");
 
-const config = require("./config.js");
+const config = require("config");
 const Hook = require("./hook.js");
 const Logger = require("./logger.js");
 const Utils = require("./utils.js");
+const responses = require("./responses.js")
 
 // Web server that listens for API calls and process them.
 module.exports = class WebServer {
@@ -56,16 +57,16 @@ module.exports = class WebServer {
     }
 
     if (callbackURL == null) {
-      respondWithXML(res, config.api.responses.missingParamCallbackURL);
+      respondWithXML(res, responses.missingParamCallbackURL);
     } else {
       Hook.addSubscription(callbackURL, meetingID, getRaw, function(error, hook) {
         let msg;
         if (error != null) { // the only error for now is for duplicated callbackURL
-          msg = config.api.responses.createDuplicated(hook.id);
+          msg = responses.createDuplicated(hook.id);
         } else if (hook != null) {
-          msg = config.api.responses.createSuccess(hook.id, hook.permanent, hook.getRaw);
+          msg = responses.createSuccess(hook.id, hook.permanent, hook.getRaw);
         } else {
-          msg = config.api.responses.createFailure;
+          msg = responses.createFailure;
         }
         respondWithXML(res, msg);
       });
@@ -73,8 +74,8 @@ module.exports = class WebServer {
   }
   // Create a permanent hook. Permanent hooks can't be deleted via API and will try to emit a message until it succeed
   createPermanents(callback) {
-    for (let i = 0; i < config.hooks.permanentURLs.length; i++) {
-      Hook.addSubscription(config.hooks.permanentURLs[i].url, null, config.hooks.permanentURLs[i].getRaw, function(error, hook) {
+    for (let i = 0; i < config.get("hooks.permanentURLs").length; i++) {
+      Hook.addSubscription(config.get("hooks.permanentURLs")[i].url, null, config.get("hooks.permanentURLs")[i].getRaw, function(error, hook) {
         if (error != null) { // there probably won't be any errors here
           Logger.info("[WebServer] duplicated permanent hook", error);
         } else if (hook != null) {
@@ -92,16 +93,16 @@ module.exports = class WebServer {
     const hookID = urlObj.query["hookID"];
 
     if (hookID == null) {
-      respondWithXML(res, config.api.responses.missingParamHookID);
+      respondWithXML(res, responses.missingParamHookID);
     } else {
       Hook.removeSubscription(hookID, function(error, result) {
         let msg;
         if (error != null) {
-          msg = config.api.responses.destroyFailure;
+          msg = responses.destroyFailure;
         } else if (!result) {
-          msg = config.api.responses.destroyNoHook;
+          msg = responses.destroyNoHook;
         } else {
-          msg = config.api.responses.destroySuccess;
+          msg = responses.destroySuccess;
         }
         respondWithXML(res, msg);
       });
@@ -144,14 +145,14 @@ module.exports = class WebServer {
   _validateChecksum(req, res, next) {
     const urlObj = url.parse(req.url, true);
     const checksum = urlObj.query["checksum"];
-    const sharedSecret = process.env.SHARED_SECRET || config.bbb.sharedSecret;
+    const sharedSecret = config.get("bbb.sharedSecret");
 
     if (checksum === Utils.checksumAPI(req.url, sharedSecret)) {
       next();
     } else {
       Logger.info("[WebServer] checksum check failed, sending a checksumError response");
       res.setHeader("Content-Type", "text/xml");
-      res.send(cleanupXML(config.api.responses.checksumError));
+      res.send(cleanupXML(responses.checksumError));
     }
   }
 };
-- 
GitLab