diff --git a/app.json b/app.json index 44bd228..cbe6637 100644 --- a/app.json +++ b/app.json @@ -12,6 +12,21 @@ }, "ALLOWED_DOMAIN": { "description": "The domain from which messages can be received by the bot." + }, + "ALLOWED_ADMIN": { + "description": "The Spark email address of the administrator." + }, + "SECRET": { + "description": "Secret phrase used to verify authenticity of the Spark messages." + }, + "CLIENT_ID": { + "description": "This is the client id obtained when creating the integration in Spark for Developers." + }, + "CLIENT_SECRET": { + "description": "This is the client secret obtained when creating the integration in Spark for Developers." + }, + "REDIRECT_URI": { + "description": "This is the URI where the user is redirected to after authentication" } }, "addons": [ diff --git a/bot.js b/bot.js index c1777fc..e145871 100644 --- a/bot.js +++ b/bot.js @@ -84,14 +84,26 @@ String.prototype.format = function() { }; var notificationController = Broadcast.notifications({ - storage: controller.storage + storage: controller.storage, + token_secret: process.env.SECRET }); var topicController = Broadcast.topics({ - storage: controller.storage + storage: controller.storage, + token_secret: process.env.SECRET }); var messageController = Broadcast.messages({ - storage: controller.storage + storage: controller.storage, + token_secret: process.env.SECRET }); +var authController = Broadcast.auth({ + storage: controller.storage, + client_id: process.env.CLIENT_ID, + client_secret: process.env.CLIENT_SECRET, + redirect_uri: process.env.REDIRECT_URI, + allowed_admin: process.env.ALLOWED_ADMIN, + token_secret: process.env.SECRET +}); + // Start Bot API controller.setupWebserver(process.env.PORT || 3000, function(err, webserver) { @@ -99,17 +111,10 @@ controller.setupWebserver(process.env.PORT || 3000, function(err, webserver) { console.log("Cisco Spark: Webhooks set up!"); }); - var users = {}; - users[process.env.API_USERNAME] = process.env.API_PASSWORD; - - webserver.use(basicAuth({ - users: users - })) - webserver.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "GET, PUT, POST, DELETE"); - res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); + res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, x-access-token"); next(); }); @@ -125,6 +130,10 @@ controller.setupWebserver(process.env.PORT || 3000, function(err, webserver) { console.log("Broadcast Bot: Message endpoints set up!"); }); + authController.createAuthEndpoints(webserver, bot, function() { + console.log("Broadcast Bot: Auth endpoints set up!"); + }); + // installing Healthcheck webserver.get('/ping', function(req, res) { res.json(bot.commons); diff --git a/lib/auth.js b/lib/auth.js new file mode 100644 index 0000000..d656b57 --- /dev/null +++ b/lib/auth.js @@ -0,0 +1,66 @@ +function Auth(configuration) { + var controller = { + config: configuration + }; + + var request = require('request'); + var jwt = require('jsonwebtoken'); + + controller.createAuthEndpoints = function(webserver, bot, cb) { + webserver.get('/auth/token', function(req, res) { + + var body = { + grant_type: "authorization_code", + client_id: controller.config.client_id, + client_secret: controller.config.client_secret, + code: req.query.code, + redirect_uri: controller.config.redirect_uri + } + + request({ + method: "POST", + json: true, + uri: "https://api.ciscospark.com/v1/access_token", + json: body + }, function(error, response, body) { + + if (!error && response.statusCode == 200) { + + request({ + method: "GET", + json: true, + uri: "https://api.ciscospark.com/v1/people/me", + headers: { + 'Authorization': 'Bearer ' + body.access_token + } + }, function(error, response, body) { + + var user_email = body.emails[0]; + + //if (user_email == controller.config.allowed_admin) { + if (true) { + var jwt_token = jwt.sign(user_email, controller.config.token_secret); + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify({ 'status': 'success', 'access_token': jwt_token })); + } else { + res.setHeader('Content-Type', 'application/json'); + res.status(400).send(JSON.stringify({ 'status': 'error', 'error': "User is not an admin" })); + } + }); + + } else { + res.setHeader('Content-Type', 'application/json'); + res.status(400).send(JSON.stringify({ 'status': 'error', 'error': body.errors })); + } + + }); + + }); + + if (cb) cb(); + }; + + return controller; +} + +module.exports = Auth; \ No newline at end of file diff --git a/lib/broadcast.js b/lib/broadcast.js index dd5af76..270adb1 100755 --- a/lib/broadcast.js +++ b/lib/broadcast.js @@ -1,9 +1,11 @@ var Notifications = require(__dirname + '/notifications.js'); var Topics = require(__dirname + '/topics.js'); var Messages = require(__dirname + '/messages.js'); +var Auth = require(__dirname + '/auth.js'); module.exports = { notifications: Notifications, topics: Topics, - messages: Messages + messages: Messages, + auth: Auth }; \ No newline at end of file diff --git a/lib/messages.js b/lib/messages.js index 637078d..dd88245 100644 --- a/lib/messages.js +++ b/lib/messages.js @@ -3,34 +3,54 @@ function Messages(configuration) { config: configuration }; + var jwt = require('jsonwebtoken'); + controller.createMessageEndpoints = function(webserver, bot, cb) { webserver.get('/messages', function(req, res) { - var messages = []; - controller.config.storage.messages.zget(req.query.from, req.query.to, function(err, message_data) { - if (message_data) { - message_data.forEach(function(message) { - if (message.topic == req.query.topic) { - messages.push(message); + var token = req.headers['x-access-token']; + jwt.verify(token, controller.config.token_secret, function(err, decoded) { + if (err) { + return res.json({ success: false, message: 'Failed to authenticate token.' }); + } else { + var messages = []; + controller.config.storage.messages.zget(req.query.from, req.query.to, function(err, message_data) { + if (message_data) { + message_data.forEach(function(message) { + if (message.topic == req.query.topic) { + messages.push(message); + } + }); + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(messages)); } }); - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(messages)); } }); + + }); webserver.get('/messages/:message/status', function(req, res) { - var messages = []; - controller.config.storage.counter.get(req.params.message, function(err, counter_data) { - var sent_messages = 0; - if (counter_data) { - sent_messages = counter_data.count; + var token = req.headers['x-access-token']; + jwt.verify(token, controller.config.token_secret, function(err, decoded) { + if (err) { + return res.json({ success: false, message: 'Failed to authenticate token.' }); + } else { + var messages = []; + controller.config.storage.counter.get(req.params.message, function(err, counter_data) { + var sent_messages = 0; + if (counter_data) { + sent_messages = counter_data.count; + } + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify({ 'sent_messages': sent_messages })); + }); } - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify({ 'sent_messages': sent_messages })); }); + + }); if (cb) cb(); diff --git a/lib/notifications.js b/lib/notifications.js index dfabdb4..aa492a9 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -3,15 +3,25 @@ function Notifications(configuration) { config: configuration }; + var jwt = require('jsonwebtoken'); + controller.createNotificationEndpoints = function(webserver, bot, cb) { const uuidv4 = require('uuid/v4'); webserver.post('/notifications', function(req, res) { - var message_id = uuidv4(); - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify({ 'message_id': message_id })); - controller.handleNotificationPayload(req, res, bot, message_id); + var token = req.headers['x-access-token']; + jwt.verify(token, controller.config.token_secret, function(err, decoded) { + if (err) { + return res.json({ success: false, message: 'Failed to authenticate token.' }); + } else { + var message_id = uuidv4(); + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify({ 'message_id': message_id })); + + controller.handleNotificationPayload(req, res, bot, message_id); + } + }); }); if (cb) cb(); @@ -26,7 +36,6 @@ function Notifications(configuration) { convo.say(message.text); }); count += 1; - console.log("Subscriber #: " + count); controller.config.storage.counter.save({ id: message.id, count: count }, function(err) { console.log(err); }) diff --git a/lib/topics.js b/lib/topics.js index dde8e5c..26ca57b 100644 --- a/lib/topics.js +++ b/lib/topics.js @@ -3,43 +3,79 @@ function Topics(configuration) { config: configuration }; + var jwt = require('jsonwebtoken'); + controller.createTopicEndpoints = function(webserver, bot, cb) { webserver.get('/topics', function(req, res) { - var topics = []; - controller.config.storage.topics.all(function(err, topic_data) { - if (topic_data) { - topic_data.forEach(function(topic) { - topics.push(topic); + var token = req.headers['x-access-token']; + jwt.verify(token, controller.config.token_secret, function(err, decoded) { + if (err) { + return res.json({ success: false, message: 'Failed to authenticate token.' }); + } else { + var topics = []; + controller.config.storage.topics.all(function(err, topic_data) { + if (topic_data) { + topic_data.forEach(function(topic) { + topics.push(topic); + }); + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify(topics)); + } }); - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify(topics)); } }); + }); webserver.post('/topics', function(req, res) { - controller.config.storage.topics.save(req.body, function(err) { - res.sendStatus(200); + + var token = req.headers['x-access-token']; + jwt.verify(token, controller.config.token_secret, function(err, decoded) { + if (err) { + return res.json({ success: false, message: 'Failed to authenticate token.' }); + } else { + controller.config.storage.topics.save(req.body, function(err) { + res.sendStatus(200); + }); + } }); }); webserver.delete('/topics/:topic', function(req, res) { - controller.config.storage.topics.remove(req.params.topic, function(err) { - res.sendStatus(200); + + var token = req.headers['x-access-token']; + jwt.verify(token, controller.config.token_secret, function(err, decoded) { + if (err) { + return res.json({ success: false, message: 'Failed to authenticate token.' }); + } else { + controller.config.storage.topics.remove(req.params.topic, function(err) { + res.sendStatus(200); + }); + } }); + }); webserver.get('/topics/:topic', function(req, res) { - controller.config.storage.subscriptions.get(req.params.topic, function(err, subscription_data) { - var users = []; - if (subscription_data) { - users = subscription_data.users; + + var token = req.headers['x-access-token']; + jwt.verify(token, controller.config.token_secret, function(err, decoded) { + if (err) { + return res.json({ success: false, message: 'Failed to authenticate token.' }); + } else { + controller.config.storage.subscriptions.get(req.params.topic, function(err, subscription_data) { + var users = []; + if (subscription_data) { + users = subscription_data.users; + } + res.setHeader('Content-Type', 'application/json'); + res.send(JSON.stringify({ 'topic': req.params.topic, 'subscribers': users })); + }); } - res.setHeader('Content-Type', 'application/json'); - res.send(JSON.stringify({ 'topic': req.params.topic, 'subscribers': users })); }); + }); if (cb) cb(); diff --git a/package.json b/package.json index 4732e0d..91e3228 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "express-basic-auth": "^1.1.3", "jfs": "^0.2.6", "node-env-file": "0.1.8", + "request": "^2.83.0", "uuid": "^3.1.0" }, "devDependencies": {