Ghost/ghost/core/test
Michael Barrett c285b0a0f1
🔒 Added timestamp to webhook signature hash (#20500)
refs
[ENG-1238](https://linear.app/tryghost/issue/ENG-1238/🔒-webhook-signatures-dont-include-timestamp-in-the-signature)

Added timestamp to the webhook signature hash to prevent replay attacks.
This is
a breaking change for webhook consumers as signature verification logic
will need to be updated to account for the timestamp in the hash, for
example:

```js
const crypto = require('crypto');

// Webhook secret from Ghost Admin
const WEBHOOK_SECRET = 'FOOBARBAZ'

// Sample incoming webhook request object
const req = {
    headers: {
        'x-ghost-signature': 'sha256=fc9749d5b3333109bd779f65d4b1b891576bc5c92febea3b1d186a7f946d0745, t=1719842984367'
    },
    body: {
        tag: {
            current: {
                id: '6682b8a8e10cc04306284330',
                name: 'test',
                slug: 'test',
                description: null,
                feature_image: null,
                visibility: 'public',
                og_image: null,
                og_title: null,
                og_description: null,
                twitter_image: null,
                twitter_title: null,
                twitter_description: null,
                meta_title: null,
                meta_description: null,
                codeinjection_head: null,
                codeinjection_foot: null,
                canonical_url: null,
                accent_color: null,
                created_at: '2024-07-01T14:09:44.000Z',
                updated_at: '2024-07-01T14:09:44.000Z',
                url: 'http://localhost:2368/404/'
            },
            previous: {}
        }
    }
};

// Get the request body as a JSON string
const reqBodyJSON = JSON.stringify(req.body);

// Extract the hash and timestamp from the x-ghost-signature header
const {sha256: hash, t: timestamp} = req.headers['x-ghost-signature']
    .split(', ')
    .map((x) => x.split('='))
    .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {})

// Recreate the hash using the secret, request body, and timestamp and compare it to the hash from the header
const isValid = crypto.createHmac('sha256', WEBHOOK_SECRET).update(`${reqBodyJSON}${timestamp}`).digest('hex') === hash

if (isValid) {
    console.log('Valid signature!')
}
```
2024-07-01 15:59:04 +01:00
..
e2e-api Center aligned feature image in email template (#20491) 2024-07-01 08:43:26 +00:00
e2e-browser Fixed browser tests 2024-07-01 14:49:20 +02:00
e2e-frontend Fixed offer not found case during Stripe checkout (#20322) 2024-06-04 10:27:45 +00:00
e2e-server Fixed race condition when updating member's last_seen_at timestamp (#20389) 2024-06-18 20:03:32 -07:00
e2e-webhooks Cleaned up lexicalEditor labs flag, switched Post model to lexical-by-default (#18607) 2023-10-23 17:51:34 +01:00
integration Center aligned feature image in email template (#20491) 2024-07-01 08:43:26 +00:00
regression Updated staff deletion logic (#20069) 2024-04-25 08:19:11 -05:00
unit 🔒 Added timestamp to webhook signature hash (#20500) 2024-07-01 15:59:04 +01:00
utils Fixed handling SVG files with missing tag 2024-06-18 14:41:11 +02:00
.eslintignore Added Source as the new default theme 2023-10-03 14:02:08 +02:00
.eslintrc.js Enforced more Mocha lint rules (#19720) 2024-04-16 09:37:06 +02:00