Webhooks: sign callback calls with a checksum
Simple signature done initially in a way similar to how bbb-web does it.
This commit is contained in:
parent
e070f3c5c5
commit
4e72d53b94
@ -1,12 +1,15 @@
|
||||
EventEmitter = require('events').EventEmitter
|
||||
request = require("request")
|
||||
|
||||
config = require("./config")
|
||||
Utils = require("./utils")
|
||||
|
||||
# Class that emits a callback. Will try several times until the callback is
|
||||
# properly emitted and stop when successful (or after a given number of tries).
|
||||
# Emits "success" on success and "error" when gave up trying to emit the callback.
|
||||
module.exports = class CallbackEmitter extends EventEmitter
|
||||
|
||||
constructor: (@url, @message) ->
|
||||
constructor: (@callbackURL, @message) ->
|
||||
|
||||
start: ->
|
||||
@_scheduleNext 0
|
||||
@ -23,13 +26,19 @@ module.exports = class CallbackEmitter extends EventEmitter
|
||||
, timeout)
|
||||
|
||||
_emitMessage: (callback) ->
|
||||
# TODO: the external meeting ID is not on redis yet
|
||||
# message.meetingID = rep.externalMeetingID
|
||||
# basic data structure
|
||||
data =
|
||||
timestamp: new Date().getTime()
|
||||
event: @message
|
||||
|
||||
# add a checksum to the post data
|
||||
checksum = Utils.checksum("#{@callbackURL}#{JSON.stringify(data)}#{config.bbb.sharedSecret}")
|
||||
data.checksum = checksum
|
||||
|
||||
requestOptions =
|
||||
uri: @url
|
||||
uri: @callbackURL
|
||||
method: "POST"
|
||||
json: @message
|
||||
json: data
|
||||
|
||||
request requestOptions, (error, response, body) ->
|
||||
if error?
|
||||
|
62
labs/bbb-callback/utils.coffee
Normal file
62
labs/bbb-callback/utils.coffee
Normal file
@ -0,0 +1,62 @@
|
||||
sha1 = require("sha1")
|
||||
url = require("url")
|
||||
|
||||
config = require("./config")
|
||||
|
||||
Utils = exports
|
||||
|
||||
# Calculates the checksum given a url `fullUrl` and a `salt`, as calculate by bbb-web.
|
||||
Utils.checksumAPI = (fullUrl, salt) ->
|
||||
query = Utils.queryFromUrl(fullUrl)
|
||||
method = Utils.methodFromUrl(fullUrl)
|
||||
Utils.checksum(method + query + salt)
|
||||
|
||||
# Calculates the checksum for a string.
|
||||
# Just a wrapper for the method that actually does it.
|
||||
Utils.checksum = (string) ->
|
||||
sha1(string)
|
||||
|
||||
# Get the query of an API call from the url object (from url.parse())
|
||||
# Example:
|
||||
#
|
||||
# * `fullUrl` = `http://bigbluebutton.org/bigbluebutton/api/create?name=Demo+Meeting&meetingID=Demo`
|
||||
# * returns: `name=Demo+Meeting&meetingID=Demo`
|
||||
Utils.queryFromUrl = (fullUrl) ->
|
||||
|
||||
# Returns the query without the checksum.
|
||||
# We can't use url.parse() because it would change the encoding
|
||||
# and the checksum wouldn't match. We need the url exactly as
|
||||
# the client sent us.
|
||||
query = fullUrl.replace(/&checksum=[^&]*/, '')
|
||||
query = query.replace(/checksum=[^&]*&/, '')
|
||||
query = query.replace(/checksum=[^&]*$/, '')
|
||||
matched = query.match(/\?(.*)/)
|
||||
if matched?
|
||||
matched[1]
|
||||
else
|
||||
''
|
||||
|
||||
# Get the method name of an API call from the url object (from url.parse())
|
||||
# Example:
|
||||
#
|
||||
# * `fullUrl` = `http://mconf.org/bigbluebutton/api/create?name=Demo+Meeting&meetingID=Demo`
|
||||
# * returns: `create`
|
||||
Utils.methodFromUrl = (fullUrl) ->
|
||||
urlObj = url.parse(fullUrl, true)
|
||||
urlObj.pathname.substr (config.bbb.apiPath + "/").length
|
||||
|
||||
# Returns the IP address of the client that made a request `req`.
|
||||
# If can not determine the IP, returns `127.0.0.1`.
|
||||
Utils.ipFromRequest = (req) ->
|
||||
|
||||
# the first ip in the list if the ip of the client
|
||||
# the others are proxys between him and us
|
||||
if req.headers?["x-forwarded-for"]?
|
||||
ips = req.headers["x-forwarded-for"].split(",")
|
||||
ipAddress = ips[0]?.trim()
|
||||
|
||||
# fallbacks
|
||||
ipAddress ||= req.headers?["x-real-ip"] # when behind nginx
|
||||
ipAddress ||= req.connection?.remoteAddress
|
||||
ipAddress ||= "127.0.0.1"
|
||||
ipAddress
|
@ -1,9 +1,9 @@
|
||||
express = require("express")
|
||||
sha1 = require("sha1")
|
||||
url = require("url")
|
||||
|
||||
config = require("./config")
|
||||
Hook = require("./hook")
|
||||
Utils = require("./utils")
|
||||
|
||||
# Web server that listens for API calls and process them.
|
||||
module.exports = class WebServer
|
||||
@ -77,48 +77,13 @@ module.exports = class WebServer
|
||||
urlObj = url.parse(req.url, true)
|
||||
checksum = urlObj.query["checksum"]
|
||||
|
||||
if checksum is @_checksum(req.url, config.bbb.sharedSecret)
|
||||
if checksum is Utils.checksumAPI(req.url, config.bbb.sharedSecret)
|
||||
next()
|
||||
else
|
||||
console.log "checksum check failed, sending a checksumError response"
|
||||
res.setHeader("Content-Type", "text/xml")
|
||||
res.send cleanupXML(config.api.responses.checksumError)
|
||||
|
||||
# Calculates the checksum given a url `fullUrl` and a `salt`.
|
||||
_checksum: (fullUrl, salt) ->
|
||||
query = @_queryFromUrl(fullUrl)
|
||||
method = @_methodFromUrl(fullUrl)
|
||||
sha1(method + query + salt)
|
||||
|
||||
# Get the query of an API call from the url object (from url.parse())
|
||||
# Example:
|
||||
#
|
||||
# * `fullUrl` = `http://bigbluebutton.org/bigbluebutton/api/create?name=Demo+Meeting&meetingID=Demo`
|
||||
# * returns: `name=Demo+Meeting&meetingID=Demo`
|
||||
_queryFromUrl: (fullUrl) ->
|
||||
|
||||
# Returns the query without the checksum.
|
||||
# We can't use url.parse() because it would change the encoding
|
||||
# and the checksum wouldn't match. We need the url exactly as
|
||||
# the client sent us.
|
||||
query = fullUrl.replace(/&checksum=[^&]*/, '')
|
||||
query = query.replace(/checksum=[^&]*&/, '')
|
||||
query = query.replace(/checksum=[^&]*$/, '')
|
||||
matched = query.match(/\?(.*)/)
|
||||
if matched?
|
||||
matched[1]
|
||||
else
|
||||
''
|
||||
|
||||
# Get the method name of an API call from the url object (from url.parse())
|
||||
# Example:
|
||||
#
|
||||
# * `fullUrl` = `http://mconf.org/bigbluebutton/api/create?name=Demo+Meeting&meetingID=Demo`
|
||||
# * returns: `create`
|
||||
_methodFromUrl: (fullUrl) ->
|
||||
urlObj = url.parse(fullUrl, true)
|
||||
urlObj.pathname.substr (config.bbb.apiPath + "/").length
|
||||
|
||||
respondWithXML = (res, msg) ->
|
||||
res.setHeader("Content-Type", "text/xml")
|
||||
res.send cleanupXML(msg)
|
||||
@ -126,23 +91,7 @@ respondWithXML = (res, msg) ->
|
||||
# Returns a simple string with a description of the client that made
|
||||
# the request. It includes the IP address and the user agent.
|
||||
clientDataSimple = (req) ->
|
||||
"ip " + ipFromRequest(req) + ", using " + req.headers["user-agent"]
|
||||
|
||||
# Returns the IP address of the client that made a request `req`.
|
||||
# If can not determine the IP, returns `127.0.0.1`.
|
||||
ipFromRequest = (req) ->
|
||||
|
||||
# the first ip in the list if the ip of the client
|
||||
# the others are proxys between him and us
|
||||
if req.headers?["x-forwarded-for"]?
|
||||
ips = req.headers["x-forwarded-for"].split(",")
|
||||
ipAddress = ips[0]?.trim()
|
||||
|
||||
# fallbacks
|
||||
ipAddress ||= req.headers?["x-real-ip"] # when behind nginx
|
||||
ipAddress ||= req.connection?.remoteAddress
|
||||
ipAddress ||= "127.0.0.1"
|
||||
ipAddress
|
||||
"ip " + Utils.ipFromRequest(req) + ", using " + req.headers["user-agent"]
|
||||
|
||||
# Cleans up a string with an XML in it removing spaces and new lines from between the tags.
|
||||
cleanupXML = (string) ->
|
||||
|
Loading…
Reference in New Issue
Block a user