nuxt-webhook-validators
Advanced tools
+1
-1
@@ -7,3 +7,3 @@ { | ||
| }, | ||
| "version": "0.2.6", | ||
| "version": "0.2.7", | ||
| "builder": { | ||
@@ -10,0 +10,0 @@ "@nuxt/module-builder": "1.0.2", |
@@ -36,1 +36,3 @@ import { type webcrypto } from 'node:crypto'; | ||
| }>) => Promise<boolean>; | ||
| export declare const readRawBodyClone: (event: H3Event) => Promise<string | undefined>; | ||
| export declare const readBodyClone: <T = unknown>(event: H3Event) => Promise<T | undefined>; |
| import { subtle } from "node:crypto"; | ||
| import { Buffer } from "node:buffer"; | ||
| import { snakeCase } from "scule"; | ||
| import { createError } from "h3"; | ||
| import { createError, readRawBody } from "h3"; | ||
| import { useRuntimeConfig } from "#imports"; | ||
@@ -39,3 +39,3 @@ export const HMAC_SHA256 = { name: "HMAC", hash: "SHA-256" }; | ||
| throw createError({ | ||
| statusCode: 500, | ||
| status: 500, | ||
| message: errorMessage | ||
@@ -53,1 +53,13 @@ }); | ||
| }; | ||
| export const readRawBodyClone = async (event) => { | ||
| const hasClone = "clone" in (event.req ?? {}); | ||
| const body = hasClone ? await event.req.clone().text() : await readRawBody(event); | ||
| if (!hasClone) { | ||
| event._requestBody = body; | ||
| } | ||
| return body; | ||
| }; | ||
| export const readBodyClone = async (event) => { | ||
| const rawBody = await readRawBodyClone(event); | ||
| return rawBody ? JSON.parse(rawBody) : void 0; | ||
| }; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { verifyPublicSignature, ED25519, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { verifyPublicSignature, ED25519, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const DISCORD_SIGNATURE = "x-signature-ed25519"; | ||
@@ -8,3 +8,3 @@ const DISCORD_SIGNATURE_TIMESTAMP = "x-signature-timestamp"; | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const webhookSignature = headers[DISCORD_SIGNATURE]; | ||
@@ -11,0 +11,0 @@ const webhookTimestamp = headers[DISCORD_SIGNATURE_TIMESTAMP]; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const DROPBOX_SIGNATURE = "X-Dropbox-Signature".toLowerCase(); | ||
@@ -7,3 +7,3 @@ export const isValidDropboxWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const webhookSignature = headers[DROPBOX_SIGNATURE]; | ||
@@ -10,0 +10,0 @@ if (!body || !webhookSignature) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const FOURTHWALL_SIGNATURE = "X-Fourthwall-Hmac-SHA256".toLowerCase(); | ||
@@ -7,3 +7,3 @@ export const isValidFourthwallWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const webhookSignature = headers[FOURTHWALL_SIGNATURE]; | ||
@@ -10,0 +10,0 @@ if (!body || !webhookSignature) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const GITHUB_SIGNATURE = "X-Hub-Signature-256".toLowerCase(); | ||
@@ -7,3 +7,3 @@ export const isValidGitHubWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const header = headers[GITHUB_SIGNATURE]; | ||
@@ -10,0 +10,0 @@ if (!body || !header) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const GITLAB_TOKEN = "X-Gitlab-Token".toLowerCase(); | ||
@@ -7,3 +7,3 @@ export const isValidGitLabWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const header = headers[GITLAB_TOKEN]; | ||
@@ -10,0 +10,0 @@ if (!body || !header) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const HEROKU_HMAC = "Heroku-Webhook-Hmac-SHA256".toLowerCase(); | ||
@@ -7,3 +7,3 @@ export const isValidHerokuWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const header = headers[HEROKU_HMAC]; | ||
@@ -10,0 +10,0 @@ if (!body || !header) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const DEFAULT_TOLERANCE = 300; | ||
@@ -22,3 +22,3 @@ const HYGRAPH_SIGNATURE = "gcms-signature"; | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const hygraphSignature = headers[HYGRAPH_SIGNATURE]; | ||
@@ -25,0 +25,0 @@ if (!body || !hygraphSignature) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { verifyPublicSignature, RSASSA_PKCS1_v1_5_SHA256, stripPemHeaders } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { verifyPublicSignature, RSASSA_PKCS1_v1_5_SHA256, stripPemHeaders, readRawBodyClone } from "../helpers.js"; | ||
| import { useRuntimeConfig } from "#imports"; | ||
@@ -19,3 +19,3 @@ const KICK_MESSAGE_ID = "Kick-Event-Message-Id".toLowerCase(); | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const messageId = headers[KICK_MESSAGE_ID]; | ||
@@ -22,0 +22,0 @@ const messageTimestamp = headers[KICK_MESSAGE_TIMESTAMP]; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { verifyPublicSignature, ED25519, validateSha256, stripPemHeaders } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { verifyPublicSignature, ED25519, validateSha256, stripPemHeaders, readRawBodyClone } from "../helpers.js"; | ||
| import { useRuntimeConfig } from "#imports"; | ||
@@ -35,3 +35,3 @@ const MAILCHANNELS_CONTENT_DIGEST = "content-digest"; | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const contentDigest = headers[MAILCHANNELS_CONTENT_DIGEST]; | ||
@@ -38,0 +38,0 @@ const messageSignature = headers[MAILCHANNELS_SIGNATURE]; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const META_SIGNATURE = "X-Hub-Signature-256".toLowerCase(); | ||
@@ -7,3 +7,3 @@ export const isValidMetaWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const signatureHeader = headers[META_SIGNATURE]; | ||
@@ -10,0 +10,0 @@ if (!signatureHeader) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { ensureConfiguration, sha256 } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { ensureConfiguration, readRawBodyClone, sha256 } from "../helpers.js"; | ||
| const NUXTHUB_SIGNATURE = "x-nuxthub-signature"; | ||
@@ -7,3 +7,3 @@ export const isValidNuxtHubWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const webhookSignature = headers[NUXTHUB_SIGNATURE]; | ||
@@ -10,0 +10,0 @@ if (!body || !webhookSignature) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const MAX_VALID_TIME_DIFFERENCE = 5; | ||
@@ -22,3 +22,3 @@ const PADDLE_SIGNATURE = "paddle-signature"; | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const paddleSignature = headers[PADDLE_SIGNATURE]; | ||
@@ -25,0 +25,0 @@ if (!body || !paddleSignature) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readBody } from "h3"; | ||
| import { ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { ensureConfiguration, readBodyClone } from "../helpers.js"; | ||
| const baseAPI = import.meta.dev ? "https://api-m.sandbox.paypal.com/v1" : "https://api-m.paypal.com/v1"; | ||
@@ -7,3 +7,3 @@ export const isValidPaypalWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readBody(event); | ||
| const body = await readBodyClone(event); | ||
| if (!body || !headers) return false; | ||
@@ -10,0 +10,0 @@ const basicAuth = btoa(`${config.clientId}:${config.secretKey}`); |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const POLAR_SIGNATURE_ID = "webhook-id"; | ||
@@ -10,3 +10,3 @@ const POLAR_SIGNATURE = "webhook-signature"; | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const webhookId = headers[POLAR_SIGNATURE_ID]; | ||
@@ -13,0 +13,0 @@ const webhookSignature = headers[POLAR_SIGNATURE]; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const SHOPIFY_SIGNATURE = "X-Shopify-Hmac-Sha256".toLowerCase(); | ||
@@ -7,3 +7,3 @@ export const isValidShopifyWebhook = async (event) => { | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const webhookSignature = headers[SHOPIFY_SIGNATURE]; | ||
@@ -10,0 +10,0 @@ if (!body || !webhookSignature) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const SLACK_SIGNATURE = "X-Slack-Signature".toLowerCase(); | ||
@@ -9,3 +9,3 @@ const SLACK_TIMESTAMP = "X-Slack-Request-Timestamp".toLowerCase(); | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const fullSignature = headers[SLACK_SIGNATURE]; | ||
@@ -12,0 +12,0 @@ const timestamp = headers[SLACK_TIMESTAMP]; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const DEFAULT_TOLERANCE = 300; | ||
@@ -22,3 +22,3 @@ const STRIPE_SIGNATURE = "Stripe-Signature".toLowerCase(); | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const stripeSignature = headers[STRIPE_SIGNATURE]; | ||
@@ -25,0 +25,0 @@ if (!body || !stripeSignature) return false; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const SVIX_SIGNATURE_ID = "svix-id"; | ||
@@ -9,3 +9,3 @@ const SVIX_SIGNATURE = "svix-signature"; | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const webhookId = headers[SVIX_SIGNATURE_ID]; | ||
@@ -12,0 +12,0 @@ const webhookSignature = headers[SVIX_SIGNATURE]; |
@@ -1,3 +0,3 @@ | ||
| import { getRequestHeaders, readRawBody } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration } from "../helpers.js"; | ||
| import { getRequestHeaders } from "h3"; | ||
| import { computeSignature, HMAC_SHA256, ensureConfiguration, readRawBodyClone } from "../helpers.js"; | ||
| const TWITCH_MESSAGE_ID = "Twitch-Eventsub-Message-Id".toLowerCase(); | ||
@@ -10,3 +10,3 @@ const TWITCH_MESSAGE_TIMESTAMP = "Twitch-Eventsub-Message-Timestamp".toLowerCase(); | ||
| const headers = getRequestHeaders(event); | ||
| const body = await readRawBody(event); | ||
| const body = await readRawBodyClone(event); | ||
| const message_id = headers[TWITCH_MESSAGE_ID]; | ||
@@ -13,0 +13,0 @@ const message_timestamp = headers[TWITCH_MESSAGE_TIMESTAMP]; |
+10
-10
| { | ||
| "name": "nuxt-webhook-validators", | ||
| "version": "0.2.6", | ||
| "version": "0.2.7", | ||
| "description": "A simple nuxt module that works on the edge to easily validate incoming webhooks from different services.", | ||
@@ -33,3 +33,3 @@ "keywords": [ | ||
| "dependencies": { | ||
| "@nuxt/kit": "^4.2.2", | ||
| "@nuxt/kit": "^4.3.0", | ||
| "defu": "^6.1.4", | ||
@@ -40,13 +40,13 @@ "scule": "^1.3.0" | ||
| "@nuxt/devtools": "^3.1.1", | ||
| "@nuxt/eslint-config": "^1.11.0", | ||
| "@nuxt/eslint-config": "^1.13.0", | ||
| "@nuxt/module-builder": "^1.0.2", | ||
| "@nuxt/schema": "^4.2.2", | ||
| "@nuxt/test-utils": "^3.21.0", | ||
| "@types/node": "^24.10.2", | ||
| "@nuxt/schema": "^4.3.0", | ||
| "@nuxt/test-utils": "^3.23.0", | ||
| "@types/node": "^25.0.10", | ||
| "changelogen": "^0.6.2", | ||
| "eslint": "^9.39.1", | ||
| "nuxt": "^4.2.2", | ||
| "eslint": "^9.39.2", | ||
| "nuxt": "^4.3.0", | ||
| "typescript": "^5.9.3", | ||
| "vitest": "^4.0.15", | ||
| "vue-tsc": "^3.1.8" | ||
| "vitest": "^4.0.18", | ||
| "vue-tsc": "^3.2.3" | ||
| }, | ||
@@ -53,0 +53,0 @@ "scripts": { |
+24
-3
@@ -105,7 +105,9 @@  | ||
| ## Example | ||
| ## Examples | ||
| ### Basic | ||
| Validate a GitHub webhook in a server API route. | ||
| `~/server/api/webhooks/github.post.ts` | ||
| `~~/server/api/webhooks/github.post.ts` | ||
@@ -117,3 +119,3 @@ ```js | ||
| if (!isValidWebhook) { | ||
| throw createError({ statusCode: 401, message: 'Unauthorized: webhook is not valid' }) | ||
| throw createError({ status: 401, message: 'Unauthorized: webhook is not valid' }) | ||
| } | ||
@@ -127,2 +129,21 @@ | ||
| ## Reading the request body | ||
| Make sure to read the body after validating the webhook, as most validators rely on a caching/cloning of the raw body to compute the signature and validate the webhook. | ||
| ```js | ||
| export default defineEventHandler(async (event) => { | ||
| const isValidWebhook = await isValidGitHubWebhook(event) | ||
| if (!isValidWebhook) { | ||
| throw createError({ status: 401, message: 'Unauthorized: webhook is not valid' }) | ||
| } | ||
| // Make sure to read the body after validating the webhook | ||
| const body = await readBody(event) | ||
| return { isValidWebhook } | ||
| }) | ||
| ``` | ||
| # Development | ||
@@ -129,0 +150,0 @@ |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
45109
3.12%826
1.72%190
12.43%Updated