Latest Threat Research:SANDWORM_MODE: Shai-Hulud-Style npm Worm Hijacks CI Workflows and Poisons AI Toolchains.Details
Socket
Book a DemoInstallSign in
Socket

nuxt-webhook-validators

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

nuxt-webhook-validators - npm Package Compare versions

Comparing version
0.1.10
to
0.1.11
+8
dist/runtime/server/lib/validators/mailchannels.d.ts
import { type H3Event } from 'h3';
/**
* Validates MailChannels webhooks on the Edge
* @see {@link https://docs.mailchannels.net/email-api/advanced/delivery-events/#verifying-message-signatures}
* @param event H3Event
* @returns {boolean} `true` if the webhook is valid, `false` otherwise
*/
export declare const isValidMailChannelsWebhook: (event: H3Event) => Promise<boolean>;
import { getRequestHeaders, readRawBody } from "h3";
import { verifyPublicSignature, ED25519, validateSha256, stripPemHeaders } from "../helpers.js";
import { useRuntimeConfig } from "#imports";
const MAILCHANNELS_CONTENT_DIGEST = "content-digest";
const MAILCHANNELS_SIGNATURE = "signature";
const MAILCHANNELS_SIGNATURE_INPUT = "signature-input";
const DEFAULT_TOLERANCE = 300;
const validateContentDigest = async (header, body) => {
const match = header.match(/^(.*?)=:(.*?):$/);
if (!match) return false;
const [, algorithm, hash] = match;
const normalizedAlgorithm = algorithm.replace("-", "").toLowerCase();
if (!["sha256"].includes(normalizedAlgorithm)) return false;
return validateSha256(hash, body, { encoding: "base64" });
};
const extractSignature = (signatureHeader) => {
const signatureMatch = signatureHeader.match(/sig_\d+=:([^:]+):/);
return signatureMatch ? signatureMatch[1] : null;
};
const extractInputValues = (header) => {
const regex = /^(\w+)=\(([^)]+)\);created=(\d+);alg="([^"]+)";keyid="([^"]+)"$/;
const match = header.match(regex);
if (!match) return null;
return {
name: match[1],
timestamp: Number.parseInt(match[3], 10),
algorithm: match[4],
keyId: match[5]
};
};
export const isValidMailChannelsWebhook = async (event) => {
const config = useRuntimeConfig(event).webhook.mailchannels;
const headers = getRequestHeaders(event);
const body = await readRawBody(event);
const contentDigest = headers[MAILCHANNELS_CONTENT_DIGEST];
const messageSignature = headers[MAILCHANNELS_SIGNATURE];
const signatureInput = headers[MAILCHANNELS_SIGNATURE_INPUT];
if (!body || !contentDigest || !messageSignature || !signatureInput) return false;
if (!await validateContentDigest(contentDigest, body)) return false;
const signature = extractSignature(messageSignature);
if (!signature) return false;
const values = extractInputValues(signatureInput);
if (!values) return false;
const now = Math.floor(Date.now() / 1e3);
if (now - values.timestamp > DEFAULT_TOLERANCE) return false;
const signingString = `"content-digest": ${contentDigest}
"@signature-params": ("content-digest");created=${values.timestamp};alg="${values.algorithm}";keyid="${values.keyId}"`;
let publicKey = config.publicKey;
if (!publicKey) {
const publicKeyResponse = await $fetch("/tx/v1/webhook/public-key", {
baseURL: "https://api.mailchannels.net",
query: { id: values.keyId }
}).catch(() => null);
if (!publicKeyResponse) return false;
publicKey = publicKeyResponse.key;
}
publicKey = stripPemHeaders(publicKey);
const isValid = await verifyPublicSignature(publicKey, ED25519, signingString, signature, {
encoding: "base64",
format: "spki"
});
return isValid;
};
+2
-1

@@ -7,2 +7,3 @@ import * as _nuxt_schema from '@nuxt/schema';

export { type ModuleOptions, _default as default };
export { _default as default };
export type { ModuleOptions };

@@ -7,2 +7,3 @@ import * as _nuxt_schema from '@nuxt/schema';

export { type ModuleOptions, _default as default };
export { _default as default };
export type { ModuleOptions };

@@ -7,3 +7,3 @@ {

},
"version": "0.1.10",
"version": "0.1.11",
"builder": {

@@ -10,0 +10,0 @@ "@nuxt/module-builder": "0.8.4",

@@ -36,2 +36,5 @@ import { defineNuxtModule, createResolver, addServerImportsDir } from '@nuxt/kit';

});
runtimeConfig.webhook.mailchannels = defu(runtimeConfig.webhook.mailchannels, {
publicKey: ""
});
runtimeConfig.webhook.meta = defu(runtimeConfig.webhook.meta, {

@@ -38,0 +41,0 @@ appSecret: ""

@@ -32,1 +32,5 @@ import { type webcrypto } from 'node:crypto';

export declare const stripPemHeaders: (pem: string) => string;
export declare const sha256: (payload: string | object, encoding?: BufferEncoding) => Promise<string>;
export declare const validateSha256: (hash: string, payload: string, options?: Partial<{
encoding: BufferEncoding;
}>) => Promise<boolean>;

@@ -44,1 +44,9 @@ import { subtle } from "node:crypto";

export const stripPemHeaders = (pem) => pem.replace(/-----[^-]+-----|\s/g, "");
export const sha256 = async (payload, encoding) => {
const buffer = typeof payload === "object" ? Buffer.from(JSON.stringify(payload)) : encoder.encode(payload);
const signatureBuffer = await subtle.digest(HMAC_SHA256.hash, buffer);
return Buffer.from(signatureBuffer).toString(encoding ?? "hex");
};
export const validateSha256 = async (hash, payload, options) => {
return hash === await sha256(payload, options?.encoding);
};

@@ -1,5 +0,3 @@

import { subtle } from "node:crypto";
import { Buffer } from "node:buffer";
import { getRequestHeaders, readRawBody } from "h3";
import { encoder, HMAC_SHA256, ensureConfiguration } from "../helpers.js";
import { ensureConfiguration, sha256 } from "../helpers.js";
const NUXTHUB_SIGNATURE = "x-nuxthub-signature";

@@ -13,6 +11,5 @@ export const isValidNuxtHubWebhook = async (event) => {

const payload = body + config.secretKey;
const signatureBuffer = await subtle.digest(HMAC_SHA256.hash, encoder.encode(payload));
const signature = Buffer.from(signatureBuffer).toString("hex");
const signature = await sha256(payload);
return signature === webhookSignature;
};
export const isValidNuxthubWebhook = isValidNuxtHubWebhook;

@@ -1,1 +0,7 @@

export { type ModuleOptions, default } from './module.js'
import type { NuxtModule } from '@nuxt/schema'
import type { default as Module } from './module.js'
export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
export { default } from './module.js'

@@ -1,1 +0,7 @@

export { type ModuleOptions, default } from './module'
import type { NuxtModule } from '@nuxt/schema'
import type { default as Module } from './module'
export type ModuleOptions = typeof Module extends NuxtModule<infer O> ? Partial<O> : Record<string, any>
export { default } from './module'
{
"name": "nuxt-webhook-validators",
"version": "0.1.10",
"version": "0.1.11",
"description": "A simple nuxt module that works on the edge to easily validate incoming webhooks from different services.",

@@ -47,3 +47,3 @@ "keywords": [

"dependencies": {
"@nuxt/kit": "^3.16.0",
"@nuxt/kit": "^3.16.1",
"defu": "^6.1.4",

@@ -53,16 +53,16 @@ "scule": "^1.3.0"

"devDependencies": {
"@nuxt/devtools": "^2.2.1",
"@nuxt/eslint-config": "^1.1.0",
"@nuxt/devtools": "^2.3.1",
"@nuxt/eslint-config": "^1.2.0",
"@nuxt/module-builder": "^0.8.4",
"@nuxt/schema": "^3.16.0",
"@nuxt/test-utils": "^3.17.1",
"@types/node": "^22.13.9",
"@nuxt/schema": "^3.16.1",
"@nuxt/test-utils": "^3.17.2",
"@types/node": "^22.13.10",
"changelogen": "^0.6.1",
"eslint": "^9.22.0",
"nuxt": "^3.16.0",
"nuxt": "^3.16.1",
"typescript": "^5.8.2",
"vitest": "^3.0.8",
"vitest": "^3.0.9",
"vue-tsc": "^2.2.8"
},
"packageManager": "pnpm@10.6.1",
"packageManager": "pnpm@10.6.5",
"changelog": {

@@ -69,0 +69,0 @@ "repo": {

@@ -17,3 +17,3 @@ ![webhook-validators](https://github.com/Yizack/nuxt-webhook-validators/assets/16264115/56cded71-46b2-4895-8732-484ab6df5181)

- 17 [Webhook validators](#supported-webhook-validators)
- 18 [Webhook validators](#supported-webhook-validators)
- Works on the edge

@@ -89,2 +89,3 @@ - Exposed [Server utils](#server-utils)

- Kick
- MailChannels
- Meta

@@ -91,0 +92,0 @@ - NuxtHub