@voltro/plugin-webhooks 0.32.0 → 0.34.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/dist/mixin.d.ts CHANGED
@@ -32,7 +32,7 @@ export declare const _voltroWebhookDeliveriesTable: Table<"_voltro_webhook_deliv
32
32
  readonly eventId: ColumnBuilder<string | null, "text", boolean>;
33
33
  /** Attempt counter (1-indexed). */
34
34
  readonly attempt: ColumnBuilder<number, "integer", boolean>;
35
- readonly status: ColumnBuilder<"failed" | "succeeded" | "inFlight" | "pending" | "retryScheduled", "text", boolean>;
35
+ readonly status: ColumnBuilder<"failed" | "pending" | "succeeded" | "inFlight" | "retryScheduled", "text", boolean>;
36
36
  /** Payload as sent over the wire. Stored verbatim — re-rendering
37
37
  * from a referenced event row would lose the snapshot if the
38
38
  * source event was deleted. */
@@ -384,7 +384,7 @@ export declare const webhookTables: () => readonly [ Table<"_voltro_webhook_targ
384
384
  readonly eventId: ColumnBuilder<string | null, "text", boolean>;
385
385
  /** Attempt counter (1-indexed). */
386
386
  readonly attempt: ColumnBuilder<number, "integer", boolean>;
387
- readonly status: ColumnBuilder<"failed" | "succeeded" | "inFlight" | "pending" | "retryScheduled", "text", boolean>;
387
+ readonly status: ColumnBuilder<"failed" | "pending" | "succeeded" | "inFlight" | "retryScheduled", "text", boolean>;
388
388
  /** Payload as sent over the wire. Stored verbatim — re-rendering
389
389
  * from a referenced event row would lose the snapshot if the
390
390
  * source event was deleted. */
package/dist/mixin.js CHANGED
@@ -60,10 +60,12 @@ var u = o("_voltro_webhook_targets", {
60
60
  createdAt: c(),
61
61
  updatedAt: c()
62
62
  }), m = () => (i({
63
+ source: "plugin",
63
64
  table: "_voltro_webhook_deliveries",
64
65
  timeColumn: "createdAt",
65
66
  ttlMs: a(process.env.VOLTRO_WEBHOOK_DELIVERIES_TTL_HOURS, 2160)
66
67
  }), i({
68
+ source: "plugin",
67
69
  table: "_voltro_webhook_rate_windows",
68
70
  timeColumn: "createdAt",
69
71
  ttlMs: 24 * 36e5
@@ -5,7 +5,10 @@ declare interface CustomSignatureScheme {
5
5
  readonly _tag: 'custom';
6
6
  readonly header: string;
7
7
  readonly sign: (rawBody: Uint8Array, secret: string) => string;
8
- readonly verify: (rawBody: Uint8Array, secret: string, signatureHeader: string) => boolean;
8
+ /** Verify against the request's FULL header map (lowercased keys) a scheme
9
+ * whose timestamp lives in a second header can read it here instead of
10
+ * relying on a caller to splice the two values together. */
11
+ readonly verify: (rawBody: Uint8Array, secret: string, headers: Readonly<Record<string, string>>) => boolean;
9
12
  }
10
13
 
11
14
  export declare const genericProvider: (options?: {
@@ -74,11 +77,24 @@ declare interface IncomingWebhookDescriptor<Body> {
74
77
  * integrations (`/integrations/stripe/v1`). */
75
78
  readonly path?: `/${string}`;
76
79
  /** Signature scheme used to authenticate inbound requests. Reject
77
- * on mismatch with a 401. `undefined` (the default) means NO
78
- * verification — only acceptable when behind a separate trust
79
- * boundary (gateway + IP allow-list). The dashboard surfaces a
80
- * warning for unsigned incoming webhooks. */
80
+ * on mismatch with a 401. Usually filled in by `provider`. */
81
81
  readonly signature?: SignatureScheme;
82
+ /**
83
+ * How this endpoint authenticates its caller. An incoming webhook is a
84
+ * PUBLIC POST that runs your application code, so the framework will not
85
+ * mount one that has made no decision here: `mountIncomingWebhook` throws
86
+ * at boot when there is no effective `signature` (from this descriptor or
87
+ * from `provider`) AND no explicit value below.
88
+ *
89
+ * - omitted → derived. `signature` / `provider` present → `'signature'`;
90
+ * nothing present → boot refuses.
91
+ * - `'provider'` → the handler verifies with the provider's own SDK
92
+ * (Stripe's `constructEvent`, etc.). The framework's generic HMAC layer
93
+ * is not the authority and does not require a framework-side secret.
94
+ * - `'none'` → deliberately unverified, because a gateway + IP allow-list
95
+ * owns the trust boundary. Logged as a warning at every boot, on purpose.
96
+ */
97
+ readonly verification?: 'signature' | 'provider' | 'none';
82
98
  /** Idempotency key extraction. Default `'Idempotency-Key'` header.
83
99
  * Provider-templates override this to match the provider's wire
84
100
  * format. */
@@ -130,10 +146,27 @@ declare interface IncomingWorkflowFacade {
130
146
  }>;
131
147
  }
132
148
 
133
- declare type SignatureScheme = HmacSignatureScheme | CustomSignatureScheme;
149
+ declare type SignatureScheme = HmacSignatureScheme | CustomSignatureScheme | StandardWebhooksSignatureScheme;
134
150
 
135
151
  export declare const slackProvider: () => WebhookProviderDescriptor;
136
152
 
153
+ /**
154
+ * The Standard Webhooks (v1.0.0) scheme — three `webhook-*` headers, a
155
+ * `msg_id.timestamp.payload` signed content, base64 `v1,` signatures, a
156
+ * `whsec_`-prefixed base64 key. Implemented in `./standardWebhooks`, which
157
+ * carries the spec quotations and the interop vector it is verified against.
158
+ */
159
+ declare interface StandardWebhooksSignatureScheme {
160
+ readonly _tag: 'standardWebhooks';
161
+ /** Replay tolerance. The spec REQUIRES a tolerance and names no number; 300s
162
+ * is ours. */
163
+ readonly toleranceSeconds?: number;
164
+ /** A second key accepted on verify AND signed alongside on send — the spec's
165
+ * zero-downtime rotation, which is what the space-delimited signature list
166
+ * exists for. */
167
+ readonly previousSecret?: string;
168
+ }
169
+
137
170
  export declare const stripeProvider: (options?: {
138
171
  /** Override the replay window. Stripe's default is 5 min;
139
172
  * callers running tests sometimes want a wider window. */
@@ -1,12 +1,12 @@
1
- import { a as e, c as t, n, t as r } from "../signing-DUG4JWwk.js";
1
+ import { T as e, n as t, r as n, s as r } from "../signing-DeHObNv6.js";
2
2
  import { createHmac as i, timingSafeEqual as a } from "node:crypto";
3
3
  //#region src/providers/stripe.ts
4
- var o = (n) => t({
4
+ var o = (t) => e({
5
5
  id: "stripe",
6
6
  name: "Stripe",
7
7
  signature: {
8
- ...e(),
9
- ...n?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: n.replayWindowSeconds }
8
+ ...r(),
9
+ ...t?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: t.replayWindowSeconds }
10
10
  },
11
11
  idempotency: {
12
12
  from: (e, t) => {
@@ -25,7 +25,7 @@ var o = (n) => t({
25
25
  return typeof t == "string" ? t : void 0;
26
26
  }
27
27
  }
28
- }), s = () => t({
28
+ }), s = () => e({
29
29
  id: "github",
30
30
  name: "GitHub",
31
31
  signature: n(),
@@ -43,16 +43,14 @@ var o = (n) => t({
43
43
  return `v0=${i("sha256", t).update(n).digest("hex")}`;
44
44
  },
45
45
  verify: (e, t, n) => {
46
- let r = n.indexOf("|");
47
- if (r === -1) return !1;
48
- let o = n.slice(0, r), s = n.slice(r + 1);
49
- if (!/^\d+$/.test(o)) return !1;
50
- let c = Math.floor(Date.now() / 1e3);
51
- if (Math.abs(c - Number(o)) > 300) return !1;
52
- let l = `v0:${o}:${new TextDecoder().decode(e)}`, u = `v0=${i("sha256", t).update(l).digest("hex")}`, d = Buffer.from(u, "utf8"), f = Buffer.from(s, "utf8");
53
- return d.length === f.length ? a(d, f) : (a(d, Buffer.alloc(d.length)), !1);
46
+ let r = n["x-slack-signature"], o = n["x-slack-request-timestamp"];
47
+ if (!r || !o || !/^\d+$/.test(o)) return !1;
48
+ let s = Math.floor(Date.now() / 1e3);
49
+ if (Math.abs(s - Number(o)) > 300) return !1;
50
+ let c = `v0:${o}:${new TextDecoder().decode(e)}`, l = `v0=${i("sha256", t).update(c).digest("hex")}`, u = Buffer.from(l, "utf8"), d = Buffer.from(r, "utf8");
51
+ return u.length === d.length ? a(u, d) : (a(u, Buffer.alloc(u.length)), !1);
54
52
  }
55
- }), l = () => t({
53
+ }), l = () => e({
56
54
  id: "slack",
57
55
  name: "Slack",
58
56
  signature: c(),
@@ -70,13 +68,13 @@ var o = (n) => t({
70
68
  }
71
69
  }
72
70
  }
73
- }), u = (e) => t({
71
+ }), u = (n) => e({
74
72
  id: "generic",
75
73
  name: "Generic",
76
74
  signature: {
77
- ...r(),
78
- ...e?.signatureHeader === void 0 ? {} : { header: e.signatureHeader },
79
- ...e?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: e.replayWindowSeconds }
75
+ ...t(),
76
+ ...n?.signatureHeader === void 0 ? {} : { header: n.signatureHeader },
77
+ ...n?.replayWindowSeconds === void 0 ? {} : { replayWindowSeconds: n.replayWindowSeconds }
80
78
  },
81
79
  idempotency: {
82
80
  from: "idempotency-key",
@@ -0,0 +1,234 @@
1
+ import { createHmac as e, randomBytes as t, timingSafeEqual as n } from "node:crypto";
2
+ //#region src/dsl.ts
3
+ var r = (e) => ({
4
+ _tag: "incomingWebhook",
5
+ ...e
6
+ }), i = (e) => ({
7
+ _tag: "webhookProvider",
8
+ ...e
9
+ }), a = (e) => {
10
+ if (typeof e != "object" || !e) return !1;
11
+ let t = e._tag;
12
+ return t === "outgoingEvent" || t === "incomingWebhook" || t === "webhookProvider";
13
+ }, o = "webhook-id", s = "webhook-timestamp", c = "webhook-signature", l = "v1", u = "whsec_", d = 24, f = 64, p = 300, m = () => `${u}${t(32).toString("base64")}`, h = (e) => {
14
+ if (!e.startsWith("whsec_")) throw Error(`standard-webhooks: the signing secret must be the spec's serialization — "${u}" + base64(24..64 random bytes). Mint one with generateStandardWebhooksSecret(). (A hex secret is also valid base64, so accepting it would sign with the wrong key bytes and every conformant consumer would reject the delivery.)`);
15
+ let t = Buffer.from(e.slice(6), "base64");
16
+ if (t.length < 24 || t.length > 64) throw Error(`standard-webhooks: the decoded key is ${t.length} bytes; the spec requires 24..64.`);
17
+ return t;
18
+ }, g = (e) => e.startsWith(u), _ = (e, t) => {
19
+ if (t.includes(".")) throw Error(`standard-webhooks: the ${e} may not contain "." — it is the field delimiter of the signed content (id.timestamp.payload), and a "." in it makes one signed string readable two ways. Got ${JSON.stringify(t)}.`);
20
+ if (t.length === 0) throw Error(`standard-webhooks: the ${e} may not be empty`);
21
+ }, v = (e, t, n) => {
22
+ _("webhook-id", e);
23
+ let r = String(t);
24
+ return _("webhook-timestamp", r), Buffer.concat([Buffer.from(`${e}.${r}.`, "utf8"), Buffer.from(n)]);
25
+ }, y = (t, n, r, i) => {
26
+ let a = e("sha256", h(t));
27
+ return a.update(v(n, r, i)), `v1,${a.digest("base64")}`;
28
+ }, b = (e) => {
29
+ if (e.secrets.length === 0) throw Error("standard-webhooks: at least one signing secret is required");
30
+ let t = e.secrets.map((t) => y(t, e.messageId, e.timestampSeconds, e.rawBody));
31
+ return {
32
+ [o]: e.messageId,
33
+ [s]: String(e.timestampSeconds),
34
+ [c]: t.join(" ")
35
+ };
36
+ }, x = (e, t) => {
37
+ let r = Buffer.from(e, "utf8"), i = Buffer.from(t, "utf8");
38
+ return r.length === i.length ? n(r, i) : (n(r, Buffer.alloc(r.length)), !1);
39
+ }, S = (e) => {
40
+ let t = e.headers, n = t[o], r = t[s], i = t[c];
41
+ if (!n) return {
42
+ ok: !1,
43
+ reason: `missing ${o}`
44
+ };
45
+ if (!r) return {
46
+ ok: !1,
47
+ reason: `missing ${s}`
48
+ };
49
+ if (!i) return {
50
+ ok: !1,
51
+ reason: `missing ${c}`
52
+ };
53
+ if (n.includes(".")) return {
54
+ ok: !1,
55
+ reason: "webhook-id contains the \".\" field delimiter"
56
+ };
57
+ let a = Number(r);
58
+ if (!Number.isInteger(a)) return {
59
+ ok: !1,
60
+ reason: `${s} is not an integer unix timestamp`
61
+ };
62
+ let l = e.toleranceSeconds ?? 300, u = e.nowSeconds ?? Math.floor(Date.now() / 1e3), d = Math.abs(u - a);
63
+ if (d > l) return {
64
+ ok: !1,
65
+ reason: `timestamp outside the ${l}s tolerance (off by ${d}s)`
66
+ };
67
+ let f = i.split(" ").map((e) => e.trim()).filter((e) => e.length > 0);
68
+ if (f.length === 0) return {
69
+ ok: !1,
70
+ reason: "no signature in webhook-signature"
71
+ };
72
+ if (f.every((e) => e.startsWith("v1a,"))) return {
73
+ ok: !1,
74
+ reason: "only asymmetric (v1a / ed25519) signatures were offered; this verifier implements the symmetric v1 scheme only"
75
+ };
76
+ for (let t of e.secrets) {
77
+ let r;
78
+ try {
79
+ r = y(t, n, a, e.rawBody);
80
+ } catch (e) {
81
+ return {
82
+ ok: !1,
83
+ reason: e.message
84
+ };
85
+ }
86
+ for (let e of f) if (e.startsWith("v1,") && x(r, e)) return {
87
+ ok: !0,
88
+ messageId: n,
89
+ timestampSeconds: a
90
+ };
91
+ }
92
+ return {
93
+ ok: !1,
94
+ reason: "signature mismatch"
95
+ };
96
+ }, C = (e) => e === "hmacSha256" ? "sha256" : "sha1", w = (t, n, r, i = "hex") => {
97
+ let a = e(C(t), n);
98
+ return a.update(r), a.digest(i);
99
+ };
100
+ function T(e, t) {
101
+ if (e._tag !== "hmac" || typeof e.header != "string") throw Error(`webhook signing: cannot ${t} with this scheme — expected one of "standardWebhooks" | "hmac" | "custom" with a header, got ${JSON.stringify(e)}`);
102
+ }
103
+ var E = (e, t) => {
104
+ let n = t.timestampSeconds ?? Math.floor(Date.now() / 1e3);
105
+ if (e._tag === "standardWebhooks") return {
106
+ headers: b({
107
+ secrets: e.previousSecret === void 0 ? [t.secret] : [t.secret, e.previousSecret],
108
+ messageId: t.messageId,
109
+ timestampSeconds: n,
110
+ rawBody: t.rawBody
111
+ }),
112
+ timestamp: n
113
+ };
114
+ if (e._tag === "custom") return { headers: { [e.header.toLowerCase()]: e.sign(t.rawBody, t.secret) } };
115
+ T(e, "sign");
116
+ let r = e.encoding ?? "hex", i = e.versionPrefix ?? "";
117
+ if (e.includeTimestamp) {
118
+ let a = Buffer.concat([Buffer.from(`${n}.`, "utf8"), Buffer.from(t.rawBody)]), o = w(e.algorithm, t.secret, a, r);
119
+ return {
120
+ headers: { [e.header.toLowerCase()]: `t=${n},${i}${o}` },
121
+ timestamp: n
122
+ };
123
+ }
124
+ let a = w(e.algorithm, t.secret, t.rawBody, r);
125
+ return { headers: { [e.header.toLowerCase()]: `${i}${a}` } };
126
+ }, D = (e, t) => {
127
+ if (e._tag === "standardWebhooks") {
128
+ let n = S({
129
+ secrets: e.previousSecret === void 0 ? [t.secret] : [t.secret, e.previousSecret],
130
+ rawBody: t.rawBody,
131
+ headers: t.headers,
132
+ toleranceSeconds: e.toleranceSeconds ?? 300,
133
+ ...t.nowSeconds === void 0 ? {} : { nowSeconds: t.nowSeconds }
134
+ });
135
+ return n.ok ? { ok: !0 } : {
136
+ ok: !1,
137
+ reason: n.reason
138
+ };
139
+ }
140
+ if (e._tag === "custom") return e.verify(t.rawBody, t.secret, t.headers) ? { ok: !0 } : {
141
+ ok: !1,
142
+ reason: "custom verifier rejected"
143
+ };
144
+ T(e, "verify");
145
+ let n = t.headers[e.header.toLowerCase()];
146
+ if (!n) return {
147
+ ok: !1,
148
+ reason: "missing signature header"
149
+ };
150
+ let r = e.encoding ?? "hex", i = e.versionPrefix ?? "", a = (a) => {
151
+ if (e.includeTimestamp) {
152
+ let o = n.split(",").map((e) => e.trim()), s = o.find((e) => e.startsWith("t="));
153
+ if (!s) return {
154
+ ok: !1,
155
+ reason: "no timestamp in signature header"
156
+ };
157
+ let c = Number(s.slice(2));
158
+ if (!Number.isFinite(c)) return {
159
+ ok: !1,
160
+ reason: "invalid timestamp"
161
+ };
162
+ let l = e.replayWindowSeconds ?? 300, u = t.nowSeconds ?? Math.floor(Date.now() / 1e3);
163
+ if (Math.abs(u - c) > l) return {
164
+ ok: !1,
165
+ reason: `replay window exceeded (${Math.abs(u - c)}s > ${l}s)`
166
+ };
167
+ let d = i.length > 0 ? o.find((e) => e.startsWith(i))?.slice(i.length) : o.find((e) => !e.startsWith("t="));
168
+ if (!d) return {
169
+ ok: !1,
170
+ reason: "no signature in header"
171
+ };
172
+ let f = Buffer.concat([Buffer.from(`${c}.`, "utf8"), Buffer.from(t.rawBody)]);
173
+ return O(w(e.algorithm, a, f, r), d) ? { ok: !0 } : {
174
+ ok: !1,
175
+ reason: "signature mismatch"
176
+ };
177
+ }
178
+ let o = i.length > 0 && n.startsWith(i) ? n.slice(i.length) : n;
179
+ return O(w(e.algorithm, a, t.rawBody, r), o) ? { ok: !0 } : {
180
+ ok: !1,
181
+ reason: "signature mismatch"
182
+ };
183
+ }, o = a(t.secret);
184
+ if (o.ok) return o;
185
+ if (e.previousSecret !== void 0) {
186
+ let t = a(e.previousSecret);
187
+ if (t.ok) return t;
188
+ }
189
+ return o;
190
+ }, O = (e, t) => {
191
+ let r = Buffer.from(e, "utf8"), i = Buffer.from(t, "utf8");
192
+ return r.length === i.length ? n(r, i) : (n(r, Buffer.alloc(r.length)), !1);
193
+ }, k = (e = "Stripe-Signature") => ({
194
+ _tag: "hmac",
195
+ algorithm: "hmacSha256",
196
+ header: e,
197
+ includeTimestamp: !0,
198
+ replayWindowSeconds: 300,
199
+ encoding: "hex",
200
+ versionPrefix: "v1="
201
+ }), A = () => ({
202
+ _tag: "hmac",
203
+ algorithm: "hmacSha256",
204
+ header: "X-Hub-Signature-256",
205
+ includeTimestamp: !1,
206
+ encoding: "hex",
207
+ versionPrefix: "sha256="
208
+ }), j = (e = {}) => ({
209
+ _tag: "custom",
210
+ header: "X-Slack-Signature",
211
+ sign: (e, t) => `v0=${w("hmacSha256", t, `v0:${Math.floor(Date.now() / 1e3)}:${Buffer.from(e).toString("utf8")}`)}`,
212
+ verify: (t, n, r) => {
213
+ let i = r["x-slack-signature"], a = r["x-slack-request-timestamp"];
214
+ if (!i || !a) return !1;
215
+ let o = Number(a);
216
+ if (!Number.isFinite(o)) return !1;
217
+ let s = e.replayWindowSeconds ?? 300;
218
+ return Math.abs(Math.floor(Date.now() / 1e3) - o) > s ? !1 : O(`v0=${w("hmacSha256", n, `v0:${o}:${Buffer.from(t).toString("utf8")}`)}`, i);
219
+ }
220
+ }), M = (e = {}) => ({
221
+ _tag: "standardWebhooks",
222
+ ...e.toleranceSeconds === void 0 ? {} : { toleranceSeconds: e.toleranceSeconds },
223
+ ...e.previousSecret === void 0 ? {} : { previousSecret: e.previousSecret }
224
+ }), N = () => M(), P = () => ({
225
+ _tag: "hmac",
226
+ algorithm: "hmacSha256",
227
+ header: "X-Webhook-Signature",
228
+ includeTimestamp: !0,
229
+ replayWindowSeconds: 300,
230
+ encoding: "hex",
231
+ versionPrefix: "v1="
232
+ });
233
+ //#endregion
234
+ export { S as C, a as E, v as S, i as T, m as _, j as a, h as b, D as c, f as d, d as f, l as g, s as h, E as i, p as l, c as m, P as n, M as o, u as p, A as r, k as s, N as t, o as u, g as v, r as w, y as x, b as y };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-webhooks",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Webhooks plugin — first-class outgoing (multi-target, durable-workflow delivery, HMAC signing, retry policies, rate limits) + incoming (signature verification, idempotency, provider templates for Stripe / GitHub / Slack / generic).",
5
5
  "keywords": [
6
6
  "voltro",
@@ -37,7 +37,8 @@
37
37
  "types": "./dist/errors.d.ts",
38
38
  "import": "./dist/errors.js",
39
39
  "default": "./dist/errors.js"
40
- }
40
+ },
41
+ "./package.json": "./package.json"
41
42
  },
42
43
  "main": "./dist/index.js",
43
44
  "module": "./dist/index.js",
@@ -48,12 +49,12 @@
48
49
  },
49
50
  "dependencies": {
50
51
  "@effect/workflow": "^0.19.0",
51
- "@voltro/database": "0.32.0",
52
- "@voltro/integration-http": "0.32.0",
53
- "@voltro/logger": "0.32.0",
54
- "@voltro/plugin-multitenancy": "0.32.0",
55
- "@voltro/protocol": "0.32.0",
56
- "@voltro/runtime": "0.32.0"
52
+ "@voltro/database": "0.34.0",
53
+ "@voltro/integration-http": "0.34.0",
54
+ "@voltro/logger": "0.34.0",
55
+ "@voltro/plugin-multitenancy": "0.34.0",
56
+ "@voltro/protocol": "0.34.0",
57
+ "@voltro/runtime": "0.34.0"
57
58
  },
58
59
  "peerDependencies": {
59
60
  "effect": "^3.22.0"
@@ -1,117 +0,0 @@
1
- import { createHmac as e, timingSafeEqual as t } from "node:crypto";
2
- //#region src/dsl.ts
3
- var n = (e) => ({
4
- _tag: "incomingWebhook",
5
- ...e
6
- }), r = (e) => ({
7
- _tag: "webhookProvider",
8
- ...e
9
- }), i = (e) => {
10
- if (typeof e != "object" || !e) return !1;
11
- let t = e._tag;
12
- return t === "outgoingEvent" || t === "incomingWebhook" || t === "webhookProvider";
13
- }, a = (e) => e === "hmacSha256" ? "sha256" : "sha1", o = (t, n, r, i = "hex") => {
14
- let o = e(a(t), n);
15
- return o.update(r), o.digest(i);
16
- }, s = (e, t, n) => {
17
- if (e._tag === "custom") return {
18
- headerName: e.header,
19
- headerValue: e.sign(t, n)
20
- };
21
- let r = e.encoding ?? "hex", i = e.versionPrefix ?? "";
22
- if (e.includeTimestamp) {
23
- let a = Math.floor(Date.now() / 1e3), s = Buffer.concat([Buffer.from(`${a}.`, "utf8"), Buffer.from(t)]), c = o(e.algorithm, n, s, r);
24
- return {
25
- headerName: e.header,
26
- headerValue: `t=${a},${i}${c}`,
27
- timestamp: a
28
- };
29
- }
30
- let a = o(e.algorithm, n, t, r);
31
- return {
32
- headerName: e.header,
33
- headerValue: `${i}${a}`
34
- };
35
- }, c = (e, t, n, r) => {
36
- if (!r) return {
37
- ok: !1,
38
- reason: "missing signature header"
39
- };
40
- if (e._tag === "custom") return e.verify(t, n, r) ? { ok: !0 } : {
41
- ok: !1,
42
- reason: "custom verifier rejected"
43
- };
44
- let i = e.encoding ?? "hex", a = e.versionPrefix ?? "", s = (n) => {
45
- if (e.includeTimestamp) {
46
- let s = r.split(",").map((e) => e.trim()), c = s.find((e) => e.startsWith("t="));
47
- if (!c) return {
48
- ok: !1,
49
- reason: "no timestamp in signature header"
50
- };
51
- let u = Number(c.slice(2));
52
- if (!Number.isFinite(u)) return {
53
- ok: !1,
54
- reason: "invalid timestamp"
55
- };
56
- let d = e.replayWindowSeconds ?? 300, f = Math.floor(Date.now() / 1e3);
57
- if (Math.abs(f - u) > d) return {
58
- ok: !1,
59
- reason: `replay window exceeded (${Math.abs(f - u)}s > ${d}s)`
60
- };
61
- let p = a.length > 0 ? s.find((e) => e.startsWith(a))?.slice(a.length) : s.find((e) => !e.startsWith("t="));
62
- if (!p) return {
63
- ok: !1,
64
- reason: "no signature in header"
65
- };
66
- let m = Buffer.concat([Buffer.from(`${u}.`, "utf8"), Buffer.from(t)]);
67
- return l(o(e.algorithm, n, m, i), p) ? { ok: !0 } : {
68
- ok: !1,
69
- reason: "signature mismatch"
70
- };
71
- }
72
- let s = a.length > 0 && r.startsWith(a) ? r.slice(a.length) : r;
73
- return l(o(e.algorithm, n, t, i), s) ? { ok: !0 } : {
74
- ok: !1,
75
- reason: "signature mismatch"
76
- };
77
- }, c = s(n);
78
- if (c.ok) return c;
79
- if (e.previousSecret !== void 0) {
80
- let t = s(e.previousSecret);
81
- if (t.ok) return t;
82
- }
83
- return c;
84
- }, l = (e, n) => {
85
- let r = Buffer.from(e, "utf8"), i = Buffer.from(n, "utf8");
86
- return r.length === i.length ? t(r, i) : (t(r, Buffer.alloc(r.length)), !1);
87
- }, u = (e = "Stripe-Signature") => ({
88
- _tag: "hmac",
89
- algorithm: "hmacSha256",
90
- header: e,
91
- includeTimestamp: !0,
92
- replayWindowSeconds: 300,
93
- encoding: "hex",
94
- versionPrefix: "v1="
95
- }), d = () => ({
96
- _tag: "hmac",
97
- algorithm: "hmacSha256",
98
- header: "X-Hub-Signature-256",
99
- includeTimestamp: !1,
100
- encoding: "hex",
101
- versionPrefix: "sha256="
102
- }), f = () => ({
103
- _tag: "custom",
104
- header: "X-Slack-Signature",
105
- sign: (e, t) => `v0=${o("hmacSha256", t, `v0:${Math.floor(Date.now() / 1e3)}:${Buffer.from(e).toString("utf8")}`)}`,
106
- verify: (e, t, n) => !1
107
- }), p = () => ({
108
- _tag: "hmac",
109
- algorithm: "hmacSha256",
110
- header: "X-Webhook-Signature",
111
- includeTimestamp: !0,
112
- replayWindowSeconds: 300,
113
- encoding: "hex",
114
- versionPrefix: "v1="
115
- });
116
- //#endregion
117
- export { u as a, r as c, f as i, i as l, d as n, c as o, s as r, n as s, p as t };