npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ciscospark-webhook-validator

v1.1.0

Published

Use co-body and dataloader to validate incoming webhooks from Cisco Spark

Downloads

10

Readme

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'