Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/hagemt/node-ciscospark-webhook-validator
Provides an safe and efficient `validate` Function. `npm install --save ciscospark-webhook-validator`
https://github.com/hagemt/node-ciscospark-webhook-validator
Last synced: about 1 month ago
JSON representation
Provides an safe and efficient `validate` Function. `npm install --save ciscospark-webhook-validator`
- Host: GitHub
- URL: https://github.com/hagemt/node-ciscospark-webhook-validator
- Owner: hagemt
- Created: 2017-03-20T17:01:03.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2023-02-26T10:13:48.000Z (over 1 year ago)
- Last Synced: 2024-04-14T19:40:04.868Z (7 months ago)
- Language: JavaScript
- Size: 551 KB
- Stars: 0
- Watchers: 1
- Forks: 2
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Cisco Spark Webhook Validator
[![Travis CI badge](https://api.travis-ci.org/hagemt/node-ciscospark-webhook-validator.svg?branch=master)](https://travis-ci.org/hagemt/node-ciscospark-webhook-validator)
[![Greenkeeper badge](https://badges.greenkeeper.io/hagemt/node-ciscospark-webhook-validator.svg)](https://account.greenkeeper.io/account/hagemt)NOTE: "Spark" was previous branding for the Cisco Webex Teams message platform.
Official documentation available at: https://developer.webex.com/docs/webhooks#overview
This module facilitates business-logic that operates on a Cisco Spark webhook payload, such as:
```javascript
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):
```javascript
// 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+)
```javascript
// 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
3) Calls to `validate` that load the same token do so exactly once
2) Calls to `validate` that load the same webhook do so exactly onceThe 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:
```javascript
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:
```javascript
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:
```javascript
const Spark = require('ciscospark-webhook-validator')
Spark.getAPIEndpoint = () => 'my.spark.endpoint.com'
```