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,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sentryDescriptor = void 0;
|
|
4
|
+
const shared_js_1 = require("./shared.cjs");
|
|
5
|
+
const SIGNATURE_HEADER = 'sentry-hook-signature';
|
|
6
|
+
/**
|
|
7
|
+
* Sentry App integration-platform webhooks: `Sentry-Hook-Signature` is a single
|
|
8
|
+
* 64-character hex HMAC-SHA256 over the exact raw request body, keyed with the
|
|
9
|
+
* literal app client secret. Missing or trim-blank -> missing-signature; every
|
|
10
|
+
* other nonconforming value (prefixes, whitespace, comma-combined duplicates,
|
|
11
|
+
* lists, wrong length or non-hex) fails closed.
|
|
12
|
+
*
|
|
13
|
+
* `Request-Id`, `Sentry-Hook-Timestamp` and `Sentry-Hook-Resource` are outside
|
|
14
|
+
* the MAC: they cannot establish freshness or trusted identity. Sentry's docs
|
|
15
|
+
* examples re-serialize the parsed body, which can change the signed bytes; this
|
|
16
|
+
* descriptor always returns the untouched `rawBody` as signed content.
|
|
17
|
+
*
|
|
18
|
+
* Scope is Sentry App integration-platform webhooks only, not every Sentry
|
|
19
|
+
* webhook product. Sources:
|
|
20
|
+
* - https://docs.sentry.io/integrations/integration-platform/webhooks/
|
|
21
|
+
* - https://github.com/getsentry/sentry/blob/72757dae9b6d527e564c81d4921dec0d62d7beca/src/sentry/sentry_apps/api/serializers/app_platform_event.py
|
|
22
|
+
*
|
|
23
|
+
* No Sentry-published signature vector is claimed; test anchors are synthetic.
|
|
24
|
+
*/
|
|
25
|
+
exports.sentryDescriptor = {
|
|
26
|
+
provider: 'sentry',
|
|
27
|
+
decodeSecret(secret) {
|
|
28
|
+
return (0, shared_js_1.decodeLiteralSecret)(secret, 'Sentry App');
|
|
29
|
+
},
|
|
30
|
+
parse(headers, rawBody) {
|
|
31
|
+
const raw = headers.get(SIGNATURE_HEADER);
|
|
32
|
+
if (raw === undefined || raw.trim() === '') {
|
|
33
|
+
return (0, shared_js_1.failure)('missing-signature', 'No Sentry App signature header was provided.');
|
|
34
|
+
}
|
|
35
|
+
const signature = (0, shared_js_1.decodeHexSignature)(raw);
|
|
36
|
+
if (signature === null) {
|
|
37
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Sentry App signature header is malformed.');
|
|
38
|
+
}
|
|
39
|
+
const identity = {
|
|
40
|
+
deliveryId: null,
|
|
41
|
+
eventId: null,
|
|
42
|
+
timestamp: null,
|
|
43
|
+
authenticatedFields: [],
|
|
44
|
+
};
|
|
45
|
+
return { ok: true, signatures: [signature], signedContent: rawBody, identity };
|
|
46
|
+
},
|
|
47
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { NormalizedHeaders, ParseResult } from '../internal/provider.cjs';
|
|
2
|
+
/**
|
|
3
|
+
* Sentry App integration-platform webhooks: `Sentry-Hook-Signature` is a single
|
|
4
|
+
* 64-character hex HMAC-SHA256 over the exact raw request body, keyed with the
|
|
5
|
+
* literal app client secret. Missing or trim-blank -> missing-signature; every
|
|
6
|
+
* other nonconforming value (prefixes, whitespace, comma-combined duplicates,
|
|
7
|
+
* lists, wrong length or non-hex) fails closed.
|
|
8
|
+
*
|
|
9
|
+
* `Request-Id`, `Sentry-Hook-Timestamp` and `Sentry-Hook-Resource` are outside
|
|
10
|
+
* the MAC: they cannot establish freshness or trusted identity. Sentry's docs
|
|
11
|
+
* examples re-serialize the parsed body, which can change the signed bytes; this
|
|
12
|
+
* descriptor always returns the untouched `rawBody` as signed content.
|
|
13
|
+
*
|
|
14
|
+
* Scope is Sentry App integration-platform webhooks only, not every Sentry
|
|
15
|
+
* webhook product. Sources:
|
|
16
|
+
* - https://docs.sentry.io/integrations/integration-platform/webhooks/
|
|
17
|
+
* - https://github.com/getsentry/sentry/blob/72757dae9b6d527e564c81d4921dec0d62d7beca/src/sentry/sentry_apps/api/serializers/app_platform_event.py
|
|
18
|
+
*
|
|
19
|
+
* No Sentry-published signature vector is claimed; test anchors are synthetic.
|
|
20
|
+
*/
|
|
21
|
+
export declare const sentryDescriptor: {
|
|
22
|
+
provider: "sentry";
|
|
23
|
+
decodeSecret(secret: string): Uint8Array;
|
|
24
|
+
parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
|
|
25
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SIGNATURE_BYTES = void 0;
|
|
4
|
+
exports.failure = failure;
|
|
5
|
+
exports.isSchemeToken = isSchemeToken;
|
|
6
|
+
exports.isCanonicalTimestamp = isCanonicalTimestamp;
|
|
7
|
+
exports.decodeHexSignature = decodeHexSignature;
|
|
8
|
+
exports.decodeBase64Signature = decodeBase64Signature;
|
|
9
|
+
exports.isNonEmptySecret = isNonEmptySecret;
|
|
10
|
+
exports.decodeLiteralSecret = decodeLiteralSecret;
|
|
11
|
+
const errors_js_1 = require("../errors.cjs");
|
|
12
|
+
const bytes_js_1 = require("../internal/bytes.cjs");
|
|
13
|
+
/** HMAC-SHA256 signatures are always exactly 32 bytes. */
|
|
14
|
+
exports.SIGNATURE_BYTES = 32;
|
|
15
|
+
/** Scheme/key tokens accepted in provider signature headers (e.g. `v1`, `sha256`). */
|
|
16
|
+
const TOKEN = /^[A-Za-z0-9_-]+$/;
|
|
17
|
+
function failure(reason, message) {
|
|
18
|
+
return { ok: false, reason, message };
|
|
19
|
+
}
|
|
20
|
+
function isSchemeToken(value) {
|
|
21
|
+
return TOKEN.test(value);
|
|
22
|
+
}
|
|
23
|
+
/** Canonical decimal integer: `0`, or a nonzero digit followed by digits. */
|
|
24
|
+
function isCanonicalTimestamp(value) {
|
|
25
|
+
if (!/^(?:0|[1-9]\d*)$/.test(value))
|
|
26
|
+
return false;
|
|
27
|
+
const numeric = Number(value);
|
|
28
|
+
return Number.isSafeInteger(numeric) && numeric >= 0;
|
|
29
|
+
}
|
|
30
|
+
/** Hex signature of exactly 32 bytes; accepts either hex case. */
|
|
31
|
+
function decodeHexSignature(value) {
|
|
32
|
+
const bytes = (0, bytes_js_1.decodeHex)(value);
|
|
33
|
+
return bytes !== null && bytes.byteLength === exports.SIGNATURE_BYTES ? bytes : null;
|
|
34
|
+
}
|
|
35
|
+
/** Canonical Base64 signature of exactly 32 bytes. */
|
|
36
|
+
function decodeBase64Signature(value) {
|
|
37
|
+
const bytes = (0, bytes_js_1.decodeBase64)(value);
|
|
38
|
+
return bytes !== null && bytes.byteLength === exports.SIGNATURE_BYTES ? bytes : null;
|
|
39
|
+
}
|
|
40
|
+
function isNonEmptySecret(secret) {
|
|
41
|
+
return typeof secret === 'string' && secret.length > 0;
|
|
42
|
+
}
|
|
43
|
+
/** Literal, untrimmed UTF-8 secret bytes for Stripe/GitHub/Shopify. */
|
|
44
|
+
function decodeLiteralSecret(secret, label) {
|
|
45
|
+
if (!isNonEmptySecret(secret)) {
|
|
46
|
+
throw new errors_js_1.HooklatchError('invalid-secret', `Invalid ${label} signing secret.`);
|
|
47
|
+
}
|
|
48
|
+
return (0, bytes_js_1.utf8)(secret);
|
|
49
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FailureDetails, VerificationFailureReason } from '../types.cjs';
|
|
2
|
+
/** HMAC-SHA256 signatures are always exactly 32 bytes. */
|
|
3
|
+
export declare const SIGNATURE_BYTES = 32;
|
|
4
|
+
export declare function failure(reason: VerificationFailureReason, message: string): FailureDetails;
|
|
5
|
+
export declare function isSchemeToken(value: string): boolean;
|
|
6
|
+
/** Canonical decimal integer: `0`, or a nonzero digit followed by digits. */
|
|
7
|
+
export declare function isCanonicalTimestamp(value: string): boolean;
|
|
8
|
+
/** Hex signature of exactly 32 bytes; accepts either hex case. */
|
|
9
|
+
export declare function decodeHexSignature(value: string): Uint8Array | null;
|
|
10
|
+
/** Canonical Base64 signature of exactly 32 bytes. */
|
|
11
|
+
export declare function decodeBase64Signature(value: string): Uint8Array | null;
|
|
12
|
+
export declare function isNonEmptySecret(secret: string): secret is string;
|
|
13
|
+
/** Literal, untrimmed UTF-8 secret bytes for Stripe/GitHub/Shopify. */
|
|
14
|
+
export declare function decodeLiteralSecret(secret: string, label: string): Uint8Array;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shopifyDescriptor = void 0;
|
|
4
|
+
const shared_js_1 = require("./shared.cjs");
|
|
5
|
+
const SIGNATURE_HEADER = 'x-shopify-hmac-sha256';
|
|
6
|
+
const DELIVERY_HEADER = 'x-shopify-webhook-id';
|
|
7
|
+
const EVENT_HEADER = 'x-shopify-event-id';
|
|
8
|
+
function optionalHeader(headers, name) {
|
|
9
|
+
const value = headers.get(name);
|
|
10
|
+
return value === undefined || value === '' ? null : value;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Shopify HTTPS `X-Shopify-Hmac-Sha256`: canonical Base64 HMAC-SHA256 over the
|
|
14
|
+
* raw body only, keyed with the app client secret. Delivery and event IDs are
|
|
15
|
+
* unsigned metadata. EventBridge/Pub/Sub deliveries carry no HMAC header and are
|
|
16
|
+
* therefore not verifiable by this descriptor.
|
|
17
|
+
*/
|
|
18
|
+
exports.shopifyDescriptor = {
|
|
19
|
+
provider: 'shopify',
|
|
20
|
+
decodeSecret(secret) {
|
|
21
|
+
return (0, shared_js_1.decodeLiteralSecret)(secret, 'Shopify');
|
|
22
|
+
},
|
|
23
|
+
parse(headers, rawBody) {
|
|
24
|
+
const raw = headers.get(SIGNATURE_HEADER);
|
|
25
|
+
if (raw === undefined || raw.trim() === '') {
|
|
26
|
+
return (0, shared_js_1.failure)('missing-signature', 'No Shopify signature header was provided.');
|
|
27
|
+
}
|
|
28
|
+
const signature = (0, shared_js_1.decodeBase64Signature)(raw);
|
|
29
|
+
if (signature === null) {
|
|
30
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Shopify signature header is malformed.');
|
|
31
|
+
}
|
|
32
|
+
const identity = {
|
|
33
|
+
deliveryId: optionalHeader(headers, DELIVERY_HEADER),
|
|
34
|
+
eventId: optionalHeader(headers, EVENT_HEADER),
|
|
35
|
+
timestamp: null,
|
|
36
|
+
authenticatedFields: [],
|
|
37
|
+
};
|
|
38
|
+
return { ok: true, signatures: [signature], signedContent: rawBody, identity };
|
|
39
|
+
},
|
|
40
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ProviderDescriptor } from '../internal/provider.cjs';
|
|
2
|
+
/**
|
|
3
|
+
* Shopify HTTPS `X-Shopify-Hmac-Sha256`: canonical Base64 HMAC-SHA256 over the
|
|
4
|
+
* raw body only, keyed with the app client secret. Delivery and event IDs are
|
|
5
|
+
* unsigned metadata. EventBridge/Pub/Sub deliveries carry no HMAC header and are
|
|
6
|
+
* therefore not verifiable by this descriptor.
|
|
7
|
+
*/
|
|
8
|
+
export declare const shopifyDescriptor: ProviderDescriptor;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createStandardWebhooksProtocol = createStandardWebhooksProtocol;
|
|
4
|
+
const errors_js_1 = require("../errors.cjs");
|
|
5
|
+
const bytes_js_1 = require("../internal/bytes.cjs");
|
|
6
|
+
const shared_js_1 = require("./shared.cjs");
|
|
7
|
+
const SCHEME = 'v1';
|
|
8
|
+
const SECRET_PREFIX = 'whsec_';
|
|
9
|
+
const MIN_KEY_BYTES = 24;
|
|
10
|
+
const MAX_KEY_BYTES = 64;
|
|
11
|
+
/**
|
|
12
|
+
* Internal Standard Webhooks v1 protocol factory. `headerNames` selects the
|
|
13
|
+
* canonical or branded header family; callers own alias policy and must never
|
|
14
|
+
* mix names from two families in one configuration.
|
|
15
|
+
*/
|
|
16
|
+
function createStandardWebhooksProtocol(provider, label, headerNames) {
|
|
17
|
+
return {
|
|
18
|
+
provider,
|
|
19
|
+
decodeSecret(secret) {
|
|
20
|
+
if (!(0, shared_js_1.isNonEmptySecret)(secret)) {
|
|
21
|
+
throw new errors_js_1.HooklatchError('invalid-secret', `Invalid ${label} signing secret.`);
|
|
22
|
+
}
|
|
23
|
+
const encoded = secret.startsWith(SECRET_PREFIX)
|
|
24
|
+
? secret.slice(SECRET_PREFIX.length)
|
|
25
|
+
: secret;
|
|
26
|
+
const bytes = (0, bytes_js_1.decodeBase64)(encoded);
|
|
27
|
+
if (bytes === null || bytes.byteLength < MIN_KEY_BYTES || bytes.byteLength > MAX_KEY_BYTES) {
|
|
28
|
+
throw new errors_js_1.HooklatchError('invalid-secret', `Invalid ${label} signing secret.`);
|
|
29
|
+
}
|
|
30
|
+
return bytes;
|
|
31
|
+
},
|
|
32
|
+
parse(headers, rawBody) {
|
|
33
|
+
const signatureRaw = headers.get(headerNames.signature);
|
|
34
|
+
if (signatureRaw === undefined || signatureRaw.trim() === '') {
|
|
35
|
+
return (0, shared_js_1.failure)('missing-signature', `No ${label} signature header was provided.`);
|
|
36
|
+
}
|
|
37
|
+
const signatures = [];
|
|
38
|
+
let unknownSchemes = 0;
|
|
39
|
+
for (const piece of signatureRaw.split(/\s+/)) {
|
|
40
|
+
if (piece === '')
|
|
41
|
+
continue;
|
|
42
|
+
const separator = piece.indexOf(',');
|
|
43
|
+
if (separator <= 0 || separator === piece.length - 1) {
|
|
44
|
+
return (0, shared_js_1.failure)('malformed-signature', `The ${label} signature header is malformed.`);
|
|
45
|
+
}
|
|
46
|
+
const version = piece.slice(0, separator);
|
|
47
|
+
const value = piece.slice(separator + 1);
|
|
48
|
+
// HTTP comma-combined duplicate header values never form a valid token.
|
|
49
|
+
if (!(0, shared_js_1.isSchemeToken)(version) || /[,\s]/.test(value)) {
|
|
50
|
+
return (0, shared_js_1.failure)('malformed-signature', `The ${label} signature header is malformed.`);
|
|
51
|
+
}
|
|
52
|
+
if (version === SCHEME) {
|
|
53
|
+
const decoded = (0, shared_js_1.decodeBase64Signature)(value);
|
|
54
|
+
// A malformed `v1` fails closed even when another `v1` value is valid.
|
|
55
|
+
if (decoded === null) {
|
|
56
|
+
return (0, shared_js_1.failure)('malformed-signature', `The ${label} signature header is malformed.`);
|
|
57
|
+
}
|
|
58
|
+
signatures.push(decoded);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
unknownSchemes += 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (signatures.length === 0) {
|
|
65
|
+
return unknownSchemes > 0
|
|
66
|
+
? (0, shared_js_1.failure)('unsupported-scheme', `No supported ${label} signature scheme was provided.`)
|
|
67
|
+
: (0, shared_js_1.failure)('missing-signature', `No ${label} signature value was provided.`);
|
|
68
|
+
}
|
|
69
|
+
const id = headers.get(headerNames.id);
|
|
70
|
+
if (id === undefined || id === '') {
|
|
71
|
+
return (0, shared_js_1.failure)('missing-identity', `No ${label} message id was provided.`);
|
|
72
|
+
}
|
|
73
|
+
if (id.includes('.') || id.includes(',')) {
|
|
74
|
+
return (0, shared_js_1.failure)('malformed-signature', `The ${label} message id is malformed.`);
|
|
75
|
+
}
|
|
76
|
+
const timestampText = headers.get(headerNames.timestamp);
|
|
77
|
+
if (timestampText === undefined || timestampText === '') {
|
|
78
|
+
return (0, shared_js_1.failure)('missing-timestamp', `No ${label} timestamp was provided.`);
|
|
79
|
+
}
|
|
80
|
+
if (!(0, shared_js_1.isCanonicalTimestamp)(timestampText)) {
|
|
81
|
+
return (0, shared_js_1.failure)('malformed-signature', `The ${label} timestamp is malformed.`);
|
|
82
|
+
}
|
|
83
|
+
const identity = {
|
|
84
|
+
deliveryId: null,
|
|
85
|
+
eventId: id,
|
|
86
|
+
timestamp: Number(timestampText),
|
|
87
|
+
authenticatedFields: ['eventId', 'timestamp'],
|
|
88
|
+
};
|
|
89
|
+
return {
|
|
90
|
+
ok: true,
|
|
91
|
+
signatures,
|
|
92
|
+
signedContent: (0, bytes_js_1.concatBytes)((0, bytes_js_1.utf8)(`${id}.${timestampText}.`), rawBody),
|
|
93
|
+
identity,
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ProviderDescriptor } from '../internal/provider.cjs';
|
|
2
|
+
export interface StandardWebhooksHeaderNames {
|
|
3
|
+
readonly id: string;
|
|
4
|
+
readonly timestamp: string;
|
|
5
|
+
readonly signature: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Internal Standard Webhooks v1 protocol factory. `headerNames` selects the
|
|
9
|
+
* canonical or branded header family; callers own alias policy and must never
|
|
10
|
+
* mix names from two families in one configuration.
|
|
11
|
+
*/
|
|
12
|
+
export declare function createStandardWebhooksProtocol<P extends string>(provider: P, label: string, headerNames: StandardWebhooksHeaderNames): {
|
|
13
|
+
readonly provider: P;
|
|
14
|
+
} & Pick<ProviderDescriptor, 'decodeSecret' | 'parse'>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.standardWebhooksDescriptor = void 0;
|
|
4
|
+
const standard_webhooks_protocol_js_1 = require("./standard-webhooks-protocol.cjs");
|
|
5
|
+
/**
|
|
6
|
+
* Standard Webhooks v1: `webhook-id`, `webhook-timestamp`, and a
|
|
7
|
+
* whitespace-separated `webhook-signature` list of `v1,<base64>` tokens.
|
|
8
|
+
* The signature covers `${id}.${timestamp}.<raw body>`. The Ed25519
|
|
9
|
+
* `v1a` scheme is recognized but unsupported in this release.
|
|
10
|
+
*/
|
|
11
|
+
exports.standardWebhooksDescriptor = (0, standard_webhooks_protocol_js_1.createStandardWebhooksProtocol)('standard-webhooks', 'Standard Webhooks', {
|
|
12
|
+
id: 'webhook-id',
|
|
13
|
+
timestamp: 'webhook-timestamp',
|
|
14
|
+
signature: 'webhook-signature',
|
|
15
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standard Webhooks v1: `webhook-id`, `webhook-timestamp`, and a
|
|
3
|
+
* whitespace-separated `webhook-signature` list of `v1,<base64>` tokens.
|
|
4
|
+
* The signature covers `${id}.${timestamp}.<raw body>`. The Ed25519
|
|
5
|
+
* `v1a` scheme is recognized but unsupported in this release.
|
|
6
|
+
*/
|
|
7
|
+
export declare const standardWebhooksDescriptor: {
|
|
8
|
+
readonly provider: "standard-webhooks";
|
|
9
|
+
} & Pick<import("../internal/provider.cjs").ProviderDescriptor, "decodeSecret" | "parse">;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stripeDescriptor = void 0;
|
|
4
|
+
const bytes_js_1 = require("../internal/bytes.cjs");
|
|
5
|
+
const shared_js_1 = require("./shared.cjs");
|
|
6
|
+
const HEADER = 'stripe-signature';
|
|
7
|
+
const SCHEME = 'v1';
|
|
8
|
+
const TIMESTAMP_KEY = 't';
|
|
9
|
+
/**
|
|
10
|
+
* Stripe `Stripe-Signature` header: `t=<timestamp>,v1=<hex>[,v1=<hex>][,v0=...]`.
|
|
11
|
+
* Whitespace is permitted around comma-separated items. `t` is a singleton and
|
|
12
|
+
* is signed; repeated `t` is rejected. The signature covers `${t}.<raw body>`.
|
|
13
|
+
*/
|
|
14
|
+
exports.stripeDescriptor = {
|
|
15
|
+
provider: 'stripe',
|
|
16
|
+
decodeSecret(secret) {
|
|
17
|
+
// Stripe keys the HMAC with the literal secret string, including `whsec_`.
|
|
18
|
+
return (0, shared_js_1.decodeLiteralSecret)(secret, 'Stripe');
|
|
19
|
+
},
|
|
20
|
+
parse(headers, rawBody) {
|
|
21
|
+
const raw = headers.get(HEADER);
|
|
22
|
+
if (raw === undefined || raw.trim() === '') {
|
|
23
|
+
return (0, shared_js_1.failure)('missing-signature', 'No Stripe signature header was provided.');
|
|
24
|
+
}
|
|
25
|
+
const signatures = [];
|
|
26
|
+
let timestampText;
|
|
27
|
+
let unknownSchemes = 0;
|
|
28
|
+
for (const rawItem of raw.split(',')) {
|
|
29
|
+
const item = rawItem.trim();
|
|
30
|
+
const separator = item.indexOf('=');
|
|
31
|
+
if (item === '' || separator <= 0 || separator === item.length - 1) {
|
|
32
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Stripe signature header is malformed.');
|
|
33
|
+
}
|
|
34
|
+
const key = item.slice(0, separator);
|
|
35
|
+
const value = item.slice(separator + 1);
|
|
36
|
+
if (key === TIMESTAMP_KEY) {
|
|
37
|
+
if (timestampText !== undefined) {
|
|
38
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Stripe signature header is malformed.');
|
|
39
|
+
}
|
|
40
|
+
if (!(0, shared_js_1.isCanonicalTimestamp)(value)) {
|
|
41
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Stripe signature header is malformed.');
|
|
42
|
+
}
|
|
43
|
+
timestampText = value;
|
|
44
|
+
}
|
|
45
|
+
else if (key === SCHEME) {
|
|
46
|
+
const decoded = (0, shared_js_1.decodeHexSignature)(value);
|
|
47
|
+
// A malformed `v1` fails closed even when another `v1` value is valid.
|
|
48
|
+
if (decoded === null) {
|
|
49
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Stripe signature header is malformed.');
|
|
50
|
+
}
|
|
51
|
+
signatures.push(decoded);
|
|
52
|
+
}
|
|
53
|
+
else if ((0, shared_js_1.isSchemeToken)(key)) {
|
|
54
|
+
unknownSchemes += 1;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Stripe signature header is malformed.');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (timestampText === undefined) {
|
|
61
|
+
return (0, shared_js_1.failure)('missing-timestamp', 'No Stripe signature timestamp was provided.');
|
|
62
|
+
}
|
|
63
|
+
if (signatures.length === 0) {
|
|
64
|
+
return unknownSchemes > 0
|
|
65
|
+
? (0, shared_js_1.failure)('unsupported-scheme', 'No supported Stripe signature scheme was provided.')
|
|
66
|
+
: (0, shared_js_1.failure)('missing-signature', 'No Stripe signature value was provided.');
|
|
67
|
+
}
|
|
68
|
+
const identity = {
|
|
69
|
+
deliveryId: null,
|
|
70
|
+
eventId: null,
|
|
71
|
+
timestamp: Number(timestampText),
|
|
72
|
+
authenticatedFields: ['timestamp'],
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
signatures,
|
|
77
|
+
signedContent: (0, bytes_js_1.concatBytes)((0, bytes_js_1.utf8)(`${timestampText}.`), rawBody),
|
|
78
|
+
identity,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProviderDescriptor } from '../internal/provider.cjs';
|
|
2
|
+
/**
|
|
3
|
+
* Stripe `Stripe-Signature` header: `t=<timestamp>,v1=<hex>[,v1=<hex>][,v0=...]`.
|
|
4
|
+
* Whitespace is permitted around comma-separated items. `t` is a singleton and
|
|
5
|
+
* is signed; repeated `t` is rejected. The signature covers `${t}.<raw body>`.
|
|
6
|
+
*/
|
|
7
|
+
export declare const stripeDescriptor: ProviderDescriptor;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.svixDescriptor = void 0;
|
|
4
|
+
const shared_js_1 = require("./shared.cjs");
|
|
5
|
+
const standard_webhooks_protocol_js_1 = require("./standard-webhooks-protocol.cjs");
|
|
6
|
+
const BRANDED_HEADERS = {
|
|
7
|
+
id: 'svix-id',
|
|
8
|
+
timestamp: 'svix-timestamp',
|
|
9
|
+
signature: 'svix-signature',
|
|
10
|
+
};
|
|
11
|
+
const CANONICAL_HEADERS = {
|
|
12
|
+
id: 'webhook-id',
|
|
13
|
+
timestamp: 'webhook-timestamp',
|
|
14
|
+
signature: 'webhook-signature',
|
|
15
|
+
};
|
|
16
|
+
const brandedDescriptor = (0, standard_webhooks_protocol_js_1.createStandardWebhooksProtocol)('svix', 'Svix', BRANDED_HEADERS);
|
|
17
|
+
const canonicalDescriptor = (0, standard_webhooks_protocol_js_1.createStandardWebhooksProtocol)('svix', 'Svix', CANONICAL_HEADERS);
|
|
18
|
+
function familyPresent(headers, names) {
|
|
19
|
+
return headers.has(names.id) || headers.has(names.timestamp) || headers.has(names.signature);
|
|
20
|
+
}
|
|
21
|
+
function familyComplete(headers, names) {
|
|
22
|
+
const id = headers.get(names.id);
|
|
23
|
+
const timestamp = headers.get(names.timestamp);
|
|
24
|
+
const signature = headers.get(names.signature);
|
|
25
|
+
return id !== undefined && id !== ''
|
|
26
|
+
&& timestamp !== undefined && timestamp !== ''
|
|
27
|
+
&& signature !== undefined && signature !== '';
|
|
28
|
+
}
|
|
29
|
+
function familiesEqual(headers) {
|
|
30
|
+
return familyComplete(headers, BRANDED_HEADERS)
|
|
31
|
+
&& familyComplete(headers, CANONICAL_HEADERS)
|
|
32
|
+
&& headers.get(BRANDED_HEADERS.id) === headers.get(CANONICAL_HEADERS.id)
|
|
33
|
+
&& headers.get(BRANDED_HEADERS.timestamp) === headers.get(CANONICAL_HEADERS.timestamp)
|
|
34
|
+
&& headers.get(BRANDED_HEADERS.signature) === headers.get(CANONICAL_HEADERS.signature);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Svix accepts the branded `svix-*` and canonical `webhook-*` Standard
|
|
38
|
+
* Webhooks header families. A single family is parsed with the existing
|
|
39
|
+
* signature -> identity -> timestamp ordering. When both families have any
|
|
40
|
+
* header present, both must be complete and value-identical; otherwise the
|
|
41
|
+
* request is malformed. A failed family is never rescued by the other one.
|
|
42
|
+
*/
|
|
43
|
+
exports.svixDescriptor = {
|
|
44
|
+
provider: 'svix',
|
|
45
|
+
decodeSecret(secret) {
|
|
46
|
+
return brandedDescriptor.decodeSecret(secret);
|
|
47
|
+
},
|
|
48
|
+
parse(headers, rawBody) {
|
|
49
|
+
const brandedPresent = familyPresent(headers, BRANDED_HEADERS);
|
|
50
|
+
const canonicalPresent = familyPresent(headers, CANONICAL_HEADERS);
|
|
51
|
+
if (brandedPresent && canonicalPresent) {
|
|
52
|
+
if (!familiesEqual(headers)) {
|
|
53
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Svix signature headers are malformed.');
|
|
54
|
+
}
|
|
55
|
+
return brandedDescriptor.parse(headers, rawBody);
|
|
56
|
+
}
|
|
57
|
+
if (canonicalPresent)
|
|
58
|
+
return canonicalDescriptor.parse(headers, rawBody);
|
|
59
|
+
if (brandedPresent)
|
|
60
|
+
return brandedDescriptor.parse(headers, rawBody);
|
|
61
|
+
return (0, shared_js_1.failure)('missing-signature', 'No Svix signature header was provided.');
|
|
62
|
+
},
|
|
63
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProviderDescriptor } from '../internal/provider.cjs';
|
|
2
|
+
/**
|
|
3
|
+
* Svix accepts the branded `svix-*` and canonical `webhook-*` Standard
|
|
4
|
+
* Webhooks header families. A single family is parsed with the existing
|
|
5
|
+
* signature -> identity -> timestamp ordering. When both families have any
|
|
6
|
+
* header present, both must be complete and value-identical; otherwise the
|
|
7
|
+
* request is malformed. A failed family is never rescued by the other one.
|
|
8
|
+
*/
|
|
9
|
+
export declare const svixDescriptor: ProviderDescriptor;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.vercelDescriptor = void 0;
|
|
4
|
+
const shared_js_1 = require("./shared.cjs");
|
|
5
|
+
/** Scoped x-vercel-signature webhook recipe; exact bytes and literal UTF-8 keys.
|
|
6
|
+
* https://vercel.com/docs/webhooks/webhooks-api
|
|
7
|
+
* Either hex case is accepted as receiver policy. No freshness is inferred.
|
|
8
|
+
*/
|
|
9
|
+
exports.vercelDescriptor = {
|
|
10
|
+
provider: 'vercel',
|
|
11
|
+
crypto: { kind: 'hmac', hash: 'SHA-1', signatureBytes: 20 },
|
|
12
|
+
decodeSecret(secret) {
|
|
13
|
+
return (0, shared_js_1.decodeLiteralSecret)(secret, 'Vercel');
|
|
14
|
+
},
|
|
15
|
+
parse(headers, rawBody) {
|
|
16
|
+
const raw = headers.get('x-vercel-signature');
|
|
17
|
+
if (raw === undefined || raw.trim() === '') {
|
|
18
|
+
return (0, shared_js_1.failure)('missing-signature', 'No Vercel signature header was provided.');
|
|
19
|
+
}
|
|
20
|
+
const signature = decodeSignature(raw);
|
|
21
|
+
if (signature === null) {
|
|
22
|
+
return (0, shared_js_1.failure)('malformed-signature', 'The Vercel signature header is malformed.');
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
ok: true, signatures: [signature], signedContent: rawBody,
|
|
26
|
+
identity: { deliveryId: null, eventId: null, timestamp: null, authenticatedFields: [] },
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
/** Deliberately private: the existing shared SHA256 decoder stays 32 bytes. */
|
|
31
|
+
function decodeSignature(value) {
|
|
32
|
+
if (!/^[a-fA-F0-9]{40}$/.test(value))
|
|
33
|
+
return null;
|
|
34
|
+
const bytes = new Uint8Array(20);
|
|
35
|
+
for (let index = 0; index < bytes.length; index++) {
|
|
36
|
+
bytes[index] = Number.parseInt(value.slice(index * 2, index * 2 + 2), 16);
|
|
37
|
+
}
|
|
38
|
+
return bytes;
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NormalizedHeaders, ParseResult } from '../internal/provider.cjs';
|
|
2
|
+
/** Scoped x-vercel-signature webhook recipe; exact bytes and literal UTF-8 keys.
|
|
3
|
+
* https://vercel.com/docs/webhooks/webhooks-api
|
|
4
|
+
* Either hex case is accepted as receiver policy. No freshness is inferred.
|
|
5
|
+
*/
|
|
6
|
+
export declare const vercelDescriptor: {
|
|
7
|
+
provider: "vercel";
|
|
8
|
+
crypto: {
|
|
9
|
+
kind: "hmac";
|
|
10
|
+
hash: "SHA-1";
|
|
11
|
+
signatureBytes: 20;
|
|
12
|
+
};
|
|
13
|
+
decodeSecret(secret: string): Uint8Array;
|
|
14
|
+
parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
|
|
15
|
+
};
|