@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/CHANGELOG.md +2006 -0
- package/THIRD-PARTY-NOTICES.md +1 -29
- package/dist/index.d.ts +355 -37
- package/dist/index.js +398 -344
- package/dist/mixin.d.ts +2 -2
- package/dist/mixin.js +2 -0
- package/dist/providers/index.d.ts +39 -6
- package/dist/providers/index.js +16 -18
- package/dist/signing-DeHObNv6.js +234 -0
- package/package.json +9 -8
- package/dist/signing-DUG4JWwk.js +0 -117
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" | "
|
|
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" | "
|
|
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
|
-
|
|
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.
|
|
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. */
|
package/dist/providers/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import {
|
|
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 = (
|
|
4
|
+
var o = (t) => e({
|
|
5
5
|
id: "stripe",
|
|
6
6
|
name: "Stripe",
|
|
7
7
|
signature: {
|
|
8
|
-
...
|
|
9
|
-
...
|
|
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 = () =>
|
|
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
|
|
47
|
-
if (r
|
|
48
|
-
let
|
|
49
|
-
if (
|
|
50
|
-
let c =
|
|
51
|
-
|
|
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 = () =>
|
|
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 = (
|
|
71
|
+
}), u = (n) => e({
|
|
74
72
|
id: "generic",
|
|
75
73
|
name: "Generic",
|
|
76
74
|
signature: {
|
|
77
|
-
...
|
|
78
|
-
...
|
|
79
|
-
...
|
|
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.
|
|
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.
|
|
52
|
-
"@voltro/integration-http": "0.
|
|
53
|
-
"@voltro/logger": "0.
|
|
54
|
-
"@voltro/plugin-multitenancy": "0.
|
|
55
|
-
"@voltro/protocol": "0.
|
|
56
|
-
"@voltro/runtime": "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"
|
package/dist/signing-DUG4JWwk.js
DELETED
|
@@ -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 };
|