diff --git a/.gitignore b/.gitignore index ec25558..cfe6ad8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ # npm /node_modules /package-lock.json + +# JSDoc +/docs diff --git a/README.md b/README.md index ac7ae2c..6b1ee2a 100644 --- a/README.md +++ b/README.md @@ -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`.', }) ``` @@ -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. diff --git a/package.json b/package.json index cf9d1ab..f26d4ca 100644 --- a/package.json +++ b/package.json @@ -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 .", @@ -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" diff --git a/src/error.js b/src/error.js index 077127e..3abdae3 100644 --- a/src/error.js +++ b/src/error.js @@ -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) diff --git a/src/index.js b/src/index.js index 7a620e9..ea426f5 100644 --- a/src/index.js +++ b/src/index.js @@ -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') @@ -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)) @@ -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. @@ -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) @@ -117,4 +157,5 @@ function verifyWebhook({ url, secret, header, body, leewayMinutes = 5 }) { return true } +/** @module @truepic/webhook-verifier */ export { verifyWebhook, TruepicWebhookVerifierError }