Skip to content

Commit

Permalink
Tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
jstayton committed Dec 5, 2023
1 parent 3377243 commit 8e4b5f8
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 39 deletions.
45 changes: 25 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
# Truepic Webhook Verifier for Node.js
<h1 align="center" width="100%">
Truepic Webhook Verifier for Node.js
</h1>

Verify webhooks from Truepic Vision or Lens in your Node.js app.
<h3 align="center" width="100%">
<i>Verify webhooks from Truepic Vision or Lens in your Node.js app</i>
</h3>

This module verifies

- the integrity of the data being received,
- the authenticity of the sender (Truepic),
- the authenticity of the receiver (you), and
- the time between the request being sent and received to prevent replay attack.
- the time between the request being sent and received to prevent replay
attacks.

If you're not using Node.js, this also serves as a reference implementation with
thorough documentation to make the translation into another language as painless
Expand All @@ -21,17 +26,18 @@ npm install @truepic/webhook-verifier

## Usage

The `@truepic/webhook-verifier` module exports a named `verifyWebhook` function
that should be imported to begin:
The `@truepic/webhook-verifier` module exports a default function that should be
imported to begin:

```js
import { verifyWebhook } from '@truepic/webhook-verifier'
import verifyTruepicWebhook from '@truepic/webhook-verifier'
```

This `verifyWebhook` function is then called with the following arguments:
This `verifyTruepicWebhook` function (or whatever you imported it as) is then
called with the following arguments:

```js
verifyWebhook({
verifyTruepicWebhook({
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.',
Expand All @@ -54,21 +60,21 @@ to adapt if you're using a different one.
### Example: Express.js

```js
import { verifyWebhook } from '@truepic/webhook-verifier'
import verifyTruepicWebhook from '@truepic/webhook-verifier'
import express from 'express'
import { env } from 'node:process'

const app = express()

app.post(
'/truepic/webhook',
// This is important! We need the raw request body for `verifyWebhook`.
// This is important! We need the raw request body for `verifyTruepicWebhook`.
express.raw({
type: 'application/json',
}),
(req, res, next) => {
try {
verifyWebhook({
verifyTruepicWebhook({
url: env.TRUEPIC_WEBHOOK_URL,
secret: env.TRUEPIC_WEBHOOK_SECRET,
header: req.header('truepic-signature'),
Expand Down Expand Up @@ -99,20 +105,20 @@ npm install fastify-raw-body
```

```js
import { verifyWebhook } from '@truepic/webhook-verifier'
import verifyTruepicWebhook from '@truepic/webhook-verifier'
import Fastify from 'fastify'
import { env } from 'node:process'

const app = Fastify({
logger: true,
})

// This is important! We need the raw request body for `verifyWebhook`.
// This is important! We need the raw request body for `verifyTruepicWebhook`.
await app.register(import('fastify-raw-body'))

app.post('/truepic/webhook', async (request) => {
try {
verifyWebhook({
verifyTruepicWebhook({
url: env.TRUEPIC_WEBHOOK_URL,
secret: env.TRUEPIC_WEBHOOK_SECRET,
header: request.headers['truepic-signature'],
Expand Down Expand Up @@ -200,14 +206,13 @@ npm run lint

### Releasing

After development is done in the `development` branch and is ready for release,
it should be merged into the `main` branch, where the latest release code lives.
[Release It!](https://github.com/release-it/release-it) is then used to
orchestrate the release process:
When the `development` branch is ready for release,
[Release It!](https://github.com/release-it/release-it) is used to orchestrate
the release process:

```bash
npm run release
```

Once the release process is complete, merge the `main` branch back into the
`development` branch. They should have the same history at this point.
Once the release process is complete, merge the `development` branch into the
`main` branch, which should always reflect the latest release.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@truepic/webhook-verifier",
"version": "0.1.0",
"version": "1.0.0",
"type": "module",
"description": "Verify webhooks from Truepic Vision or Lens in your Node.js app",
"homepage": "https://github.com/TRUEPIC/webhook-verifier-nodejs#readme",
Expand Down
11 changes: 9 additions & 2 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,13 @@ function verifySignature({ url, secret, body, timestamp, signature }) {
* @throws {TruepicWebhookVerifierError} If verification fails.
* @returns {true} If verification succeeds.
*/
function verifyWebhook({ url, secret, header, body, leewayMinutes = 5 }) {
function verifyTruepicWebhook({
url,
secret,
header,
body,
leewayMinutes = 5,
}) {
const { timestamp, signature } = parseHeader(header)

verifyTimestamp({
Expand All @@ -158,4 +164,5 @@ function verifyWebhook({ url, secret, header, body, leewayMinutes = 5 }) {
}

/** @module @truepic/webhook-verifier */
export { verifyWebhook, TruepicWebhookVerifierError }
export default verifyTruepicWebhook
export { TruepicWebhookVerifierError }
32 changes: 16 additions & 16 deletions src/main.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import assert from 'node:assert/strict'
import { describe, it } from 'node:test'
import { verifyWebhook, TruepicWebhookVerifierError } from './main.js'
import verifyTruepicWebhook, { TruepicWebhookVerifierError } from './main.js'

describe('verifyWebhook', () => {
describe('verifyTruepicWebhook', () => {
// Successful values.
const url = 'http://localhost:3001/webhook'
const secret = 'secret'
Expand All @@ -13,7 +13,7 @@ describe('verifyWebhook', () => {

it('returns `true` if verification is successful', () => {
assert.strictEqual(
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header,
Expand All @@ -28,7 +28,7 @@ describe('verifyWebhook', () => {
it('if the `header` is missing', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: null,
Expand All @@ -42,7 +42,7 @@ describe('verifyWebhook', () => {
it('if the `header` is empty', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: '',
Expand All @@ -56,7 +56,7 @@ describe('verifyWebhook', () => {
it('if the `header` cannot be parsed into timestamp and signature', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: 'bad',
Expand All @@ -72,7 +72,7 @@ describe('verifyWebhook', () => {
it('if the `header` is missing the timestamp (`t`)', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: 'b=bad,s=test',
Expand All @@ -86,7 +86,7 @@ describe('verifyWebhook', () => {
it('if the `header` timestamp (`t`) is empty', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: 't=,s=test',
Expand All @@ -100,7 +100,7 @@ describe('verifyWebhook', () => {
it('if the `header` timestamp (`t`) is not a number', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: 't=bad,s=test',
Expand All @@ -114,7 +114,7 @@ describe('verifyWebhook', () => {
it('if the `header` is missing the signature (`s`)', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: 't=123,b=bad',
Expand All @@ -128,7 +128,7 @@ describe('verifyWebhook', () => {
it('if the `header` signature (`s`) is empty', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: 't=123,s=',
Expand All @@ -142,7 +142,7 @@ describe('verifyWebhook', () => {
it('if the timestamp is not within allowed window', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header,
Expand All @@ -158,7 +158,7 @@ describe('verifyWebhook', () => {
it('if the `url` is not where the request was sent', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url: 'http://bad/webhook',
secret,
header,
Expand All @@ -172,7 +172,7 @@ describe('verifyWebhook', () => {
it('if the `timestamp` is not what was signed', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header: header.replace('t=1698259719', 't=1698259718'),
Expand All @@ -186,7 +186,7 @@ describe('verifyWebhook', () => {
it('if the `body` is not what was signed', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret,
header,
Expand All @@ -200,7 +200,7 @@ describe('verifyWebhook', () => {
it('if the `secret` is not what was used to sign', () => {
assert.throws(
() =>
verifyWebhook({
verifyTruepicWebhook({
url,
secret: 'bad',
header,
Expand Down

0 comments on commit 8e4b5f8

Please sign in to comment.