Skip to content

Commit

Permalink
Use JSDoc for documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
jstayton committed Oct 30, 2023
1 parent 99651c1 commit 819fd9b
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 20 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# npm
/node_modules
/package-lock.json

# JSDoc
/docs
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ This `verifyWebhook` function is then called with the following arguments:

```js
verifyWebhook({
url: "The full URL that received the request, and that's registered with Truepic",
secret: "The shared secret that's registered with Truepic",
header: 'The value of the `truepic-signature` header from the request',
body: 'The raw body (unparsed JSON) from the request',
url: 'The full URL that received the request and is registered with Truepic.',
secret: "The shared secret that's registered with Truepic.",
header: 'The value of the `truepic-signature` header from the request.',
body: 'The raw body (unparsed JSON) from the request.',
leewayMinutes:
'The number of minutes allowed between the request being sent and received',
'The number of minutes allowed between the request being sent and received. Defaults to `5`.',
})
```

Expand Down Expand Up @@ -169,6 +169,16 @@ change:
npm test -- --watch
```

### Docs

[JSDoc](https://jsdoc.app/) is used to document the code.

To generate the docs as HTML to the (git-ignored) `docs` directory:

```bash
npm run docs
```

### Code Style & Linting

[Prettier](https://prettier.io/) is setup to enforce a consistent code style.
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"main": "src/index.js",
"repository": "TRUEPIC/webhook-verifier-nodejs",
"scripts": {
"docs": "jsdoc src --recurse --destination docs",
"lint": "run-p lint:*",
"lint:format": "prettier --check .",
"lint:format:fix": "prettier --write .",
Expand All @@ -20,6 +21,7 @@
"devDependencies": {
"eslint": "^8.52.0",
"eslint-config-prettier": "^9.0.0",
"jsdoc": "^4.0.2",
"npm-run-all": "^4.1.5",
"prettier": "3.0.3",
"release-it": "^16.2.1"
Expand Down
11 changes: 11 additions & 0 deletions src/error.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
/**
* The custom error thrown when verification fails.
*
* @memberof module:@truepic/webhook-verifier
* @extends Error
*/
class TruepicWebhookVerifierError extends Error {
/**
* Create an error for a failed verification.
*
* @param {string} message The description of what failed.
*/
constructor(message) {
super(message)

Expand Down
71 changes: 56 additions & 15 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { createHmac, timingSafeEqual } from 'node:crypto'
import TruepicWebhookVerifierError from './error.js'

// Parse the `truepic-signature` header into timestamp and signature values.
//
// The header value looks like this:
//
// t=1634066973,s=6FBEiVZ8EO79dk5XllfnG18b83ZvLt2kdxcE8FJ/BwU
//
// The `t` value is the timestamp of when the request was sent (in seconds), and
// the `s` value is the signature of the request.
/**
* Parse the `truepic-signature` header into timestamp and signature values.
*
* The header value looks like this:
*
* t=1634066973,s=6FBEiVZ8EO79dk5XllfnG18b83ZvLt2kdxcE8FJ/BwU
*
* The `t` value is the timestamp of when the request was sent (in seconds), and
* the `s` value is the signature of the request.
*
* @private
* @param {string} header The value of the `truepic-signature` header from the request.
* @throws {TruepicWebhookVerifierError} If parsing fails.
* @returns {Object} The parsed `timestamp` and `signature` values.
*/
function parseHeader(header) {
if (!header?.length) {
throw new TruepicWebhookVerifierError('Header is missing or empty')
Expand Down Expand Up @@ -53,9 +60,18 @@ function parseHeader(header) {
return { timestamp, signature }
}

// Verify the timestamp to ensure the request is recent and not a potentially
// delayed replay attack. Some leeway is required in case the clocks on either
// end of the request aren't quite in sync.
/**
* Verify the timestamp to ensure the request is recent and not a potentially
* delayed replay attack. Some leeway is required in case the clocks on either
* end of the request aren't quite in sync.
*
* @private
* @param {Object} options
* @param {number} options.timestamp The timestamp parsed from the `truepic-signature` request header.
* @param {number} options.leewayMinutes The number of minutes allowed between the request being sent and received.
* @throws {TruepicWebhookVerifierError} If verification fails.
* @returns {true} If verification succeeds.
*/
function verifyTimestamp({ timestamp, leewayMinutes }) {
const diff = Math.abs(Date.now() - timestamp * 1000)
const diffMinutes = Math.ceil(diff / (1000 * 60))
Expand All @@ -69,9 +85,21 @@ function verifyTimestamp({ timestamp, leewayMinutes }) {
return true
}

// Verify the signature to ensure the integrity of the data being received, the
// authenticity of the sender (Truepic), and the authenticity of the receiver
// (you).
/**
* Verify the signature to ensure the integrity of the data being received, the
* authenticity of the sender (Truepic), and the authenticity of the receiver
* (you).
*
* @private
* @param {Object} options
* @param {string} options.url The full URL that received the request and is registered with Truepic.
* @param {string} options.secret The shared secret that's registered with Truepic.
* @param {string} options.body The raw body (unparsed JSON) from the request.
* @param {number} options.timestamp The timestamp parsed from the `truepic-signature` request header.
* @param {string} options.signature The signature parsed from the `truepic-signature` request header.
* @throws {TruepicWebhookVerifierError} If verification fails.
* @returns {true} If verification succeeds.
*/
function verifySignature({ url, secret, body, timestamp, signature }) {
// Rebuild the signature (SHA-256, base64-encoded HMAC digest) with a secret
// that only Truepic and the intended receiver are privy to.
Expand All @@ -97,7 +125,19 @@ function verifySignature({ url, secret, body, timestamp, signature }) {
return true
}

// Verify a webhook from Truepic Vision or Lens.
/**
* Verify a webhook from Truepic Vision or Lens.
*
* @memberof module:@truepic/webhook-verifier
* @param {Object} options
* @param {string} options.url The full URL that received the request and is registered with Truepic.
* @param {string} options.secret The shared secret that's registered with Truepic.
* @param {string} options.header The value of the `truepic-signature` header from the request.
* @param {string} options.body The raw body (unparsed JSON) from the request.
* @param {number} [options.leewayMinutes=5] The number of minutes allowed between the request being sent and received.
* @throws {TruepicWebhookVerifierError} If verification fails.
* @returns {true} If verification succeeds.
*/
function verifyWebhook({ url, secret, header, body, leewayMinutes = 5 }) {
const { timestamp, signature } = parseHeader(header)

Expand All @@ -117,4 +157,5 @@ function verifyWebhook({ url, secret, header, body, leewayMinutes = 5 }) {
return true
}

/** @module @truepic/webhook-verifier */
export { verifyWebhook, TruepicWebhookVerifierError }

0 comments on commit 819fd9b

Please sign in to comment.