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,78 @@
|
|
|
1
|
+
import { HooklatchError } from '../errors.js';
|
|
2
|
+
import { decodeLiteralSecret } from './shared.js';
|
|
3
|
+
import { createStandardWebhooksProtocol } from './standard-webhooks-protocol.js';
|
|
4
|
+
const CANONICAL_HEADERS = {
|
|
5
|
+
id: 'webhook-id',
|
|
6
|
+
timestamp: 'webhook-timestamp',
|
|
7
|
+
signature: 'webhook-signature',
|
|
8
|
+
};
|
|
9
|
+
const currentDescriptor = createStandardWebhooksProtocol('polar', 'Polar', CANONICAL_HEADERS);
|
|
10
|
+
const legacyDescriptor = {
|
|
11
|
+
provider: 'polar',
|
|
12
|
+
decodeSecret(secret) {
|
|
13
|
+
return decodeLiteralSecret(secret, 'Polar');
|
|
14
|
+
},
|
|
15
|
+
parse: currentDescriptor.parse,
|
|
16
|
+
};
|
|
17
|
+
function invalidOptions() {
|
|
18
|
+
throw new HooklatchError('invalid-options', 'Invalid Polar provider options.');
|
|
19
|
+
}
|
|
20
|
+
function isConfigObject(value) {
|
|
21
|
+
if (value === null || typeof value !== 'object')
|
|
22
|
+
return false;
|
|
23
|
+
try {
|
|
24
|
+
return !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Read the selected mode as an own field and snapshot it before returning the
|
|
32
|
+
* descriptor. Inherited fields are not configuration; an own `secretMode:
|
|
33
|
+
* undefined` is invalid rather than an omitted optional field.
|
|
34
|
+
*/
|
|
35
|
+
function snapshotSecretMode(options) {
|
|
36
|
+
if (options === undefined)
|
|
37
|
+
return 'current';
|
|
38
|
+
if (!isConfigObject(options))
|
|
39
|
+
invalidOptions();
|
|
40
|
+
let keys = [];
|
|
41
|
+
let hasMode = false;
|
|
42
|
+
let mode = undefined;
|
|
43
|
+
try {
|
|
44
|
+
keys = Reflect.ownKeys(options).filter((key) => Object.getOwnPropertyDescriptor(options, key)?.enumerable);
|
|
45
|
+
hasMode = Object.hasOwn(options, 'secretMode');
|
|
46
|
+
mode = hasMode ? options.secretMode : undefined;
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
invalidOptions();
|
|
50
|
+
}
|
|
51
|
+
if (keys.some((key) => key !== 'secretMode'))
|
|
52
|
+
invalidOptions();
|
|
53
|
+
if (!hasMode)
|
|
54
|
+
return 'current';
|
|
55
|
+
if (mode !== 'current' && mode !== 'legacy')
|
|
56
|
+
invalidOptions();
|
|
57
|
+
return mode;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Polar webhooks use the canonical Standard Webhooks headers and signed
|
|
61
|
+
* content: `${webhook-id}.${webhook-timestamp}.${rawBody}`.
|
|
62
|
+
*
|
|
63
|
+
* `current` (the default) keeps the shared Standard Webhooks decoder: an
|
|
64
|
+
* optional `whsec_` prefix is removed and the remainder must decode as
|
|
65
|
+
* canonical Base64 to 24..64 bytes. `legacy` treats the complete secret string
|
|
66
|
+
* as literal UTF-8 bytes, including any `whsec_` prefix, with only the shared
|
|
67
|
+
* nonempty policy. Exactly one mode applies to every secret configured for the
|
|
68
|
+
* descriptor; there is no prefix inference or cross-mode fallback.
|
|
69
|
+
*
|
|
70
|
+
* Polar's SDK tries both key interpretations and floors fractional timestamps.
|
|
71
|
+
* Hooklatch deliberately does neither, and does not decode opaque bodies as
|
|
72
|
+
* text. Source:
|
|
73
|
+
* https://raw.githubusercontent.com/polarsource/polar/89917ad4fe752b06ff84bd31c313d2a53a6085f1/sdk/typescript/src/webhooks.ts
|
|
74
|
+
*/
|
|
75
|
+
export function createPolarDescriptor(options) {
|
|
76
|
+
return snapshotSecretMode(options) === 'current' ? currentDescriptor : legacyDescriptor;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=polar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"polar.js","sourceRoot":"","sources":["../../src/providers/polar.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AAWjF,MAAM,iBAAiB,GAAG;IACxB,EAAE,EAAE,YAAY;IAChB,SAAS,EAAE,mBAAmB;IAC9B,SAAS,EAAE,mBAAmB;CACtB,CAAC;AAEX,MAAM,iBAAiB,GAAG,8BAA8B,CAAC,OAAO,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC;AAE9F,MAAM,gBAAgB,GAAoB;IACxC,QAAQ,EAAE,OAAO;IAEjB,YAAY,CAAC,MAAc;QACzB,OAAO,mBAAmB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,EAAE,iBAAiB,CAAC,KAAK;CAC/B,CAAC;AAEF,SAAS,cAAc;IACrB,MAAM,IAAI,cAAc,CAAC,iBAAiB,EAAE,iCAAiC,CAAC,CAAC;AACjF,CAAC;AAED,SAAS,cAAc,CACrB,KAAgD;IAEhD,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAE9D,IAAI,CAAC;QACH,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,OAA2C;IACrE,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAE5C,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC;QAAE,cAAc,EAAE,CAAC;IAE/C,IAAI,IAAI,GAAiC,EAAE,CAAC;IAC5C,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,IAAI,GAAyC,SAAS,CAAC;IAE3D,IAAI,CAAC;QACH,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,CACpC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,wBAAwB,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,UAAU,CACnE,CAAC;QACF,OAAO,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;QAC/C,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,cAAc,EAAE,CAAC;IACnB,CAAC;IAED,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,YAAY,CAAC;QAAE,cAAc,EAAE,CAAC;IAE/D,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ;QAAE,cAAc,EAAE,CAAC;IAE9D,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAgC;IACpE,OAAO,kBAAkB,CAAC,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,gBAAgB,CAAC;AAC1F,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { NormalizedHeaders, ParseResult } from '../internal/provider.js';
|
|
2
|
+
/**
|
|
3
|
+
* Razorpay `X-Razorpay-Signature`: a single hex HMAC-SHA256 over the raw request
|
|
4
|
+
* body only, keyed with the literal dashboard webhook secret. A missing,
|
|
5
|
+
* blank, prefixed, non-hex, wrong-length or comma-combined value fails closed.
|
|
6
|
+
* The optional `x-razorpay-event-id` is unsigned metadata and never proves
|
|
7
|
+
* authenticity.
|
|
8
|
+
*
|
|
9
|
+
* Sources:
|
|
10
|
+
* - https://razorpay.com/docs/webhooks/validate-test/
|
|
11
|
+
* - https://github.com/razorpay/razorpay-node/blob/b9e8527225a7322ba2671a3bc0b848fd019d2d8c/lib/utils/razorpay-utils.js
|
|
12
|
+
*
|
|
13
|
+
* No Razorpay-published signature vector is claimed; test anchors are synthetic.
|
|
14
|
+
*/
|
|
15
|
+
export declare const razorpayDescriptor: {
|
|
16
|
+
provider: "razorpay";
|
|
17
|
+
decodeSecret(secret: string): Uint8Array;
|
|
18
|
+
parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=razorpay.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"razorpay.d.ts","sourceRoot":"","sources":["../../src/providers/razorpay.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAsB,MAAM,yBAAyB,CAAC;AAOlG;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB;;yBAGR,MAAM,GAAG,UAAU;mBAIzB,iBAAiB,WAAW,UAAU,GAAG,WAAW;CAwB6B,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { decodeHexSignature, decodeLiteralSecret, failure } from './shared.js';
|
|
2
|
+
const SIGNATURE_HEADER = 'x-razorpay-signature';
|
|
3
|
+
const EVENT_ID_HEADER = 'x-razorpay-event-id';
|
|
4
|
+
/**
|
|
5
|
+
* Razorpay `X-Razorpay-Signature`: a single hex HMAC-SHA256 over the raw request
|
|
6
|
+
* body only, keyed with the literal dashboard webhook secret. A missing,
|
|
7
|
+
* blank, prefixed, non-hex, wrong-length or comma-combined value fails closed.
|
|
8
|
+
* The optional `x-razorpay-event-id` is unsigned metadata and never proves
|
|
9
|
+
* authenticity.
|
|
10
|
+
*
|
|
11
|
+
* Sources:
|
|
12
|
+
* - https://razorpay.com/docs/webhooks/validate-test/
|
|
13
|
+
* - https://github.com/razorpay/razorpay-node/blob/b9e8527225a7322ba2671a3bc0b848fd019d2d8c/lib/utils/razorpay-utils.js
|
|
14
|
+
*
|
|
15
|
+
* No Razorpay-published signature vector is claimed; test anchors are synthetic.
|
|
16
|
+
*/
|
|
17
|
+
export const razorpayDescriptor = {
|
|
18
|
+
provider: 'razorpay',
|
|
19
|
+
decodeSecret(secret) {
|
|
20
|
+
return decodeLiteralSecret(secret, 'Razorpay');
|
|
21
|
+
},
|
|
22
|
+
parse(headers, rawBody) {
|
|
23
|
+
const raw = headers.get(SIGNATURE_HEADER);
|
|
24
|
+
if (raw === undefined || raw.trim() === '') {
|
|
25
|
+
return failure('missing-signature', 'No Razorpay signature header was provided.');
|
|
26
|
+
}
|
|
27
|
+
const signature = decodeHexSignature(raw);
|
|
28
|
+
if (signature === null) {
|
|
29
|
+
return failure('malformed-signature', 'The Razorpay signature header is malformed.');
|
|
30
|
+
}
|
|
31
|
+
const eventId = headers.get(EVENT_ID_HEADER);
|
|
32
|
+
const identity = {
|
|
33
|
+
deliveryId: null,
|
|
34
|
+
eventId: eventId === undefined || eventId.trim() === '' ? null : eventId,
|
|
35
|
+
timestamp: null,
|
|
36
|
+
authenticatedFields: [],
|
|
37
|
+
};
|
|
38
|
+
return { ok: true, signatures: [signature], signedContent: rawBody, identity };
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
//# sourceMappingURL=razorpay.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"razorpay.js","sourceRoot":"","sources":["../../src/providers/razorpay.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE/E,MAAM,gBAAgB,GAAG,sBAAsB,CAAC;AAChD,MAAM,eAAe,GAAG,qBAAqB,CAAC;AAE9C;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG;IAChC,QAAQ,EAAE,UAAU;IAEpB,YAAY,CAAC,MAAc;QACzB,OAAO,mBAAmB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACjD,CAAC;IAED,KAAK,CAAC,OAA0B,EAAE,OAAmB;QACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAE1C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC3C,OAAO,OAAO,CAAC,mBAAmB,EAAE,4CAA4C,CAAC,CAAC;QACpF,CAAC;QAED,MAAM,SAAS,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAE1C,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC,qBAAqB,EAAE,6CAA6C,CAAC,CAAC;QACvF,CAAC;QAED,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAE7C,MAAM,QAAQ,GAAkB;YAC9B,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO;YACxE,SAAS,EAAE,IAAI;YACf,mBAAmB,EAAE,EAAE;SACxB,CAAC;QAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACjF,CAAC;CAC+F,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replicate webhooks use the canonical Standard Webhooks `webhook-id`,
|
|
3
|
+
* `webhook-timestamp` and `webhook-signature` headers. The secret is
|
|
4
|
+
* Base64-decoded (an optional `whsec_` prefix is stripped) and must decode to
|
|
5
|
+
* 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 Replicate identity.
|
|
9
|
+
* A signature minted by another canonical alias verifies here when the same
|
|
10
|
+
* secret is configured, so use endpoint-specific secrets. No prediction or
|
|
11
|
+
* event payload is parsed and no resource id is inferred.
|
|
12
|
+
*
|
|
13
|
+
* No Replicate-published signature vector is claimed; the direct tests use
|
|
14
|
+
* synthetic anchors. Source:
|
|
15
|
+
* https://replicate.com/docs/topics/webhooks/verify-webhook
|
|
16
|
+
*/
|
|
17
|
+
export declare const replicateDescriptor: {
|
|
18
|
+
readonly provider: "replicate";
|
|
19
|
+
} & Pick<import("../internal/provider.js").ProviderDescriptor, "decodeSecret" | "parse">;
|
|
20
|
+
//# sourceMappingURL=replicate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replicate.d.ts","sourceRoot":"","sources":["../../src/providers/replicate.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,mBAAmB;;wFAQ/B,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { createStandardWebhooksProtocol } from './standard-webhooks-protocol.js';
|
|
2
|
+
/**
|
|
3
|
+
* Replicate webhooks use the canonical Standard Webhooks `webhook-id`,
|
|
4
|
+
* `webhook-timestamp` and `webhook-signature` headers. The secret is
|
|
5
|
+
* Base64-decoded (an optional `whsec_` prefix is stripped) and must decode to
|
|
6
|
+
* 24-64 bytes; the signature covers the exact
|
|
7
|
+
* `${webhook-id}.${webhook-timestamp}.` bytes followed by the raw body.
|
|
8
|
+
*
|
|
9
|
+
* These headers authenticate protocol bytes, not a unique Replicate identity.
|
|
10
|
+
* A signature minted by another canonical alias verifies here when the same
|
|
11
|
+
* secret is configured, so use endpoint-specific secrets. No prediction or
|
|
12
|
+
* event payload is parsed and no resource id is inferred.
|
|
13
|
+
*
|
|
14
|
+
* No Replicate-published signature vector is claimed; the direct tests use
|
|
15
|
+
* synthetic anchors. Source:
|
|
16
|
+
* https://replicate.com/docs/topics/webhooks/verify-webhook
|
|
17
|
+
*/
|
|
18
|
+
export const replicateDescriptor = createStandardWebhooksProtocol('replicate', 'Replicate', {
|
|
19
|
+
id: 'webhook-id',
|
|
20
|
+
timestamp: 'webhook-timestamp',
|
|
21
|
+
signature: 'webhook-signature',
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=replicate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replicate.js","sourceRoot":"","sources":["../../src/providers/replicate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AAEjF;;;;;;;;;;;;;;;GAeG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG,8BAA8B,CAC/D,WAAW,EACX,WAAW,EACX;IACE,EAAE,EAAE,YAAY;IAChB,SAAS,EAAE,mBAAmB;IAC9B,SAAS,EAAE,mBAAmB;CAC/B,CACF,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NormalizedHeaders, ParseResult } from '../internal/provider.js';
|
|
2
|
+
/**
|
|
3
|
+
* Sanity: `sanity-webhook-signature: t=<unix milliseconds>,v1=<base64url>`.
|
|
4
|
+
*
|
|
5
|
+
* The HMAC-SHA256 covers UTF8(the original `t` text + ".") concatenated with
|
|
6
|
+
* the exact raw body. The secret is the literal, untrimmed UTF-8 text; it is
|
|
7
|
+
* never Base64-decoded. The digest is canonical unpadded Base64url for exactly
|
|
8
|
+
* 32 bytes, decoded by a descriptor-local decoder so the shared standard
|
|
9
|
+
* Base64 handling stays unchanged.
|
|
10
|
+
*
|
|
11
|
+
* The accepted header is exactly `t=<ms>,v1=<digest>`: ordered fields, one
|
|
12
|
+
* each, no whitespace anywhere or outer trimming, no extra or duplicate
|
|
13
|
+
* fields, no semicolon separators, no padding, and no alternate versions. A
|
|
14
|
+
* missing or blank header is `missing-signature`; every other nonmatching
|
|
15
|
+
* structure is `malformed-signature`, with no alternate reason precedence.
|
|
16
|
+
*
|
|
17
|
+
* `t` must be a canonical nonnegative safe decimal integer of milliseconds
|
|
18
|
+
* with no leading zeros, at or after 2021-01-01T00:00:00.000Z. Identity is
|
|
19
|
+
* `timestamp = Number(t) / 1000` with no delivery or event id; freshness is
|
|
20
|
+
* enforced by core and is never evaluated here.
|
|
21
|
+
*
|
|
22
|
+
* The reference SDK verifies UTF-8 text and refuses to sign an empty payload.
|
|
23
|
+
* This descriptor accepts empty, NUL, invalid UTF-8, and non-JSON bodies as
|
|
24
|
+
* opaque bytes, so it extends rather than matches that behavior, and it does
|
|
25
|
+
* not claim exact SDK parity or live interoperability. The strict grammar
|
|
26
|
+
* is checked at parse time. The reference decoder regex accepts spaces and
|
|
27
|
+
* trimming, but full SDK verification compares the original header to canonical
|
|
28
|
+
* re-encoding and also rejects these noncanonical header variants.
|
|
29
|
+
*
|
|
30
|
+
* Sources:
|
|
31
|
+
* - https://www.sanity.io/docs/content-lake/webhook-best-practices
|
|
32
|
+
* - https://github.com/sanity-io/webhook-toolkit/blob/e0c211ed6cd80465a70829e335c9e219f37839ba/src/signature.ts
|
|
33
|
+
*/
|
|
34
|
+
export declare const sanityDescriptor: {
|
|
35
|
+
provider: "sanity";
|
|
36
|
+
decodeSecret(secret: string): Uint8Array;
|
|
37
|
+
parse(headers: NormalizedHeaders, rawBody: Uint8Array): ParseResult;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=sanity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanity.d.ts","sourceRoot":"","sources":["../../src/providers/sanity.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAsB,MAAM,yBAAyB,CAAC;AA6GlG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,gBAAgB;;yBAGN,MAAM,GAAG,UAAU;mBAIzB,iBAAiB,WAAW,UAAU,GAAG,WAAW;CA2C2B,CAAC"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { concatBytes, utf8 } from '../internal/bytes.js';
|
|
2
|
+
import { decodeLiteralSecret, failure, isCanonicalTimestamp } from './shared.js';
|
|
3
|
+
const SIGNATURE_HEADER = 'sanity-webhook-signature';
|
|
4
|
+
const TIMESTAMP_FIELD = 't=';
|
|
5
|
+
const SIGNATURE_FIELD = 'v1=';
|
|
6
|
+
const DIGEST_CHARACTERS = 43;
|
|
7
|
+
const BASE64URL_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
|
|
8
|
+
/**
|
|
9
|
+
* Sanity does not send signed payloads before 2021-01-01T00:00:00.000Z, so a
|
|
10
|
+
* millisecond timestamp below this bound is not a Sanity delivery. The bound
|
|
11
|
+
* mirrors the reference toolkit; the failure code is Hooklatch policy.
|
|
12
|
+
*/
|
|
13
|
+
const MINIMUM_TIMESTAMP_MS = 1609459200000;
|
|
14
|
+
/**
|
|
15
|
+
* Split the exact accepted header grammar `t=<ms>,v1=<digest>`.
|
|
16
|
+
*
|
|
17
|
+
* The fields are ordered, appear exactly once, and carry no whitespace, extra
|
|
18
|
+
* fields, duplicate fields, semicolon separators, or alternate versions. Any
|
|
19
|
+
* other structure is rejected instead of being repaired.
|
|
20
|
+
*/
|
|
21
|
+
function splitHeader(raw) {
|
|
22
|
+
const separator = raw.indexOf(',');
|
|
23
|
+
if (separator < 0)
|
|
24
|
+
return null;
|
|
25
|
+
const timestampField = raw.slice(0, separator);
|
|
26
|
+
const signatureField = raw.slice(separator + 1);
|
|
27
|
+
if (!timestampField.startsWith(TIMESTAMP_FIELD))
|
|
28
|
+
return null;
|
|
29
|
+
if (!signatureField.startsWith(SIGNATURE_FIELD))
|
|
30
|
+
return null;
|
|
31
|
+
const timestampText = timestampField.slice(TIMESTAMP_FIELD.length);
|
|
32
|
+
const digest = signatureField.slice(SIGNATURE_FIELD.length);
|
|
33
|
+
if (timestampText.length === 0)
|
|
34
|
+
return null;
|
|
35
|
+
return { timestampText, digest };
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Encode the 32 decoded digest bytes as canonical unpadded Base64url.
|
|
39
|
+
*
|
|
40
|
+
* 32 bytes are 256 bits: 42 characters carry 252 of them and the last four
|
|
41
|
+
* bits close the 43rd character.
|
|
42
|
+
*/
|
|
43
|
+
function encodeBase64Url(bytes) {
|
|
44
|
+
let output = '';
|
|
45
|
+
let buffer = 0;
|
|
46
|
+
let bits = 0;
|
|
47
|
+
for (const byte of bytes) {
|
|
48
|
+
buffer = (buffer << 8) | byte;
|
|
49
|
+
bits += 8;
|
|
50
|
+
while (bits >= 6) {
|
|
51
|
+
bits -= 6;
|
|
52
|
+
output += BASE64URL_ALPHABET.charAt((buffer >>> bits) & 0x3f);
|
|
53
|
+
buffer &= (1 << bits) - 1;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return output + BASE64URL_ALPHABET.charAt((buffer << (6 - bits)) & 0x3f);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Decode exactly 43 canonical unpadded Base64url characters into 32 bytes.
|
|
60
|
+
*
|
|
61
|
+
* The 43 characters carry 258 bits, so 32 bytes consume 256 of them and the
|
|
62
|
+
* two remaining low bits of the final character are pad bits. Re-encoding the
|
|
63
|
+
* decoded bytes must reproduce the input exactly, which rejects a digest whose
|
|
64
|
+
* pad bits are not zero.
|
|
65
|
+
*/
|
|
66
|
+
function decodeBase64UrlDigest(value) {
|
|
67
|
+
if (value.length !== DIGEST_CHARACTERS)
|
|
68
|
+
return null;
|
|
69
|
+
const bytes = new Uint8Array(32);
|
|
70
|
+
let buffer = 0;
|
|
71
|
+
let bits = 0;
|
|
72
|
+
let offset = 0;
|
|
73
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
74
|
+
const digit = BASE64URL_ALPHABET.indexOf(value.charAt(index));
|
|
75
|
+
if (digit < 0)
|
|
76
|
+
return null;
|
|
77
|
+
buffer = (buffer << 6) | digit;
|
|
78
|
+
bits += 6;
|
|
79
|
+
if (bits < 8)
|
|
80
|
+
continue;
|
|
81
|
+
bits -= 8;
|
|
82
|
+
bytes[offset] = (buffer >>> bits) & 0xff;
|
|
83
|
+
offset += 1;
|
|
84
|
+
buffer &= (1 << bits) - 1;
|
|
85
|
+
}
|
|
86
|
+
return encodeBase64Url(bytes) === value ? bytes : null;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Sanity: `sanity-webhook-signature: t=<unix milliseconds>,v1=<base64url>`.
|
|
90
|
+
*
|
|
91
|
+
* The HMAC-SHA256 covers UTF8(the original `t` text + ".") concatenated with
|
|
92
|
+
* the exact raw body. The secret is the literal, untrimmed UTF-8 text; it is
|
|
93
|
+
* never Base64-decoded. The digest is canonical unpadded Base64url for exactly
|
|
94
|
+
* 32 bytes, decoded by a descriptor-local decoder so the shared standard
|
|
95
|
+
* Base64 handling stays unchanged.
|
|
96
|
+
*
|
|
97
|
+
* The accepted header is exactly `t=<ms>,v1=<digest>`: ordered fields, one
|
|
98
|
+
* each, no whitespace anywhere or outer trimming, no extra or duplicate
|
|
99
|
+
* fields, no semicolon separators, no padding, and no alternate versions. A
|
|
100
|
+
* missing or blank header is `missing-signature`; every other nonmatching
|
|
101
|
+
* structure is `malformed-signature`, with no alternate reason precedence.
|
|
102
|
+
*
|
|
103
|
+
* `t` must be a canonical nonnegative safe decimal integer of milliseconds
|
|
104
|
+
* with no leading zeros, at or after 2021-01-01T00:00:00.000Z. Identity is
|
|
105
|
+
* `timestamp = Number(t) / 1000` with no delivery or event id; freshness is
|
|
106
|
+
* enforced by core and is never evaluated here.
|
|
107
|
+
*
|
|
108
|
+
* The reference SDK verifies UTF-8 text and refuses to sign an empty payload.
|
|
109
|
+
* This descriptor accepts empty, NUL, invalid UTF-8, and non-JSON bodies as
|
|
110
|
+
* opaque bytes, so it extends rather than matches that behavior, and it does
|
|
111
|
+
* not claim exact SDK parity or live interoperability. The strict grammar
|
|
112
|
+
* is checked at parse time. The reference decoder regex accepts spaces and
|
|
113
|
+
* trimming, but full SDK verification compares the original header to canonical
|
|
114
|
+
* re-encoding and also rejects these noncanonical header variants.
|
|
115
|
+
*
|
|
116
|
+
* Sources:
|
|
117
|
+
* - https://www.sanity.io/docs/content-lake/webhook-best-practices
|
|
118
|
+
* - https://github.com/sanity-io/webhook-toolkit/blob/e0c211ed6cd80465a70829e335c9e219f37839ba/src/signature.ts
|
|
119
|
+
*/
|
|
120
|
+
export const sanityDescriptor = {
|
|
121
|
+
provider: 'sanity',
|
|
122
|
+
decodeSecret(secret) {
|
|
123
|
+
return decodeLiteralSecret(secret, 'Sanity');
|
|
124
|
+
},
|
|
125
|
+
parse(headers, rawBody) {
|
|
126
|
+
const raw = headers.get(SIGNATURE_HEADER);
|
|
127
|
+
if (raw === undefined || raw.trim() === '') {
|
|
128
|
+
return failure('missing-signature', 'No Sanity signature header was provided.');
|
|
129
|
+
}
|
|
130
|
+
const fields = splitHeader(raw);
|
|
131
|
+
if (fields === null) {
|
|
132
|
+
return failure('malformed-signature', 'The Sanity signature header is malformed.');
|
|
133
|
+
}
|
|
134
|
+
if (!isCanonicalTimestamp(fields.timestampText)) {
|
|
135
|
+
return failure('malformed-signature', 'The Sanity signature header is malformed.');
|
|
136
|
+
}
|
|
137
|
+
const timestamp = Number(fields.timestampText);
|
|
138
|
+
if (timestamp < MINIMUM_TIMESTAMP_MS) {
|
|
139
|
+
return failure('malformed-signature', 'The Sanity signature header is malformed.');
|
|
140
|
+
}
|
|
141
|
+
const digest = decodeBase64UrlDigest(fields.digest);
|
|
142
|
+
if (digest === null) {
|
|
143
|
+
return failure('malformed-signature', 'The Sanity signature header is malformed.');
|
|
144
|
+
}
|
|
145
|
+
const identity = {
|
|
146
|
+
deliveryId: null,
|
|
147
|
+
eventId: null,
|
|
148
|
+
timestamp: timestamp / 1000,
|
|
149
|
+
authenticatedFields: ['timestamp'],
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
ok: true,
|
|
153
|
+
signatures: [digest],
|
|
154
|
+
signedContent: concatBytes(utf8(`${fields.timestampText}.`), rawBody),
|
|
155
|
+
identity,
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
//# sourceMappingURL=sanity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sanity.js","sourceRoot":"","sources":["../../src/providers/sanity.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { NormalizedHeaders, ParseResult } from '../internal/provider.js';
|
|
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
|
+
};
|
|
26
|
+
//# sourceMappingURL=sentry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/providers/sentry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAsB,MAAM,yBAAyB,CAAC;AAMlG;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB;;yBAGN,MAAM,GAAG,UAAU;mBAIzB,iBAAiB,WAAW,UAAU,GAAG,WAAW;CAsB2B,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { decodeHexSignature, decodeLiteralSecret, failure } from './shared.js';
|
|
2
|
+
const SIGNATURE_HEADER = 'sentry-hook-signature';
|
|
3
|
+
/**
|
|
4
|
+
* Sentry App integration-platform webhooks: `Sentry-Hook-Signature` is a single
|
|
5
|
+
* 64-character hex HMAC-SHA256 over the exact raw request body, keyed with the
|
|
6
|
+
* literal app client secret. Missing or trim-blank -> missing-signature; every
|
|
7
|
+
* other nonconforming value (prefixes, whitespace, comma-combined duplicates,
|
|
8
|
+
* lists, wrong length or non-hex) fails closed.
|
|
9
|
+
*
|
|
10
|
+
* `Request-Id`, `Sentry-Hook-Timestamp` and `Sentry-Hook-Resource` are outside
|
|
11
|
+
* the MAC: they cannot establish freshness or trusted identity. Sentry's docs
|
|
12
|
+
* examples re-serialize the parsed body, which can change the signed bytes; this
|
|
13
|
+
* descriptor always returns the untouched `rawBody` as signed content.
|
|
14
|
+
*
|
|
15
|
+
* Scope is Sentry App integration-platform webhooks only, not every Sentry
|
|
16
|
+
* webhook product. Sources:
|
|
17
|
+
* - https://docs.sentry.io/integrations/integration-platform/webhooks/
|
|
18
|
+
* - https://github.com/getsentry/sentry/blob/72757dae9b6d527e564c81d4921dec0d62d7beca/src/sentry/sentry_apps/api/serializers/app_platform_event.py
|
|
19
|
+
*
|
|
20
|
+
* No Sentry-published signature vector is claimed; test anchors are synthetic.
|
|
21
|
+
*/
|
|
22
|
+
export const sentryDescriptor = {
|
|
23
|
+
provider: 'sentry',
|
|
24
|
+
decodeSecret(secret) {
|
|
25
|
+
return decodeLiteralSecret(secret, 'Sentry App');
|
|
26
|
+
},
|
|
27
|
+
parse(headers, rawBody) {
|
|
28
|
+
const raw = headers.get(SIGNATURE_HEADER);
|
|
29
|
+
if (raw === undefined || raw.trim() === '') {
|
|
30
|
+
return failure('missing-signature', 'No Sentry App signature header was provided.');
|
|
31
|
+
}
|
|
32
|
+
const signature = decodeHexSignature(raw);
|
|
33
|
+
if (signature === null) {
|
|
34
|
+
return failure('malformed-signature', 'The Sentry App signature header is malformed.');
|
|
35
|
+
}
|
|
36
|
+
const identity = {
|
|
37
|
+
deliveryId: null,
|
|
38
|
+
eventId: null,
|
|
39
|
+
timestamp: null,
|
|
40
|
+
authenticatedFields: [],
|
|
41
|
+
};
|
|
42
|
+
return { ok: true, signatures: [signature], signedContent: rawBody, identity };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=sentry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sentry.js","sourceRoot":"","sources":["../../src/providers/sentry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE/E,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AAEjD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B,QAAQ,EAAE,QAAQ;IAElB,YAAY,CAAC,MAAc;QACzB,OAAO,mBAAmB,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,OAA0B,EAAE,OAAmB;QACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAE1C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC3C,OAAO,OAAO,CAAC,mBAAmB,EAAE,8CAA8C,CAAC,CAAC;QACtF,CAAC;QAED,MAAM,SAAS,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAE1C,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC,qBAAqB,EAAE,+CAA+C,CAAC,CAAC;QACzF,CAAC;QAED,MAAM,QAAQ,GAAkB;YAC9B,UAAU,EAAE,IAAI;YAChB,OAAO,EAAE,IAAI;YACb,SAAS,EAAE,IAAI;YACf,mBAAmB,EAAE,EAAE;SACxB,CAAC;QAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACjF,CAAC;CAC6F,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { FailureDetails, VerificationFailureReason } from '../types.js';
|
|
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;
|
|
15
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../../src/providers/shared.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7E,0DAA0D;AAC1D,eAAO,MAAM,eAAe,KAAK,CAAC;AAKlC,wBAAgB,OAAO,CACrB,MAAM,EAAE,yBAAyB,EACjC,OAAO,EAAE,MAAM,GACd,cAAc,CAEhB;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEpD;AAED,6EAA6E;AAC7E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAK3D;AAED,kEAAkE;AAClE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAInE;AAED,sDAAsD;AACtD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAItE;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,IAAI,MAAM,CAEjE;AAED,uEAAuE;AACvE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAM7E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { HooklatchError } from '../errors.js';
|
|
2
|
+
import { decodeBase64, decodeHex, utf8 } from '../internal/bytes.js';
|
|
3
|
+
/** HMAC-SHA256 signatures are always exactly 32 bytes. */
|
|
4
|
+
export const SIGNATURE_BYTES = 32;
|
|
5
|
+
/** Scheme/key tokens accepted in provider signature headers (e.g. `v1`, `sha256`). */
|
|
6
|
+
const TOKEN = /^[A-Za-z0-9_-]+$/;
|
|
7
|
+
export function failure(reason, message) {
|
|
8
|
+
return { ok: false, reason, message };
|
|
9
|
+
}
|
|
10
|
+
export function isSchemeToken(value) {
|
|
11
|
+
return TOKEN.test(value);
|
|
12
|
+
}
|
|
13
|
+
/** Canonical decimal integer: `0`, or a nonzero digit followed by digits. */
|
|
14
|
+
export function isCanonicalTimestamp(value) {
|
|
15
|
+
if (!/^(?:0|[1-9]\d*)$/.test(value))
|
|
16
|
+
return false;
|
|
17
|
+
const numeric = Number(value);
|
|
18
|
+
return Number.isSafeInteger(numeric) && numeric >= 0;
|
|
19
|
+
}
|
|
20
|
+
/** Hex signature of exactly 32 bytes; accepts either hex case. */
|
|
21
|
+
export function decodeHexSignature(value) {
|
|
22
|
+
const bytes = decodeHex(value);
|
|
23
|
+
return bytes !== null && bytes.byteLength === SIGNATURE_BYTES ? bytes : null;
|
|
24
|
+
}
|
|
25
|
+
/** Canonical Base64 signature of exactly 32 bytes. */
|
|
26
|
+
export function decodeBase64Signature(value) {
|
|
27
|
+
const bytes = decodeBase64(value);
|
|
28
|
+
return bytes !== null && bytes.byteLength === SIGNATURE_BYTES ? bytes : null;
|
|
29
|
+
}
|
|
30
|
+
export function isNonEmptySecret(secret) {
|
|
31
|
+
return typeof secret === 'string' && secret.length > 0;
|
|
32
|
+
}
|
|
33
|
+
/** Literal, untrimmed UTF-8 secret bytes for Stripe/GitHub/Shopify. */
|
|
34
|
+
export function decodeLiteralSecret(secret, label) {
|
|
35
|
+
if (!isNonEmptySecret(secret)) {
|
|
36
|
+
throw new HooklatchError('invalid-secret', `Invalid ${label} signing secret.`);
|
|
37
|
+
}
|
|
38
|
+
return utf8(secret);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../../src/providers/shared.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,sBAAsB,CAAC;AAGrE,0DAA0D;AAC1D,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,CAAC;AAElC,sFAAsF;AACtF,MAAM,KAAK,GAAG,kBAAkB,CAAC;AAEjC,MAAM,UAAU,OAAO,CACrB,MAAiC,EACjC,OAAe;IAEf,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,oBAAoB,CAAC,KAAa;IAChD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAE9B,OAAO,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC;AACvD,CAAC;AAED,kEAAkE;AAClE,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC;IAE/B,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/E,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,qBAAqB,CAAC,KAAa;IACjD,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAElC,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,UAAU,KAAK,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc;IAC7C,OAAO,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;AACzD,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,mBAAmB,CAAC,MAAc,EAAE,KAAa;IAC/D,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,cAAc,CAAC,gBAAgB,EAAE,WAAW,KAAK,kBAAkB,CAAC,CAAC;IACjF,CAAC;IAED,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC;AACtB,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ProviderDescriptor } from '../internal/provider.js';
|
|
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;
|
|
9
|
+
//# sourceMappingURL=shopify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shopify.d.ts","sourceRoot":"","sources":["../../src/providers/shopify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkC,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAclG;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,kBA6B/B,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { decodeBase64Signature, decodeLiteralSecret, failure } from './shared.js';
|
|
2
|
+
const SIGNATURE_HEADER = 'x-shopify-hmac-sha256';
|
|
3
|
+
const DELIVERY_HEADER = 'x-shopify-webhook-id';
|
|
4
|
+
const EVENT_HEADER = 'x-shopify-event-id';
|
|
5
|
+
function optionalHeader(headers, name) {
|
|
6
|
+
const value = headers.get(name);
|
|
7
|
+
return value === undefined || value === '' ? null : value;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Shopify HTTPS `X-Shopify-Hmac-Sha256`: canonical Base64 HMAC-SHA256 over the
|
|
11
|
+
* raw body only, keyed with the app client secret. Delivery and event IDs are
|
|
12
|
+
* unsigned metadata. EventBridge/Pub/Sub deliveries carry no HMAC header and are
|
|
13
|
+
* therefore not verifiable by this descriptor.
|
|
14
|
+
*/
|
|
15
|
+
export const shopifyDescriptor = {
|
|
16
|
+
provider: 'shopify',
|
|
17
|
+
decodeSecret(secret) {
|
|
18
|
+
return decodeLiteralSecret(secret, 'Shopify');
|
|
19
|
+
},
|
|
20
|
+
parse(headers, rawBody) {
|
|
21
|
+
const raw = headers.get(SIGNATURE_HEADER);
|
|
22
|
+
if (raw === undefined || raw.trim() === '') {
|
|
23
|
+
return failure('missing-signature', 'No Shopify signature header was provided.');
|
|
24
|
+
}
|
|
25
|
+
const signature = decodeBase64Signature(raw);
|
|
26
|
+
if (signature === null) {
|
|
27
|
+
return failure('malformed-signature', 'The Shopify signature header is malformed.');
|
|
28
|
+
}
|
|
29
|
+
const identity = {
|
|
30
|
+
deliveryId: optionalHeader(headers, DELIVERY_HEADER),
|
|
31
|
+
eventId: optionalHeader(headers, EVENT_HEADER),
|
|
32
|
+
timestamp: null,
|
|
33
|
+
authenticatedFields: [],
|
|
34
|
+
};
|
|
35
|
+
return { ok: true, signatures: [signature], signedContent: rawBody, identity };
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=shopify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shopify.js","sourceRoot":"","sources":["../../src/providers/shopify.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAElF,MAAM,gBAAgB,GAAG,uBAAuB,CAAC;AACjD,MAAM,eAAe,GAAG,sBAAsB,CAAC;AAC/C,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAE1C,SAAS,cAAc,CAAC,OAA0B,EAAE,IAAY;IAC9D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAEhC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC;AAC5D,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAuB;IACnD,QAAQ,EAAE,SAAS;IAEnB,YAAY,CAAC,MAAc;QACzB,OAAO,mBAAmB,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAChD,CAAC;IAED,KAAK,CAAC,OAA0B,EAAE,OAAmB;QACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAE1C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAC3C,OAAO,OAAO,CAAC,mBAAmB,EAAE,2CAA2C,CAAC,CAAC;QACnF,CAAC;QAED,MAAM,SAAS,GAAG,qBAAqB,CAAC,GAAG,CAAC,CAAC;QAE7C,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;YACvB,OAAO,OAAO,CAAC,qBAAqB,EAAE,4CAA4C,CAAC,CAAC;QACtF,CAAC;QAED,MAAM,QAAQ,GAAkB;YAC9B,UAAU,EAAE,cAAc,CAAC,OAAO,EAAE,eAAe,CAAC;YACpD,OAAO,EAAE,cAAc,CAAC,OAAO,EAAE,YAAY,CAAC;YAC9C,SAAS,EAAE,IAAI;YACf,mBAAmB,EAAE,EAAE;SACxB,CAAC;QAEF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,SAAS,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;IACjF,CAAC;CACF,CAAC"}
|