ciscospark-webhook-validator

1.1.0 • Public • Published

Cisco Spark Webhook Validator

Travis CI badge Greenkeeper badge

Official documentation available at: https://developer.ciscospark.com/webhooks-explained.html

This module facilitates business-logic that operates on a Cisco Spark webhook payload, such as:

const businessLogic = ({ data, event, resource }) => {
    const isMessagesCreated = resource === 'messages' && event === 'created'
    if (!isMessagesCreated || data.personEmail.endsWith('@sparkbot.io')) return
    console.log('some human (not a Bot) created new Spark message(s):', data)
}

The default Spark export provides a safe validate Function that can be customized for optimal efficiency.

Examples

In your project: npm install --save ciscospark-webhook-validator

In server.js, or elsewhere in your application's module(s):

// see sections below for basic usage or full customization:
const { validate } = require('ciscospark-webhook-validator')
const server = require('http').createServer(/* listener */)

Basic Usage (ES6, can be adapted for ES5 or ES7+)

// event listener fires business-logic only for valid webhook payloads
// (a payload is valid if and only if its HMAC matches X-Spark-Signature)
// possible responses are: 202 Accepted / 406 Not Acceptable (no body)
server.on('request', (req, res) => {
    const onceAccepted = () => Object.assign(res, { statusCode: 202 }).end()
    const onceNotAcceptable = () => Object.assign(res, { statusCode: 406 }).end()
    validate(req).then(businessLogic).then(onceAccepted, onceNotAcceptable)
})
 
// with async / await (or co / yield) validation could be:
// request.body = await validate(req).catch(() => null)
// if (request.body) businessLogic(request.body)

Using ngrok

if (process.env.CISCOSPARK_ACCESS_TOKEN) {
    const port = process.env.PORT || 8080
    server.listen({ port }, (listenError) => {
        if (listenError) {
            console.error(listenError)
            process.exitCode = 1
        } else {
            console.log(`listening on PORT=${port}`)
        }
    })
}

// PROTIP: in another terminal, run these commands:
// npm install ngrok # https://www.npmjs.com/package/ngrok
// node_modules/.bin/ngrok http $PORT # targetUrl is HTTPS
// with your token from https://developer.ciscospark.com/
// create a new Spark webhook w/ $secret and $targetUrl
// open http://localhost:4040/ in your favorite browser

Notes on module, correctness, and efficiency

~100 SLOC is provided by a single ES6 module. (and test coverage is complete)

NodeJS's crypto.timingSafeEqual is used to compare the contents of Buffers.

N.B. Legacy applications may require('ciscospark-webhook-validator/es5').

Algorithm Correctness

Via co-body a req's body is digested as text and then JSON.parse'd.

Using HTTPS + Authorization, that webhook's secret is requested from Spark.

X-Spark-Signature is compared against the digest; validated JSON is returned.

Correctness follows from use of the webhook's fetched secret for HMAC validation.

Algorithm Efficiency

Efficiently is achieved through use of a RequestCache such that:

  1. Calls to validate that run on the same request are coalesced
  2. Calls to validate that load the same token do so exactly once
  3. Calls to validate that load the same webhook do so exactly once

The first relies on the RequestCache (WeakMap) implementation.

The second and third are a facility of the dataloader implementation.

A basic example is included above. See the next section for advanced usage.

Full Customization

It is easy to adjust the validation process for many special circumstances:

  1. If your application uses a single token, export CISCOSPARK_ACCESS_TOKEN
  2. Or, Spark.getAccessToken may be replaced with a Promise-returning Function
  3. Spark.getWebhookDetails may be replaced similarly (see examples below)
  4. Spark.RequestCache and Spark.ResponseError type(s) may be replaced

Bearer Tokens and Webhook Secrets

If your application is a bot, the easiest way to provide its token is via environment variables:

process.env.CISCOSPARK_ACCESS_TOKEN = ... // all future requests to Spark will use this, by default

By default, one request is made to Spark for each unique webhook registered to your application.

When tokens may/must be provided somehow

For example, if your application loads tokens from a secret store:

const Spark = require('ciscospark-webhook-validator')
Spark.getAccessToken = creatorID => vault.getAccessToken(creatorID)

When webhooks may/must be provided somehow

For example, if your application makes use of a single, static webhook:

const Spark = require('ciscospark-webhook-validator')
Spark.getWebhookDetails = () => Promise.resolve({ secret: '...' })

When a different Spark API endpoint may/must be provided somehow

For example, if you want to test against a self-hosted, mock, or other implementation of the Spark APIs:

const Spark = require('ciscospark-webhook-validator')
Spark.getAPIEndpoint = () => 'my.spark.endpoint.com'

Package Sidebar

Install

npm i ciscospark-webhook-validator

Weekly Downloads

0

Version

1.1.0

License

MIT

Unpacked Size

26.3 kB

Total Files

10

Last publish

Collaborators

  • teh