hooklatch 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +438 -2
- package/dist/adapters/cloudflare.d.ts +11 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +27 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/express.d.ts +9 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +63 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/hono.d.ts +12 -0
- package/dist/adapters/hono.d.ts.map +1 -0
- package/dist/adapters/hono.js +28 -0
- package/dist/adapters/hono.js.map +1 -0
- package/dist/adapters/nextjs.d.ts +6 -0
- package/dist/adapters/nextjs.d.ts.map +1 -0
- package/dist/adapters/nextjs.js +56 -0
- package/dist/adapters/nextjs.js.map +1 -0
- package/dist/adapters/shared.d.ts +8 -0
- package/dist/adapters/shared.d.ts.map +1 -0
- package/dist/adapters/shared.js +53 -0
- package/dist/adapters/shared.js.map +1 -0
- package/dist/cjs/adapters/cloudflare.cjs +29 -0
- package/dist/cjs/adapters/cloudflare.d.cts +10 -0
- package/dist/cjs/adapters/express.cjs +65 -0
- package/dist/cjs/adapters/express.d.cts +8 -0
- package/dist/cjs/adapters/hono.cjs +30 -0
- package/dist/cjs/adapters/hono.d.cts +11 -0
- package/dist/cjs/adapters/nextjs.cjs +58 -0
- package/dist/cjs/adapters/nextjs.d.cts +5 -0
- package/dist/cjs/adapters/shared.cjs +58 -0
- package/dist/cjs/adapters/shared.d.cts +7 -0
- package/dist/cjs/core.cjs +30 -0
- package/dist/cjs/core.d.cts +5 -0
- package/dist/cjs/diagnostics/catalog.cjs +341 -0
- package/dist/cjs/diagnostics/catalog.d.cts +19 -0
- package/dist/cjs/diagnostics/index.cjs +90 -0
- package/dist/cjs/diagnostics/index.d.cts +5 -0
- package/dist/cjs/diagnostics/types.cjs +2 -0
- package/dist/cjs/diagnostics/types.d.cts +20 -0
- package/dist/cjs/errors.cjs +13 -0
- package/dist/cjs/errors.d.cts +6 -0
- package/dist/cjs/index.cjs +9 -0
- package/dist/cjs/index.d.cts +3 -0
- package/dist/cjs/internal/body.cjs +50 -0
- package/dist/cjs/internal/body.d.cts +2 -0
- package/dist/cjs/internal/bytes.cjs +43 -0
- package/dist/cjs/internal/bytes.d.cts +4 -0
- package/dist/cjs/internal/crypto.cjs +52 -0
- package/dist/cjs/internal/crypto.d.cts +3 -0
- package/dist/cjs/internal/headers.cjs +72 -0
- package/dist/cjs/internal/headers.d.cts +2 -0
- package/dist/cjs/internal/options.cjs +130 -0
- package/dist/cjs/internal/options.d.cts +7 -0
- package/dist/cjs/internal/prepare.cjs +11 -0
- package/dist/cjs/internal/prepare.d.cts +4 -0
- package/dist/cjs/internal/provider-options.cjs +72 -0
- package/dist/cjs/internal/provider-options.d.cts +6 -0
- package/dist/cjs/internal/provider.cjs +2 -0
- package/dist/cjs/internal/provider.d.cts +39 -0
- package/dist/cjs/internal/verification.cjs +71 -0
- package/dist/cjs/internal/verification.d.cts +12 -0
- package/dist/cjs/providers/clerk.cjs +15 -0
- package/dist/cjs/providers/clerk.d.cts +9 -0
- package/dist/cjs/providers/dodo-payments.cjs +26 -0
- package/dist/cjs/providers/dodo-payments.d.cts +20 -0
- package/dist/cjs/providers/doppler.cjs +71 -0
- package/dist/cjs/providers/doppler.d.cts +37 -0
- package/dist/cjs/providers/fal.cjs +80 -0
- package/dist/cjs/providers/fal.d.cts +13 -0
- package/dist/cjs/providers/github.cjs +52 -0
- package/dist/cjs/providers/github.d.cts +7 -0
- package/dist/cjs/providers/gitlab.cjs +26 -0
- package/dist/cjs/providers/gitlab.d.cts +20 -0
- package/dist/cjs/providers/grafana.cjs +180 -0
- package/dist/cjs/providers/grafana.d.cts +35 -0
- package/dist/cjs/providers/index.cjs +110 -0
- package/dist/cjs/providers/index.d.cts +26 -0
- package/dist/cjs/providers/lemon-squeezy.cjs +36 -0
- package/dist/cjs/providers/lemon-squeezy.d.cts +15 -0
- package/dist/cjs/providers/linear.cjs +58 -0
- package/dist/cjs/providers/linear.d.cts +36 -0
- package/dist/cjs/providers/paddle.cjs +105 -0
- package/dist/cjs/providers/paddle.d.cts +14 -0
- package/dist/cjs/providers/polar.cjs +80 -0
- package/dist/cjs/providers/polar.d.cts +25 -0
- package/dist/cjs/providers/razorpay.cjs +43 -0
- package/dist/cjs/providers/razorpay.d.cts +19 -0
- package/dist/cjs/providers/replicate.cjs +25 -0
- package/dist/cjs/providers/replicate.d.cts +19 -0
- package/dist/cjs/providers/sanity.cjs +161 -0
- package/dist/cjs/providers/sanity.d.cts +38 -0
- package/dist/cjs/providers/sentry.cjs +47 -0
- package/dist/cjs/providers/sentry.d.cts +25 -0
- package/dist/cjs/providers/shared.cjs +49 -0
- package/dist/cjs/providers/shared.d.cts +14 -0
- package/dist/cjs/providers/shopify.cjs +40 -0
- package/dist/cjs/providers/shopify.d.cts +8 -0
- package/dist/cjs/providers/standard-webhooks-protocol.cjs +97 -0
- package/dist/cjs/providers/standard-webhooks-protocol.d.cts +14 -0
- package/dist/cjs/providers/standard-webhooks.cjs +15 -0
- package/dist/cjs/providers/standard-webhooks.d.cts +9 -0
- package/dist/cjs/providers/stripe.cjs +81 -0
- package/dist/cjs/providers/stripe.d.cts +7 -0
- package/dist/cjs/providers/svix.cjs +63 -0
- package/dist/cjs/providers/svix.d.cts +9 -0
- package/dist/cjs/providers/vercel.cjs +39 -0
- package/dist/cjs/providers/vercel.d.cts +15 -0
- package/dist/cjs/providers/workos.cjs +120 -0
- package/dist/cjs/providers/workos.d.cts +32 -0
- package/dist/cjs/types.cjs +2 -0
- package/dist/cjs/types.d.cts +68 -0
- package/dist/cli/args.d.ts +86 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +358 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/filesystem.d.ts +15 -0
- package/dist/cli/filesystem.d.ts.map +1 -0
- package/dist/cli/filesystem.js +170 -0
- package/dist/cli/filesystem.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +64 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/templates.d.ts +13 -0
- package/dist/cli/templates.d.ts.map +1 -0
- package/dist/cli/templates.js +297 -0
- package/dist/cli/templates.js.map +1 -0
- package/dist/core.d.ts +6 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +26 -0
- package/dist/core.js.map +1 -0
- package/dist/diagnostics/catalog.d.ts +20 -0
- package/dist/diagnostics/catalog.d.ts.map +1 -0
- package/dist/diagnostics/catalog.js +335 -0
- package/dist/diagnostics/catalog.js.map +1 -0
- package/dist/diagnostics/index.d.ts +6 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +88 -0
- package/dist/diagnostics/index.js.map +1 -0
- package/dist/diagnostics/types.d.ts +21 -0
- package/dist/diagnostics/types.d.ts.map +1 -0
- package/dist/diagnostics/types.js +2 -0
- package/dist/diagnostics/types.js.map +1 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +10 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/body.d.ts +3 -0
- package/dist/internal/body.d.ts.map +1 -0
- package/dist/internal/body.js +48 -0
- package/dist/internal/body.js.map +1 -0
- package/dist/internal/bytes.d.ts +5 -0
- package/dist/internal/bytes.d.ts.map +1 -0
- package/dist/internal/bytes.js +38 -0
- package/dist/internal/bytes.js.map +1 -0
- package/dist/internal/crypto.d.ts +4 -0
- package/dist/internal/crypto.d.ts.map +1 -0
- package/dist/internal/crypto.js +49 -0
- package/dist/internal/crypto.js.map +1 -0
- package/dist/internal/headers.d.ts +3 -0
- package/dist/internal/headers.d.ts.map +1 -0
- package/dist/internal/headers.js +70 -0
- package/dist/internal/headers.js.map +1 -0
- package/dist/internal/options.d.ts +8 -0
- package/dist/internal/options.d.ts.map +1 -0
- package/dist/internal/options.js +128 -0
- package/dist/internal/options.js.map +1 -0
- package/dist/internal/prepare.d.ts +5 -0
- package/dist/internal/prepare.d.ts.map +1 -0
- package/dist/internal/prepare.js +9 -0
- package/dist/internal/prepare.js.map +1 -0
- package/dist/internal/provider-options.d.ts +7 -0
- package/dist/internal/provider-options.d.ts.map +1 -0
- package/dist/internal/provider-options.js +69 -0
- package/dist/internal/provider-options.js.map +1 -0
- package/dist/internal/provider.d.ts +40 -0
- package/dist/internal/provider.d.ts.map +1 -0
- package/dist/internal/provider.js +2 -0
- package/dist/internal/provider.js.map +1 -0
- package/dist/internal/verification.d.ts +13 -0
- package/dist/internal/verification.d.ts.map +1 -0
- package/dist/internal/verification.js +66 -0
- package/dist/internal/verification.js.map +1 -0
- package/dist/node/adapters/cloudflare.d.ts +1 -0
- package/dist/node/adapters/cloudflare.js +3 -0
- package/dist/node/adapters/express.d.ts +1 -0
- package/dist/node/adapters/express.js +3 -0
- package/dist/node/adapters/hono.d.ts +1 -0
- package/dist/node/adapters/hono.js +3 -0
- package/dist/node/adapters/nextjs.d.ts +1 -0
- package/dist/node/adapters/nextjs.js +3 -0
- package/dist/node/diagnostics/index.d.ts +1 -0
- package/dist/node/diagnostics/index.js +3 -0
- package/dist/node/index.d.ts +1 -0
- package/dist/node/index.js +6 -0
- package/dist/providers/clerk.d.ts +10 -0
- package/dist/providers/clerk.d.ts.map +1 -0
- package/dist/providers/clerk.js +13 -0
- package/dist/providers/clerk.js.map +1 -0
- package/dist/providers/dodo-payments.d.ts +21 -0
- package/dist/providers/dodo-payments.d.ts.map +1 -0
- package/dist/providers/dodo-payments.js +24 -0
- package/dist/providers/dodo-payments.js.map +1 -0
- package/dist/providers/doppler.d.ts +38 -0
- package/dist/providers/doppler.d.ts.map +1 -0
- package/dist/providers/doppler.js +69 -0
- package/dist/providers/doppler.js.map +1 -0
- package/dist/providers/fal.d.ts +14 -0
- package/dist/providers/fal.d.ts.map +1 -0
- package/dist/providers/fal.js +78 -0
- package/dist/providers/fal.js.map +1 -0
- package/dist/providers/github.d.ts +8 -0
- package/dist/providers/github.d.ts.map +1 -0
- package/dist/providers/github.js +50 -0
- package/dist/providers/github.js.map +1 -0
- package/dist/providers/gitlab.d.ts +21 -0
- package/dist/providers/gitlab.d.ts.map +1 -0
- package/dist/providers/gitlab.js +24 -0
- package/dist/providers/gitlab.js.map +1 -0
- package/dist/providers/grafana.d.ts +36 -0
- package/dist/providers/grafana.d.ts.map +1 -0
- package/dist/providers/grafana.js +178 -0
- package/dist/providers/grafana.js.map +1 -0
- package/dist/providers/index.d.ts +27 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +89 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/lemon-squeezy.d.ts +16 -0
- package/dist/providers/lemon-squeezy.d.ts.map +1 -0
- package/dist/providers/lemon-squeezy.js +34 -0
- package/dist/providers/lemon-squeezy.js.map +1 -0
- package/dist/providers/linear.d.ts +37 -0
- package/dist/providers/linear.d.ts.map +1 -0
- package/dist/providers/linear.js +56 -0
- package/dist/providers/linear.js.map +1 -0
- package/dist/providers/paddle.d.ts +15 -0
- package/dist/providers/paddle.d.ts.map +1 -0
- package/dist/providers/paddle.js +103 -0
- package/dist/providers/paddle.js.map +1 -0
- package/dist/providers/polar.d.ts +26 -0
- package/dist/providers/polar.d.ts.map +1 -0
- package/dist/providers/polar.js +78 -0
- package/dist/providers/polar.js.map +1 -0
- package/dist/providers/razorpay.d.ts +20 -0
- package/dist/providers/razorpay.d.ts.map +1 -0
- package/dist/providers/razorpay.js +41 -0
- package/dist/providers/razorpay.js.map +1 -0
- package/dist/providers/replicate.d.ts +20 -0
- package/dist/providers/replicate.d.ts.map +1 -0
- package/dist/providers/replicate.js +23 -0
- package/dist/providers/replicate.js.map +1 -0
- package/dist/providers/sanity.d.ts +39 -0
- package/dist/providers/sanity.d.ts.map +1 -0
- package/dist/providers/sanity.js +159 -0
- package/dist/providers/sanity.js.map +1 -0
- package/dist/providers/sentry.d.ts +26 -0
- package/dist/providers/sentry.d.ts.map +1 -0
- package/dist/providers/sentry.js +45 -0
- package/dist/providers/sentry.js.map +1 -0
- package/dist/providers/shared.d.ts +15 -0
- package/dist/providers/shared.d.ts.map +1 -0
- package/dist/providers/shared.js +40 -0
- package/dist/providers/shared.js.map +1 -0
- package/dist/providers/shopify.d.ts +9 -0
- package/dist/providers/shopify.d.ts.map +1 -0
- package/dist/providers/shopify.js +38 -0
- package/dist/providers/shopify.js.map +1 -0
- package/dist/providers/standard-webhooks-protocol.d.ts +15 -0
- package/dist/providers/standard-webhooks-protocol.d.ts.map +1 -0
- package/dist/providers/standard-webhooks-protocol.js +95 -0
- package/dist/providers/standard-webhooks-protocol.js.map +1 -0
- package/dist/providers/standard-webhooks.d.ts +10 -0
- package/dist/providers/standard-webhooks.d.ts.map +1 -0
- package/dist/providers/standard-webhooks.js +13 -0
- package/dist/providers/standard-webhooks.js.map +1 -0
- package/dist/providers/stripe.d.ts +8 -0
- package/dist/providers/stripe.d.ts.map +1 -0
- package/dist/providers/stripe.js +79 -0
- package/dist/providers/stripe.js.map +1 -0
- package/dist/providers/svix.d.ts +10 -0
- package/dist/providers/svix.d.ts.map +1 -0
- package/dist/providers/svix.js +61 -0
- package/dist/providers/svix.js.map +1 -0
- package/dist/providers/vercel.d.ts +16 -0
- package/dist/providers/vercel.d.ts.map +1 -0
- package/dist/providers/vercel.js +37 -0
- package/dist/providers/vercel.js.map +1 -0
- package/dist/providers/workos.d.ts +33 -0
- package/dist/providers/workos.d.ts.map +1 -0
- package/dist/providers/workos.js +118 -0
- package/dist/providers/workos.js.map +1 -0
- package/dist/types.d.ts +69 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +200 -4
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.verifyHmac = verifyHmac;
|
|
4
|
+
exports.verifyEd25519 = verifyEd25519;
|
|
5
|
+
const errors_js_1 = require("../errors.cjs");
|
|
6
|
+
function isUnsupported(error) {
|
|
7
|
+
return error !== null && typeof error === 'object' && 'name' in error && error.name === 'NotSupportedError';
|
|
8
|
+
}
|
|
9
|
+
async function verifyHmac(keys, signatures, content, policy = { kind: 'hmac', hash: 'SHA-256', signatureBytes: 32 }) {
|
|
10
|
+
const subtle = globalThis.crypto?.subtle;
|
|
11
|
+
if (!subtle)
|
|
12
|
+
throw new errors_js_1.HooklatchError('crypto-unavailable', 'Web Crypto is unavailable.');
|
|
13
|
+
try {
|
|
14
|
+
for (const bytes of keys) {
|
|
15
|
+
const key = await subtle.importKey('raw', new Uint8Array(bytes), { name: 'HMAC', hash: policy.hash }, false, ['verify']);
|
|
16
|
+
for (const signature of signatures) {
|
|
17
|
+
if (signature.byteLength !== policy.signatureBytes)
|
|
18
|
+
continue;
|
|
19
|
+
if (await subtle.verify('HMAC', key, new Uint8Array(signature), new Uint8Array(content)))
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (isUnsupported(error))
|
|
27
|
+
throw new errors_js_1.HooklatchError('crypto-unavailable', 'Web Crypto is unavailable.');
|
|
28
|
+
throw new errors_js_1.HooklatchError('internal', 'Cryptographic verification failed.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function verifyEd25519(keys, signatures, content) {
|
|
32
|
+
const subtle = globalThis.crypto?.subtle;
|
|
33
|
+
if (!subtle)
|
|
34
|
+
throw new errors_js_1.HooklatchError('crypto-unavailable', 'Web Crypto is unavailable.');
|
|
35
|
+
try {
|
|
36
|
+
for (const bytes of keys) {
|
|
37
|
+
const key = await subtle.importKey('raw', new Uint8Array(bytes), 'Ed25519', false, ['verify']);
|
|
38
|
+
for (const signature of signatures) {
|
|
39
|
+
if (signature.byteLength !== 64)
|
|
40
|
+
continue;
|
|
41
|
+
if (await subtle.verify('Ed25519', key, new Uint8Array(signature), new Uint8Array(content)))
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (isUnsupported(error))
|
|
49
|
+
throw new errors_js_1.HooklatchError('crypto-unavailable', 'Web Crypto is unavailable.');
|
|
50
|
+
throw new errors_js_1.HooklatchError('internal', 'Cryptographic verification failed.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { HmacPolicy } from './provider.cjs';
|
|
2
|
+
export declare function verifyHmac(keys: readonly Uint8Array[], signatures: readonly Uint8Array[], content: Uint8Array, policy?: HmacPolicy): Promise<boolean>;
|
|
3
|
+
export declare function verifyEd25519(keys: readonly Uint8Array[], signatures: readonly Uint8Array[], content: Uint8Array): Promise<boolean>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeHeaders = normalizeHeaders;
|
|
4
|
+
const errors_js_1 = require("../errors.cjs");
|
|
5
|
+
function isHeaderName(name) {
|
|
6
|
+
return typeof name === 'string' && /^[!#$%&'*+.^_`|~\da-z-]+$/i.test(name);
|
|
7
|
+
}
|
|
8
|
+
function isHeaderValue(value, opaque) {
|
|
9
|
+
return typeof value === 'string' && (opaque || !/[\r\n\0]/.test(value));
|
|
10
|
+
}
|
|
11
|
+
function isHeaderRecord(input) {
|
|
12
|
+
return input !== null && typeof input === 'object';
|
|
13
|
+
}
|
|
14
|
+
function isHeaderValues(value) {
|
|
15
|
+
return Array.isArray(value);
|
|
16
|
+
}
|
|
17
|
+
/** Opaque values are used only by fal so its signed-field grammar owns rejection. */
|
|
18
|
+
const FAL_HEADER_NAMES = new Set([
|
|
19
|
+
'x-fal-webhook-signature', 'x-fal-webhook-request-id',
|
|
20
|
+
'x-fal-webhook-user-id', 'x-fal-webhook-timestamp',
|
|
21
|
+
]);
|
|
22
|
+
function isFalHeaderName(name) {
|
|
23
|
+
return typeof name === 'string' && FAL_HEADER_NAMES.has(name.toLowerCase());
|
|
24
|
+
}
|
|
25
|
+
function normalizeHeaders(input, opaqueValues = false) {
|
|
26
|
+
const result = new Map();
|
|
27
|
+
const invalid = () => { throw new errors_js_1.HooklatchError('invalid-input', 'Invalid header input.'); };
|
|
28
|
+
const append = (name, value) => {
|
|
29
|
+
if (opaqueValues && !isFalHeaderName(name))
|
|
30
|
+
return;
|
|
31
|
+
if (!isHeaderName(name) || !isHeaderValue(value, opaqueValues))
|
|
32
|
+
invalid();
|
|
33
|
+
const key = name.toLowerCase();
|
|
34
|
+
const previous = result.get(key);
|
|
35
|
+
result.set(key, previous === undefined ? value : `${previous}, ${value}`);
|
|
36
|
+
};
|
|
37
|
+
if (input instanceof Headers) {
|
|
38
|
+
input.forEach((value, key) => append(key, value));
|
|
39
|
+
}
|
|
40
|
+
else if (Array.isArray(input)) {
|
|
41
|
+
for (const entry of input) {
|
|
42
|
+
if (!Array.isArray(entry) || entry.length !== 2)
|
|
43
|
+
invalid();
|
|
44
|
+
append(entry[0], entry[1]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (isHeaderRecord(input)) {
|
|
48
|
+
const entries = [];
|
|
49
|
+
if (opaqueValues) {
|
|
50
|
+
for (const key of Object.keys(input)) {
|
|
51
|
+
if (isFalHeaderName(key))
|
|
52
|
+
entries.push([key, input[key]]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else
|
|
56
|
+
entries.push(...Object.entries(input));
|
|
57
|
+
for (const [key, value] of entries) {
|
|
58
|
+
if (value === undefined)
|
|
59
|
+
continue;
|
|
60
|
+
if (isHeaderValues(value)) {
|
|
61
|
+
for (const item of value)
|
|
62
|
+
append(key, item);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
append(key, value);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else
|
|
70
|
+
invalid();
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeOptions = normalizeOptions;
|
|
4
|
+
const errors_js_1 = require("../errors.cjs");
|
|
5
|
+
const index_js_1 = require("../providers/index.cjs");
|
|
6
|
+
const provider_options_js_1 = require("./provider-options.cjs");
|
|
7
|
+
function isOptionsObject(options) {
|
|
8
|
+
return options !== null && typeof options === 'object' && !Array.isArray(options);
|
|
9
|
+
}
|
|
10
|
+
function isNonEmptySecret(secret) {
|
|
11
|
+
return typeof secret === 'string' && secret.length > 0;
|
|
12
|
+
}
|
|
13
|
+
function isClock(now) {
|
|
14
|
+
return typeof now === 'function';
|
|
15
|
+
}
|
|
16
|
+
function invalidOptions() {
|
|
17
|
+
throw new errors_js_1.HooklatchError('invalid-options', 'Invalid verification options.');
|
|
18
|
+
}
|
|
19
|
+
function isUserId(value) {
|
|
20
|
+
return typeof value === 'string' && /^[\x21-\x2b\x2d-\x7e]{1,1024}$/.test(value);
|
|
21
|
+
}
|
|
22
|
+
/** Data descriptors avoid invoking credential accessors, including non-enumerable ones. */
|
|
23
|
+
function ownData(options, name) {
|
|
24
|
+
const property = observe(() => Object.getOwnPropertyDescriptor(options, name));
|
|
25
|
+
if (!property || !Object.hasOwn(property, 'value'))
|
|
26
|
+
invalidOptions();
|
|
27
|
+
return property;
|
|
28
|
+
}
|
|
29
|
+
function isKeyArray(value) {
|
|
30
|
+
return Array.isArray(value);
|
|
31
|
+
}
|
|
32
|
+
function snapshotFal(options) {
|
|
33
|
+
for (const field of ['secrets', 'secret', 'providerOptions']) {
|
|
34
|
+
if (observe(() => Object.hasOwn(options, field)))
|
|
35
|
+
invalidOptions();
|
|
36
|
+
}
|
|
37
|
+
const input = ownData(options, 'publicKeys').value;
|
|
38
|
+
const expectedUserId = ownData(options, 'expectedUserId').value;
|
|
39
|
+
if (!isUserId(expectedUserId))
|
|
40
|
+
invalidOptions();
|
|
41
|
+
const values = observe(() => {
|
|
42
|
+
if (!isKeyArray(input))
|
|
43
|
+
invalidOptions();
|
|
44
|
+
return input;
|
|
45
|
+
});
|
|
46
|
+
const length = observe(() => values.length);
|
|
47
|
+
if (!Number.isSafeInteger(length) || length < 1 || length > 16)
|
|
48
|
+
invalidOptions();
|
|
49
|
+
const publicKeys = [];
|
|
50
|
+
for (let index = 0; index < length; index++) {
|
|
51
|
+
const key = ownData(values, String(index)).value;
|
|
52
|
+
publicKeys.push(observe(() => {
|
|
53
|
+
if (!(key instanceof Uint8Array))
|
|
54
|
+
invalidOptions();
|
|
55
|
+
const bytes = new Uint8Array(32);
|
|
56
|
+
let offset = 0;
|
|
57
|
+
// The intrinsic iterator reads real view bytes and rejects proxies/detached views.
|
|
58
|
+
// Stop at byte 33 so malformed configuration cannot allocate an oversized copy.
|
|
59
|
+
for (const byte of Uint8Array.prototype.values.call(key)) {
|
|
60
|
+
if (offset === 32)
|
|
61
|
+
invalidOptions();
|
|
62
|
+
bytes[offset++] = byte;
|
|
63
|
+
}
|
|
64
|
+
if (offset !== 32)
|
|
65
|
+
invalidOptions();
|
|
66
|
+
return bytes;
|
|
67
|
+
}));
|
|
68
|
+
}
|
|
69
|
+
return { publicKeys, expectedUserId };
|
|
70
|
+
}
|
|
71
|
+
/** Only observations of caller-owned data are caught; authored errors retain their codes. */
|
|
72
|
+
function observe(read) {
|
|
73
|
+
try {
|
|
74
|
+
return read();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return invalidOptions();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function normalizeOptions(options) {
|
|
81
|
+
if (!observe(() => isOptionsObject(options)))
|
|
82
|
+
invalidOptions();
|
|
83
|
+
const provider = observe(() => options.provider);
|
|
84
|
+
if (!index_js_1.SUPPORTED_PROVIDERS.includes(provider)) {
|
|
85
|
+
throw new errors_js_1.HooklatchError('unknown-provider', 'Unsupported webhook provider.');
|
|
86
|
+
}
|
|
87
|
+
const fal = provider === 'fal' ? snapshotFal(options) : undefined;
|
|
88
|
+
const secrets = provider === 'fal' ? [] : observe(() => options.secrets);
|
|
89
|
+
if (provider !== 'fal' && (!observe(() => Array.isArray(secrets)) || observe(() => secrets?.length) === 0)) {
|
|
90
|
+
throw new errors_js_1.HooklatchError('missing-secrets', 'At least one signing secret is required.');
|
|
91
|
+
}
|
|
92
|
+
const secretSnapshot = observe(() => [...(secrets ?? [])]);
|
|
93
|
+
if (secretSnapshot.some((secret) => !isNonEmptySecret(secret))) {
|
|
94
|
+
throw new errors_js_1.HooklatchError('invalid-secret', 'Invalid signing secret.');
|
|
95
|
+
}
|
|
96
|
+
const supplied = observe(() => Object.hasOwn(options, 'providerOptions'));
|
|
97
|
+
const configured = supplied ? observe(() => options.providerOptions) : undefined;
|
|
98
|
+
if (supplied && configured === undefined)
|
|
99
|
+
invalidOptions();
|
|
100
|
+
if (provider !== 'grafana' && provider !== 'polar' && supplied)
|
|
101
|
+
invalidOptions();
|
|
102
|
+
if (provider === 'vercel' && observe(() => Object.hasOwn(options, 'publicKeys') || Object.hasOwn(options, 'expectedUserId')))
|
|
103
|
+
invalidOptions();
|
|
104
|
+
const grafana = provider === 'grafana' ? observe(() => (0, provider_options_js_1.snapshotGrafanaOptions)(configured)) : undefined;
|
|
105
|
+
const polar = provider === 'polar' && supplied ? observe(() => (0, provider_options_js_1.snapshotPolarOptions)(configured)) : undefined;
|
|
106
|
+
const defaultTolerance = provider === 'paddle' ? 5 : provider === 'workos' ? 180 : 300;
|
|
107
|
+
const toleranceSeconds = observe(() => options.toleranceSeconds) ?? defaultTolerance;
|
|
108
|
+
const maxBodyBytes = observe(() => options.maxBodyBytes) ?? 1048576;
|
|
109
|
+
const now = observe(() => options.now);
|
|
110
|
+
if (!Number.isFinite(toleranceSeconds) || toleranceSeconds < 0
|
|
111
|
+
|| !Number.isSafeInteger(maxBodyBytes) || maxBodyBytes <= 0
|
|
112
|
+
|| (now !== undefined && !isClock(now)))
|
|
113
|
+
invalidOptions();
|
|
114
|
+
const common = { toleranceSeconds, maxBodyBytes,
|
|
115
|
+
now: now ?? (() => (provider === 'workos' || provider === 'sanity') ? Date.now() / 1000 : Math.floor(Date.now() / 1000)) };
|
|
116
|
+
if (provider === 'fal') {
|
|
117
|
+
if (fal === undefined)
|
|
118
|
+
invalidOptions();
|
|
119
|
+
return { ...common, provider, ...fal };
|
|
120
|
+
}
|
|
121
|
+
const hmac = { ...common, secrets: secretSnapshot };
|
|
122
|
+
if (provider === 'grafana') {
|
|
123
|
+
if (grafana === undefined)
|
|
124
|
+
invalidOptions();
|
|
125
|
+
return { ...hmac, provider, providerOptions: grafana };
|
|
126
|
+
}
|
|
127
|
+
if (provider === 'polar')
|
|
128
|
+
return { ...hmac, provider, ...(polar !== undefined && { providerOptions: polar }) };
|
|
129
|
+
return { ...hmac, provider };
|
|
130
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { VerifyOptions } from '../types.cjs';
|
|
2
|
+
export type NormalizedOptions = VerifyOptions & {
|
|
3
|
+
readonly toleranceSeconds: number;
|
|
4
|
+
readonly maxBodyBytes: number;
|
|
5
|
+
readonly now: () => number;
|
|
6
|
+
};
|
|
7
|
+
export declare function normalizeOptions(options: VerifyOptions): NormalizedOptions;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepareVerification = prepareVerification;
|
|
4
|
+
const index_js_1 = require("../providers/index.cjs");
|
|
5
|
+
const options_js_1 = require("./options.cjs");
|
|
6
|
+
const verification_js_1 = require("./verification.cjs");
|
|
7
|
+
/** Shared preparation validates and snapshots trusted options before any request body is read. */
|
|
8
|
+
function prepareVerification(options) {
|
|
9
|
+
const normalized = (0, options_js_1.normalizeOptions)(options);
|
|
10
|
+
return (0, verification_js_1.prepareVerifier)(normalized, (0, index_js_1.getProviderDescriptor)(normalized.provider, normalized.providerOptions));
|
|
11
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { VerifyOptions } from '../types.cjs';
|
|
2
|
+
import type { PreparedVerifier } from './verification.cjs';
|
|
3
|
+
/** Shared preparation validates and snapshots trusted options before any request body is read. */
|
|
4
|
+
export declare function prepareVerification(options: VerifyOptions): PreparedVerifier;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.snapshotGrafanaOptions = snapshotGrafanaOptions;
|
|
4
|
+
exports.snapshotPolarOptions = snapshotPolarOptions;
|
|
5
|
+
const errors_js_1 = require("../errors.cjs");
|
|
6
|
+
const grafana_js_1 = require("../providers/grafana.cjs");
|
|
7
|
+
function invalidOptions() {
|
|
8
|
+
throw new errors_js_1.HooklatchError('invalid-options', 'Invalid provider options.');
|
|
9
|
+
}
|
|
10
|
+
function isConfiguration(value) {
|
|
11
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function isString(value) {
|
|
14
|
+
return typeof value === 'string';
|
|
15
|
+
}
|
|
16
|
+
function rejectUnexpectedKeys(value, allowed) {
|
|
17
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
18
|
+
if (Object.getOwnPropertyDescriptor(value, key)?.enumerable && !allowed.some((name) => name === key))
|
|
19
|
+
invalidOptions();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Read selected own properties exactly once, before validation or asynchronous work. */
|
|
23
|
+
function snapshotGrafanaOptions(value) {
|
|
24
|
+
if (!isConfiguration(value))
|
|
25
|
+
invalidOptions();
|
|
26
|
+
rejectUnexpectedKeys(value, ['mode', 'signatureHeader', 'timestampHeader']);
|
|
27
|
+
if (!Object.hasOwn(value, 'mode') || !('mode' in value))
|
|
28
|
+
invalidOptions();
|
|
29
|
+
const mode = value.mode;
|
|
30
|
+
const hasSignature = Object.hasOwn(value, 'signatureHeader');
|
|
31
|
+
const signature = hasSignature && 'signatureHeader' in value ? value.signatureHeader : undefined;
|
|
32
|
+
if (hasSignature && !isString(signature))
|
|
33
|
+
invalidOptions();
|
|
34
|
+
const signatureOptions = signature !== undefined ? { signatureHeader: signature } : {};
|
|
35
|
+
let snapshot;
|
|
36
|
+
if (mode === 'body') {
|
|
37
|
+
if (Object.hasOwn(value, 'timestampHeader'))
|
|
38
|
+
invalidOptions();
|
|
39
|
+
snapshot = { mode, ...signatureOptions };
|
|
40
|
+
}
|
|
41
|
+
else if (mode === 'timestamped') {
|
|
42
|
+
if (!Object.hasOwn(value, 'timestampHeader') || !('timestampHeader' in value))
|
|
43
|
+
invalidOptions();
|
|
44
|
+
const timestamp = value.timestampHeader;
|
|
45
|
+
if (!isString(timestamp))
|
|
46
|
+
invalidOptions();
|
|
47
|
+
snapshot = { mode, ...signatureOptions, timestampHeader: timestamp };
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
return invalidOptions();
|
|
51
|
+
}
|
|
52
|
+
// The pure factory remains the authority for RFC field names, reservations and collisions.
|
|
53
|
+
(0, grafana_js_1.createGrafanaDescriptor)(snapshot);
|
|
54
|
+
// Validate original code points first: Unicode case conversion can produce ASCII.
|
|
55
|
+
const normalizedSignature = signature !== undefined ? { signatureHeader: signature.toLowerCase() } : {};
|
|
56
|
+
return snapshot.mode === 'body'
|
|
57
|
+
? { mode: snapshot.mode, ...normalizedSignature }
|
|
58
|
+
: { mode: snapshot.mode, ...normalizedSignature, timestampHeader: snapshot.timestampHeader.toLowerCase() };
|
|
59
|
+
}
|
|
60
|
+
function snapshotPolarOptions(value) {
|
|
61
|
+
if (!isConfiguration(value))
|
|
62
|
+
invalidOptions();
|
|
63
|
+
rejectUnexpectedKeys(value, ['secretMode']);
|
|
64
|
+
if (!Object.hasOwn(value, 'secretMode'))
|
|
65
|
+
return {};
|
|
66
|
+
if (!('secretMode' in value))
|
|
67
|
+
invalidOptions();
|
|
68
|
+
const secretMode = value.secretMode;
|
|
69
|
+
if (secretMode !== 'current' && secretMode !== 'legacy')
|
|
70
|
+
invalidOptions();
|
|
71
|
+
return { secretMode };
|
|
72
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { GrafanaOptions, PolarOptions } from '../types.cjs';
|
|
2
|
+
type Configuration = GrafanaOptions | PolarOptions;
|
|
3
|
+
/** Read selected own properties exactly once, before validation or asynchronous work. */
|
|
4
|
+
export declare function snapshotGrafanaOptions(value: Configuration | undefined): GrafanaOptions;
|
|
5
|
+
export declare function snapshotPolarOptions(value: Configuration | undefined): PolarOptions;
|
|
6
|
+
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { EventIdentity, FailureDetails, Provider } from '../types.cjs';
|
|
2
|
+
export type NormalizedHeaders = ReadonlyMap<string, string>;
|
|
3
|
+
export interface ParsedSignature {
|
|
4
|
+
readonly ok: true;
|
|
5
|
+
readonly signatures: readonly Uint8Array[];
|
|
6
|
+
readonly signedContent: Uint8Array;
|
|
7
|
+
/** Untrusted until core signature verification succeeds. */
|
|
8
|
+
readonly identity: EventIdentity;
|
|
9
|
+
}
|
|
10
|
+
export type ParseResult = ParsedSignature | FailureDetails;
|
|
11
|
+
export type HmacPolicy = {
|
|
12
|
+
readonly kind: 'hmac';
|
|
13
|
+
readonly hash: 'SHA-1';
|
|
14
|
+
readonly signatureBytes: 20;
|
|
15
|
+
} | {
|
|
16
|
+
readonly kind: 'hmac';
|
|
17
|
+
readonly hash: 'SHA-256';
|
|
18
|
+
readonly signatureBytes: 32;
|
|
19
|
+
};
|
|
20
|
+
export type FalParseResult = FailureDetails | (ParsedSignature & {
|
|
21
|
+
readonly recipient: string;
|
|
22
|
+
});
|
|
23
|
+
export interface FalProviderDescriptor {
|
|
24
|
+
readonly provider: 'fal';
|
|
25
|
+
readonly crypto: {
|
|
26
|
+
readonly kind: 'ed25519';
|
|
27
|
+
readonly signatureBytes: 64;
|
|
28
|
+
};
|
|
29
|
+
parse(headers: NormalizedHeaders, rawBody: Uint8Array): Promise<FalParseResult>;
|
|
30
|
+
}
|
|
31
|
+
export type AnyProviderDescriptor = ProviderDescriptor | FalProviderDescriptor;
|
|
32
|
+
export interface ProviderDescriptor {
|
|
33
|
+
readonly provider: Exclude<Provider, 'fal'>;
|
|
34
|
+
readonly crypto?: HmacPolicy;
|
|
35
|
+
/** Throw HooklatchError('invalid-secret') on invalid configuration. */
|
|
36
|
+
decodeSecret(secret: string): Uint8Array;
|
|
37
|
+
/** Parse and format only. Freshness and HMAC checks belong to core. */
|
|
38
|
+
parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
|
|
39
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepareVerifier = prepareVerifier;
|
|
4
|
+
exports.readClock = readClock;
|
|
5
|
+
exports.failure = failure;
|
|
6
|
+
exports.verifyPrepared = verifyPrepared;
|
|
7
|
+
const errors_js_1 = require("../errors.cjs");
|
|
8
|
+
const options_js_1 = require("./options.cjs");
|
|
9
|
+
const headers_js_1 = require("./headers.cjs");
|
|
10
|
+
const crypto_js_1 = require("./crypto.cjs");
|
|
11
|
+
function prepareVerifier(options, descriptor) {
|
|
12
|
+
const normalized = (0, options_js_1.normalizeOptions)(options);
|
|
13
|
+
let keys;
|
|
14
|
+
if (normalized.provider === 'fal') {
|
|
15
|
+
if (descriptor.provider !== 'fal')
|
|
16
|
+
throw new errors_js_1.HooklatchError('internal', 'Invalid provider configuration.');
|
|
17
|
+
keys = normalized.publicKeys;
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
if (descriptor.provider === 'fal')
|
|
21
|
+
throw new errors_js_1.HooklatchError('internal', 'Invalid provider configuration.');
|
|
22
|
+
keys = normalized.secrets.map((secret) => new Uint8Array(descriptor.decodeSecret(secret)));
|
|
23
|
+
}
|
|
24
|
+
if (keys.some((key) => key.byteLength === 0))
|
|
25
|
+
throw new errors_js_1.HooklatchError('invalid-secret', 'Invalid signing secret.');
|
|
26
|
+
return { options: normalized, descriptor, keys };
|
|
27
|
+
}
|
|
28
|
+
function readClock(prepared) {
|
|
29
|
+
let now;
|
|
30
|
+
try {
|
|
31
|
+
now = prepared.options.now();
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
throw new errors_js_1.HooklatchError('invalid-options', 'Invalid verification clock.');
|
|
35
|
+
}
|
|
36
|
+
if (!Number.isFinite(now) || now < 0 || now > Number.MAX_SAFE_INTEGER) {
|
|
37
|
+
throw new errors_js_1.HooklatchError('invalid-options', 'Invalid verification clock.');
|
|
38
|
+
}
|
|
39
|
+
return now;
|
|
40
|
+
}
|
|
41
|
+
function failure(prepared, reason, message) {
|
|
42
|
+
return { ok: false, provider: prepared.options.provider, reason, message };
|
|
43
|
+
}
|
|
44
|
+
async function verifyPrepared(request, prepared, receivedAt = readClock(prepared)) {
|
|
45
|
+
if (!request || !(request.rawBody instanceof Uint8Array))
|
|
46
|
+
throw new errors_js_1.HooklatchError('invalid-input', 'Raw body must be a Uint8Array.');
|
|
47
|
+
if (request.rawBody.byteLength > prepared.options.maxBodyBytes)
|
|
48
|
+
return failure(prepared, 'body-too-large', 'Request body exceeds the configured limit.');
|
|
49
|
+
const rawBody = new Uint8Array(request.rawBody);
|
|
50
|
+
const headers = (0, headers_js_1.normalizeHeaders)(request.headers, prepared.options.provider === 'fal');
|
|
51
|
+
const parsed = await prepared.descriptor.parse(headers, rawBody);
|
|
52
|
+
if (!parsed.ok)
|
|
53
|
+
return { ...parsed, provider: prepared.options.provider };
|
|
54
|
+
const valid = prepared.descriptor.provider === 'fal'
|
|
55
|
+
? await (0, crypto_js_1.verifyEd25519)(prepared.keys, parsed.signatures, parsed.signedContent)
|
|
56
|
+
: await (0, crypto_js_1.verifyHmac)(prepared.keys, parsed.signatures, parsed.signedContent, prepared.descriptor.crypto);
|
|
57
|
+
if (!valid || (prepared.options.provider === 'fal'
|
|
58
|
+
&& (!('recipient' in parsed) || parsed.recipient !== prepared.options.expectedUserId))) {
|
|
59
|
+
return failure(prepared, 'invalid-signature', 'Webhook signature is invalid.');
|
|
60
|
+
}
|
|
61
|
+
const timestamp = parsed.identity.timestamp;
|
|
62
|
+
if (timestamp !== null) {
|
|
63
|
+
const age = receivedAt - timestamp;
|
|
64
|
+
if (age > prepared.options.toleranceSeconds)
|
|
65
|
+
return failure(prepared, 'timestamp-expired', 'Webhook timestamp is outside the allowed past window.');
|
|
66
|
+
if (age < -prepared.options.toleranceSeconds)
|
|
67
|
+
return failure(prepared, 'timestamp-future', 'Webhook timestamp is outside the allowed future window.');
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, provider: prepared.options.provider, rawBody,
|
|
70
|
+
identity: { ...parsed.identity, authenticatedFields: [...parsed.identity.authenticatedFields] } };
|
|
71
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FailureDetails, VerificationResult, VerifyOptions, WebhookRequest } from '../types.cjs';
|
|
2
|
+
import type { AnyProviderDescriptor } from './provider.cjs';
|
|
3
|
+
import type { NormalizedOptions } from './options.cjs';
|
|
4
|
+
export interface PreparedVerifier {
|
|
5
|
+
readonly options: NormalizedOptions;
|
|
6
|
+
readonly descriptor: AnyProviderDescriptor;
|
|
7
|
+
readonly keys: readonly Uint8Array[];
|
|
8
|
+
}
|
|
9
|
+
export declare function prepareVerifier(options: VerifyOptions, descriptor: AnyProviderDescriptor): PreparedVerifier;
|
|
10
|
+
export declare function readClock(prepared: PreparedVerifier): number;
|
|
11
|
+
export declare function failure(prepared: PreparedVerifier, reason: FailureDetails['reason'], message: string): VerificationResult;
|
|
12
|
+
export declare function verifyPrepared(request: WebhookRequest, prepared: PreparedVerifier, receivedAt?: number): Promise<VerificationResult>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.clerkDescriptor = void 0;
|
|
4
|
+
const standard_webhooks_protocol_js_1 = require("./standard-webhooks-protocol.cjs");
|
|
5
|
+
/**
|
|
6
|
+
* Clerk delivers Standard Webhooks payloads through Svix and its official
|
|
7
|
+
* helper reads only the branded `svix-*` headers. Canonical `webhook-*`
|
|
8
|
+
* headers are deliberately ignored and never repair a missing or invalid
|
|
9
|
+
* branded field.
|
|
10
|
+
*/
|
|
11
|
+
exports.clerkDescriptor = (0, standard_webhooks_protocol_js_1.createStandardWebhooksProtocol)('clerk', 'Clerk', {
|
|
12
|
+
id: 'svix-id',
|
|
13
|
+
timestamp: 'svix-timestamp',
|
|
14
|
+
signature: 'svix-signature',
|
|
15
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clerk delivers Standard Webhooks payloads through Svix and its official
|
|
3
|
+
* helper reads only the branded `svix-*` headers. Canonical `webhook-*`
|
|
4
|
+
* headers are deliberately ignored and never repair a missing or invalid
|
|
5
|
+
* branded field.
|
|
6
|
+
*/
|
|
7
|
+
export declare const clerkDescriptor: {
|
|
8
|
+
readonly provider: "clerk";
|
|
9
|
+
} & Pick<import("../internal/provider.cjs").ProviderDescriptor, "decodeSecret" | "parse">;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dodoPaymentsDescriptor = void 0;
|
|
4
|
+
const standard_webhooks_protocol_js_1 = require("./standard-webhooks-protocol.cjs");
|
|
5
|
+
/**
|
|
6
|
+
* Dodo Payments delivers Standard Webhooks payloads with the canonical
|
|
7
|
+
* `webhook-id`, `webhook-timestamp` and `webhook-signature` headers. The
|
|
8
|
+
* secret is Base64-decoded (an optional `whsec_` prefix is stripped) and must
|
|
9
|
+
* decode to 24-64 bytes; the signature covers the exact
|
|
10
|
+
* `${webhook-id}.${webhook-timestamp}.` bytes followed by the raw body.
|
|
11
|
+
*
|
|
12
|
+
* These headers authenticate protocol bytes, not a unique Dodo identity: a
|
|
13
|
+
* signature minted by any other canonical alias verifies here when the same
|
|
14
|
+
* secret is configured. Use endpoint-specific secrets and route configuration
|
|
15
|
+
* to prevent cross-endpoint replay. No event payload is parsed and no
|
|
16
|
+
* resource id is inferred.
|
|
17
|
+
*
|
|
18
|
+
* No Dodo Payments-published signature vector is claimed; the direct tests use
|
|
19
|
+
* synthetic anchors. Source:
|
|
20
|
+
* https://docs.dodopayments.com/developer-resources/webhooks
|
|
21
|
+
*/
|
|
22
|
+
exports.dodoPaymentsDescriptor = (0, standard_webhooks_protocol_js_1.createStandardWebhooksProtocol)('dodo-payments', 'Dodo Payments', {
|
|
23
|
+
id: 'webhook-id',
|
|
24
|
+
timestamp: 'webhook-timestamp',
|
|
25
|
+
signature: 'webhook-signature',
|
|
26
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dodo Payments delivers Standard Webhooks payloads with the canonical
|
|
3
|
+
* `webhook-id`, `webhook-timestamp` and `webhook-signature` headers. The
|
|
4
|
+
* secret is Base64-decoded (an optional `whsec_` prefix is stripped) and must
|
|
5
|
+
* decode to 24-64 bytes; the signature covers the exact
|
|
6
|
+
* `${webhook-id}.${webhook-timestamp}.` bytes followed by the raw body.
|
|
7
|
+
*
|
|
8
|
+
* These headers authenticate protocol bytes, not a unique Dodo identity: a
|
|
9
|
+
* signature minted by any other canonical alias verifies here when the same
|
|
10
|
+
* secret is configured. Use endpoint-specific secrets and route configuration
|
|
11
|
+
* to prevent cross-endpoint replay. No event payload is parsed and no
|
|
12
|
+
* resource id is inferred.
|
|
13
|
+
*
|
|
14
|
+
* No Dodo Payments-published signature vector is claimed; the direct tests use
|
|
15
|
+
* synthetic anchors. Source:
|
|
16
|
+
* https://docs.dodopayments.com/developer-resources/webhooks
|
|
17
|
+
*/
|
|
18
|
+
export declare const dodoPaymentsDescriptor: {
|
|
19
|
+
readonly provider: "dodo-payments";
|
|
20
|
+
} & Pick<import("../internal/provider.cjs").ProviderDescriptor, "decodeSecret" | "parse">;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.dopplerDescriptor = void 0;
|
|
4
|
+
const shared_js_1 = require("./shared.cjs");
|
|
5
|
+
const SIGNATURE_HEADER = 'x-doppler-signature';
|
|
6
|
+
const SIGNATURE_SCHEME = 'sha256';
|
|
7
|
+
/**
|
|
8
|
+
* Doppler `X-Doppler-Signature: sha256=<hex HMAC-SHA256(raw request body)>`.
|
|
9
|
+
*
|
|
10
|
+
* Doppler documents exactly one `<scheme>=<64 hex>` value; there is no list and
|
|
11
|
+
* no unknown-field tolerance. The complete value is validated before the scheme
|
|
12
|
+
* decision, so a well-formed digest under another scheme token (`sha1=<64 hex>`)
|
|
13
|
+
* fails as `unsupported-scheme`, while any nonconforming syntax — wrong length,
|
|
14
|
+
* non-hex, bare hex, commas, extra equals, empty digest, or outer whitespace —
|
|
15
|
+
* fails as `malformed-signature`. `sha256=` is case-sensitive; `SHA256=<64 hex>`
|
|
16
|
+
* is a different scheme and therefore unsupported.
|
|
17
|
+
*
|
|
18
|
+
* The secret is the literal, untrimmed UTF-8 text of the webhook `Secret`. It is
|
|
19
|
+
* never Base64-decoded and no `whsec_`-style prefix is inferred, so an empty
|
|
20
|
+
* secret is an invalid configuration rather than an encoded empty key.
|
|
21
|
+
*
|
|
22
|
+
* The HMAC covers the exact raw body only; no timestamp or identifier is
|
|
23
|
+
* included in the MAC, so every identity field is null and
|
|
24
|
+
* `authenticatedFields` is empty: this descriptor never establishes freshness and
|
|
25
|
+
* offers no replay protection. Signing is optional on the Doppler side, so a
|
|
26
|
+
* delivery without a configured secret carries no signature and cannot be
|
|
27
|
+
* verified; there is no Basic/Bearer or unsigned fallback path here.
|
|
28
|
+
*
|
|
29
|
+
* The Doppler docs' Node example re-serializes with `JSON.stringify(req.body)`.
|
|
30
|
+
* This descriptor authenticates the received bytes instead, so a caller that has
|
|
31
|
+
* already parsed the body cannot reliably reconstruct the signed content and
|
|
32
|
+
* should pass the original bytes.
|
|
33
|
+
*
|
|
34
|
+
* Source: https://docs.doppler.com/docs/webhooks
|
|
35
|
+
*
|
|
36
|
+
* No Doppler-published signature tuple was located; test anchors are synthetic.
|
|
37
|
+
*/
|
|
38
|
+
exports.dopplerDescriptor = {
|
|
39
|
+
provider: 'doppler',
|
|
40
|
+
decodeSecret(secret) {
|
|
41
|
+
return (0, shared_js_1.decodeLiteralSecret)(secret, 'Doppler');
|
|
42
|
+
},
|
|
43
|
+
parse(headers, rawBody) {
|
|
44
|
+
const raw = headers.get(SIGNATURE_HEADER);
|
|
45
|
+
if (raw === undefined || raw.trim() === '') {
|
|
46
|
+
return (0, shared_js_1.failure)('missing-signature', 'No Doppler signature header was provided.');
|
|
47
|
+
}
|
|
48
|
+
// Validate the whole value first: exactly one `=`, a nonempty scheme token,
|
|
49
|
+
// and a nonempty digest. `decodeHexSignature` accepts either hex case but
|
|
50
|
+
// requires exactly 64 hex characters (32 bytes).
|
|
51
|
+
const separator = raw.indexOf('=');
|
|
52
|
+
if (separator <= 0 || separator === raw.length - 1 || raw.indexOf('=', separator + 1) !== -1) {
|
|
53
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Doppler signature header is malformed.');
|
|
54
|
+
}
|
|
55
|
+
const scheme = raw.slice(0, separator);
|
|
56
|
+
const signature = (0, shared_js_1.decodeHexSignature)(raw.slice(separator + 1));
|
|
57
|
+
if (!(0, shared_js_1.isSchemeToken)(scheme) || signature === null) {
|
|
58
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Doppler signature header is malformed.');
|
|
59
|
+
}
|
|
60
|
+
if (scheme !== SIGNATURE_SCHEME) {
|
|
61
|
+
return (0, shared_js_1.failure)('unsupported-scheme', 'The Doppler signature scheme is not supported.');
|
|
62
|
+
}
|
|
63
|
+
const identity = {
|
|
64
|
+
deliveryId: null,
|
|
65
|
+
eventId: null,
|
|
66
|
+
timestamp: null,
|
|
67
|
+
authenticatedFields: [],
|
|
68
|
+
};
|
|
69
|
+
return { ok: true, signatures: [signature], signedContent: rawBody, identity };
|
|
70
|
+
},
|
|
71
|
+
};
|