@blokjs/trigger-webhook 0.2.1 → 0.6.1
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/WebhookTrigger.d.ts +100 -117
- package/dist/WebhookTrigger.js +315 -316
- package/dist/index.d.ts +36 -65
- package/dist/index.js +34 -71
- package/dist/verifiers.d.ts +80 -0
- package/dist/verifiers.js +294 -0
- package/package.json +6 -4
- package/src/WebhookTrigger.integration.test.ts +162 -0
- package/src/WebhookTrigger.test.ts +232 -143
- package/src/WebhookTrigger.ts +386 -407
- package/src/index.ts +41 -70
- package/src/verifiers.test.ts +316 -0
- package/src/verifiers.ts +357 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,70 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
2
|
+
* @blokjs/trigger-webhook
|
|
3
|
+
*
|
|
4
|
+
* Inbound webhook trigger for Blok workflows. Mounts verified POST
|
|
5
|
+
* routes on the shared Hono server alongside HTTP, WebSocket, and
|
|
6
|
+
* SSE routes — same port, same middleware chain, same Studio
|
|
7
|
+
* tracing. Verifies provider signatures (GitHub, Stripe, Slack,
|
|
8
|
+
* Shopify, Svix/Standard Webhooks) or a custom HMAC scheme, applies
|
|
9
|
+
* replay protection via the idempotency cache, and dispatches the
|
|
10
|
+
* workflow.
|
|
11
|
+
*
|
|
12
|
+
* v0.7+ usage (just add the trigger to your workflow):
|
|
13
|
+
*
|
|
14
|
+
* ```json
|
|
15
|
+
* {
|
|
16
|
+
* "name": "stripe-events",
|
|
17
|
+
* "trigger": {
|
|
18
|
+
* "webhook": {
|
|
19
|
+
* "provider": "stripe",
|
|
20
|
+
* "path": "/webhooks/stripe",
|
|
21
|
+
* "secretEnv": "STRIPE_WEBHOOK_SECRET",
|
|
22
|
+
* "namespace": "stripe",
|
|
23
|
+
* "idempotencyKey": "js/ctx.request.body.id"
|
|
24
|
+
* }
|
|
25
|
+
* },
|
|
26
|
+
* "steps": [
|
|
27
|
+
* { "id": "dispatch", "subworkflow": "js/ctx.request.body.type", "inputs": { "stripeEvent": "js/ctx.request.body" } }
|
|
28
|
+
* ]
|
|
26
29
|
* }
|
|
27
|
-
*
|
|
28
|
-
* const trigger = new MyWebhookTrigger();
|
|
29
|
-
* await trigger.listen();
|
|
30
|
-
*
|
|
31
|
-
* // In your HTTP endpoint handler:
|
|
32
|
-
* app.post("/webhooks/:source", async (req, res) => {
|
|
33
|
-
* const rawBody = JSON.stringify(req.body);
|
|
34
|
-
* const result = await trigger.handleWebhook(
|
|
35
|
-
* req.params.source,
|
|
36
|
-
* rawBody,
|
|
37
|
-
* req.headers as Record<string, string>
|
|
38
|
-
* );
|
|
39
|
-
* res.status(200).json({ received: true });
|
|
40
|
-
* });
|
|
41
|
-
* ```
|
|
42
|
-
*
|
|
43
|
-
* Workflow Definition:
|
|
44
|
-
* ```typescript
|
|
45
|
-
* Workflow({ name: "github-push", version: "1.0.0" })
|
|
46
|
-
* .addTrigger("webhook", {
|
|
47
|
-
* source: "github",
|
|
48
|
-
* events: ["push", "pull_request.*"],
|
|
49
|
-
* secret: process.env.GITHUB_WEBHOOK_SECRET,
|
|
50
|
-
* })
|
|
51
|
-
* .addStep({ ... });
|
|
52
30
|
* ```
|
|
53
31
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* import { WebhookTrigger } from "@blok/trigger-webhook";
|
|
57
|
-
*
|
|
58
|
-
* WebhookTrigger.registerSourceHandler("my-service", {
|
|
59
|
-
* getEventType: (headers, body) => body.event_type,
|
|
60
|
-
* getSignature: (headers) => headers["x-my-signature"],
|
|
61
|
-
* verifySignature: (rawBody, signature, secret) => {
|
|
62
|
-
* // Your verification logic
|
|
63
|
-
* return { valid: true };
|
|
64
|
-
* },
|
|
65
|
-
* getEventId: (headers, body) => body.id,
|
|
66
|
-
* });
|
|
67
|
-
* ```
|
|
32
|
+
* See [additional-triggers-plan.mdx](../../../docs/c/devtools/additional-triggers-plan.mdx#webhook-trigger)
|
|
33
|
+
* for the full design.
|
|
68
34
|
*/
|
|
69
|
-
|
|
70
|
-
export
|
|
35
|
+
import WebhookTrigger, { _getActiveWebhookTrigger, _setActiveWebhookTrigger } from "./WebhookTrigger";
|
|
36
|
+
export default WebhookTrigger;
|
|
37
|
+
export { WebhookTrigger, _getActiveWebhookTrigger, _setActiveWebhookTrigger };
|
|
38
|
+
export type { WebhookTriggerConfig } from "./WebhookTrigger";
|
|
39
|
+
export type { WebhookTriggerOpts } from "@blokjs/helper";
|
|
40
|
+
export { BUILTIN_VERIFIERS, buildCustomVerifier, githubVerifier, shopifyVerifier, slackVerifier, stripeVerifier, svixVerifier, } from "./verifiers";
|
|
41
|
+
export type { CustomSignatureConfig, VerifyError, VerifyInput, VerifyOk, VerifyResult, Verifier } from "./verifiers";
|
package/dist/index.js
CHANGED
|
@@ -1,76 +1,39 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
|
-
* @
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
2
|
+
* @blokjs/trigger-webhook
|
|
3
|
+
*
|
|
4
|
+
* Inbound webhook trigger for Blok workflows. Mounts verified POST
|
|
5
|
+
* routes on the shared Hono server alongside HTTP, WebSocket, and
|
|
6
|
+
* SSE routes — same port, same middleware chain, same Studio
|
|
7
|
+
* tracing. Verifies provider signatures (GitHub, Stripe, Slack,
|
|
8
|
+
* Shopify, Svix/Standard Webhooks) or a custom HMAC scheme, applies
|
|
9
|
+
* replay protection via the idempotency cache, and dispatches the
|
|
10
|
+
* workflow.
|
|
11
|
+
*
|
|
12
|
+
* v0.7+ usage (just add the trigger to your workflow):
|
|
13
|
+
*
|
|
14
|
+
* ```json
|
|
15
|
+
* {
|
|
16
|
+
* "name": "stripe-events",
|
|
17
|
+
* "trigger": {
|
|
18
|
+
* "webhook": {
|
|
19
|
+
* "provider": "stripe",
|
|
20
|
+
* "path": "/webhooks/stripe",
|
|
21
|
+
* "secretEnv": "STRIPE_WEBHOOK_SECRET",
|
|
22
|
+
* "namespace": "stripe",
|
|
23
|
+
* "idempotencyKey": "js/ctx.request.body.id"
|
|
24
|
+
* }
|
|
25
|
+
* },
|
|
26
|
+
* "steps": [
|
|
27
|
+
* { "id": "dispatch", "subworkflow": "js/ctx.request.body.type", "inputs": { "stripeEvent": "js/ctx.request.body" } }
|
|
28
|
+
* ]
|
|
27
29
|
* }
|
|
28
|
-
*
|
|
29
|
-
* const trigger = new MyWebhookTrigger();
|
|
30
|
-
* await trigger.listen();
|
|
31
|
-
*
|
|
32
|
-
* // In your HTTP endpoint handler:
|
|
33
|
-
* app.post("/webhooks/:source", async (req, res) => {
|
|
34
|
-
* const rawBody = JSON.stringify(req.body);
|
|
35
|
-
* const result = await trigger.handleWebhook(
|
|
36
|
-
* req.params.source,
|
|
37
|
-
* rawBody,
|
|
38
|
-
* req.headers as Record<string, string>
|
|
39
|
-
* );
|
|
40
|
-
* res.status(200).json({ received: true });
|
|
41
|
-
* });
|
|
42
|
-
* ```
|
|
43
|
-
*
|
|
44
|
-
* Workflow Definition:
|
|
45
|
-
* ```typescript
|
|
46
|
-
* Workflow({ name: "github-push", version: "1.0.0" })
|
|
47
|
-
* .addTrigger("webhook", {
|
|
48
|
-
* source: "github",
|
|
49
|
-
* events: ["push", "pull_request.*"],
|
|
50
|
-
* secret: process.env.GITHUB_WEBHOOK_SECRET,
|
|
51
|
-
* })
|
|
52
|
-
* .addStep({ ... });
|
|
53
30
|
* ```
|
|
54
31
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
* import { WebhookTrigger } from "@blok/trigger-webhook";
|
|
58
|
-
*
|
|
59
|
-
* WebhookTrigger.registerSourceHandler("my-service", {
|
|
60
|
-
* getEventType: (headers, body) => body.event_type,
|
|
61
|
-
* getSignature: (headers) => headers["x-my-signature"],
|
|
62
|
-
* verifySignature: (rawBody, signature, secret) => {
|
|
63
|
-
* // Your verification logic
|
|
64
|
-
* return { valid: true };
|
|
65
|
-
* },
|
|
66
|
-
* getEventId: (headers, body) => body.id,
|
|
67
|
-
* });
|
|
68
|
-
* ```
|
|
32
|
+
* See [additional-triggers-plan.mdx](../../../docs/c/devtools/additional-triggers-plan.mdx#webhook-trigger)
|
|
33
|
+
* for the full design.
|
|
69
34
|
*/
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
Object.defineProperty(exports, "sourceHandlers", { enumerable: true, get: function () { return WebhookTrigger_1.sourceHandlers; } });
|
|
76
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUVHOzs7QUFFSCxlQUFlO0FBQ2YsbURBTTBCO0FBTHpCLGdIQUFBLGNBQWMsT0FBQTtBQUNkLGdIQUFBLGNBQWMsT0FBQSIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGJsb2svdHJpZ2dlci13ZWJob29rXG4gKlxuICogV2ViaG9vayB0cmlnZ2VyIGZvciBCbG9rIHdvcmtmbG93cy5cbiAqIEhhbmRsZSB3ZWJob29rIGV2ZW50cyBmcm9tIGV4dGVybmFsIHNlcnZpY2VzLlxuICpcbiAqIFN1cHBvcnRlZCBTZXJ2aWNlczpcbiAqIC0gR2l0SHViIChwdXNoLCBwdWxsX3JlcXVlc3QsIGlzc3VlcywgcmVsZWFzZXMsIGV0Yy4pXG4gKiAtIFN0cmlwZSAocGF5bWVudF9pbnRlbnQsIGNoZWNrb3V0LnNlc3Npb24sIGN1c3RvbWVyLCBldGMuKVxuICogLSBTaG9waWZ5IChvcmRlcnMsIHByb2R1Y3RzLCBjdXN0b21lcnMsIGV0Yy4pXG4gKiAtIEN1c3RvbSB3ZWJob29rcyAoYW55IHNlcnZpY2Ugd2l0aCBzaWduYXR1cmUgdmVyaWZpY2F0aW9uKVxuICpcbiAqIEZlYXR1cmVzOlxuICogLSBTaWduYXR1cmUgdmVyaWZpY2F0aW9uIChITUFDLVNIQTI1NilcbiAqIC0gRXZlbnQgdHlwZSBmaWx0ZXJpbmdcbiAqIC0gU291cmNlLXNwZWNpZmljIGhhbmRsZXJzXG4gKiAtIEN1c3RvbSBzb3VyY2UgcmVnaXN0cmF0aW9uXG4gKlxuICogQGV4YW1wbGVcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCB7IFdlYmhvb2tUcmlnZ2VyIH0gZnJvbSBcIkBibG9rL3RyaWdnZXItd2ViaG9va1wiO1xuICpcbiAqIGNsYXNzIE15V2ViaG9va1RyaWdnZXIgZXh0ZW5kcyBXZWJob29rVHJpZ2dlciB7XG4gKiAgIHByb3RlY3RlZCBub2RlcyA9IG15Tm9kZXM7XG4gKiAgIHByb3RlY3RlZCB3b3JrZmxvd3MgPSBteVdvcmtmbG93cztcbiAqIH1cbiAqXG4gKiBjb25zdCB0cmlnZ2VyID0gbmV3IE15V2ViaG9va1RyaWdnZXIoKTtcbiAqIGF3YWl0IHRyaWdnZXIubGlzdGVuKCk7XG4gKlxuICogLy8gSW4geW91ciBIVFRQIGVuZHBvaW50IGhhbmRsZXI6XG4gKiBhcHAucG9zdChcIi93ZWJob29rcy86c291cmNlXCIsIGFzeW5jIChyZXEsIHJlcykgPT4ge1xuICogICBjb25zdCByYXdCb2R5ID0gSlNPTi5zdHJpbmdpZnkocmVxLmJvZHkpO1xuICogICBjb25zdCByZXN1bHQgPSBhd2FpdCB0cmlnZ2VyLmhhbmRsZVdlYmhvb2soXG4gKiAgICAgcmVxLnBhcmFtcy5zb3VyY2UsXG4gKiAgICAgcmF3Qm9keSxcbiAqICAgICByZXEuaGVhZGVycyBhcyBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+XG4gKiAgICk7XG4gKiAgIHJlcy5zdGF0dXMoMjAwKS5qc29uKHsgcmVjZWl2ZWQ6IHRydWUgfSk7XG4gKiB9KTtcbiAqIGBgYFxuICpcbiAqIFdvcmtmbG93IERlZmluaXRpb246XG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBXb3JrZmxvdyh7IG5hbWU6IFwiZ2l0aHViLXB1c2hcIiwgdmVyc2lvbjogXCIxLjAuMFwiIH0pXG4gKiAgIC5hZGRUcmlnZ2VyKFwid2ViaG9va1wiLCB7XG4gKiAgICAgc291cmNlOiBcImdpdGh1YlwiLFxuICogICAgIGV2ZW50czogW1wicHVzaFwiLCBcInB1bGxfcmVxdWVzdC4qXCJdLFxuICogICAgIHNlY3JldDogcHJvY2Vzcy5lbnYuR0lUSFVCX1dFQkhPT0tfU0VDUkVULFxuICogICB9KVxuICogICAuYWRkU3RlcCh7IC4uLiB9KTtcbiAqIGBgYFxuICpcbiAqIEN1c3RvbSBTb3VyY2UgSGFuZGxlcjpcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCB7IFdlYmhvb2tUcmlnZ2VyIH0gZnJvbSBcIkBibG9rL3RyaWdnZXItd2ViaG9va1wiO1xuICpcbiAqIFdlYmhvb2tUcmlnZ2VyLnJlZ2lzdGVyU291cmNlSGFuZGxlcihcIm15LXNlcnZpY2VcIiwge1xuICogICBnZXRFdmVudFR5cGU6IChoZWFkZXJzLCBib2R5KSA9PiBib2R5LmV2ZW50X3R5cGUsXG4gKiAgIGdldFNpZ25hdHVyZTogKGhlYWRlcnMpID0+IGhlYWRlcnNbXCJ4LW15LXNpZ25hdHVyZVwiXSxcbiAqICAgdmVyaWZ5U2lnbmF0dXJlOiAocmF3Qm9keSwgc2lnbmF0dXJlLCBzZWNyZXQpID0+IHtcbiAqICAgICAvLyBZb3VyIHZlcmlmaWNhdGlvbiBsb2dpY1xuICogICAgIHJldHVybiB7IHZhbGlkOiB0cnVlIH07XG4gKiAgIH0sXG4gKiAgIGdldEV2ZW50SWQ6IChoZWFkZXJzLCBib2R5KSA9PiBib2R5LmlkLFxuICogfSk7XG4gKiBgYGBcbiAqL1xuXG4vLyBDb3JlIGV4cG9ydHNcbmV4cG9ydCB7XG5cdFdlYmhvb2tUcmlnZ2VyLFxuXHRzb3VyY2VIYW5kbGVycyxcblx0dHlwZSBXZWJob29rRXZlbnQsXG5cdHR5cGUgVmVyaWZpY2F0aW9uUmVzdWx0LFxuXHR0eXBlIFdlYmhvb2tTb3VyY2VIYW5kbGVyLFxufSBmcm9tIFwiLi9XZWJob29rVHJpZ2dlclwiO1xuXG4vLyBSZS1leHBvcnQgdHlwZXMgZnJvbSBoZWxwZXIgZm9yIGNvbnZlbmllbmNlXG5leHBvcnQgdHlwZSB7IFdlYmhvb2tUcmlnZ2VyT3B0cyB9IGZyb20gXCJAYmxvay9oZWxwZXJcIjtcbiJdfQ==
|
|
35
|
+
import WebhookTrigger, { _getActiveWebhookTrigger, _setActiveWebhookTrigger } from "./WebhookTrigger";
|
|
36
|
+
export default WebhookTrigger;
|
|
37
|
+
export { WebhookTrigger, _getActiveWebhookTrigger, _setActiveWebhookTrigger };
|
|
38
|
+
export { BUILTIN_VERIFIERS, buildCustomVerifier, githubVerifier, shopifyVerifier, slackVerifier, stripeVerifier, svixVerifier, } from "./verifiers";
|
|
39
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQWlDRztBQUVILE9BQU8sY0FBYyxFQUFFLEVBQUUsd0JBQXdCLEVBQUUsd0JBQXdCLEVBQUUsTUFBTSxrQkFBa0IsQ0FBQztBQUV0RyxlQUFlLGNBQWMsQ0FBQztBQUM5QixPQUFPLEVBQUUsY0FBYyxFQUFFLHdCQUF3QixFQUFFLHdCQUF3QixFQUFFLENBQUM7QUFHOUUsT0FBTyxFQUNOLGlCQUFpQixFQUNqQixtQkFBbUIsRUFDbkIsY0FBYyxFQUNkLGVBQWUsRUFDZixhQUFhLEVBQ2IsY0FBYyxFQUNkLFlBQVksR0FDWixNQUFNLGFBQWEsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGJsb2tqcy90cmlnZ2VyLXdlYmhvb2tcbiAqXG4gKiBJbmJvdW5kIHdlYmhvb2sgdHJpZ2dlciBmb3IgQmxvayB3b3JrZmxvd3MuIE1vdW50cyB2ZXJpZmllZCBQT1NUXG4gKiByb3V0ZXMgb24gdGhlIHNoYXJlZCBIb25vIHNlcnZlciBhbG9uZ3NpZGUgSFRUUCwgV2ViU29ja2V0LCBhbmRcbiAqIFNTRSByb3V0ZXMg4oCUIHNhbWUgcG9ydCwgc2FtZSBtaWRkbGV3YXJlIGNoYWluLCBzYW1lIFN0dWRpb1xuICogdHJhY2luZy4gVmVyaWZpZXMgcHJvdmlkZXIgc2lnbmF0dXJlcyAoR2l0SHViLCBTdHJpcGUsIFNsYWNrLFxuICogU2hvcGlmeSwgU3ZpeC9TdGFuZGFyZCBXZWJob29rcykgb3IgYSBjdXN0b20gSE1BQyBzY2hlbWUsIGFwcGxpZXNcbiAqIHJlcGxheSBwcm90ZWN0aW9uIHZpYSB0aGUgaWRlbXBvdGVuY3kgY2FjaGUsIGFuZCBkaXNwYXRjaGVzIHRoZVxuICogd29ya2Zsb3cuXG4gKlxuICogdjAuNysgdXNhZ2UgKGp1c3QgYWRkIHRoZSB0cmlnZ2VyIHRvIHlvdXIgd29ya2Zsb3cpOlxuICpcbiAqIGBgYGpzb25cbiAqIHtcbiAqICAgXCJuYW1lXCI6IFwic3RyaXBlLWV2ZW50c1wiLFxuICogICBcInRyaWdnZXJcIjoge1xuICogICAgIFwid2ViaG9va1wiOiB7XG4gKiAgICAgICBcInByb3ZpZGVyXCI6IFwic3RyaXBlXCIsXG4gKiAgICAgICBcInBhdGhcIjogXCIvd2ViaG9va3Mvc3RyaXBlXCIsXG4gKiAgICAgICBcInNlY3JldEVudlwiOiBcIlNUUklQRV9XRUJIT09LX1NFQ1JFVFwiLFxuICogICAgICAgXCJuYW1lc3BhY2VcIjogXCJzdHJpcGVcIixcbiAqICAgICAgIFwiaWRlbXBvdGVuY3lLZXlcIjogXCJqcy9jdHgucmVxdWVzdC5ib2R5LmlkXCJcbiAqICAgICB9XG4gKiAgIH0sXG4gKiAgIFwic3RlcHNcIjogW1xuICogICAgIHsgXCJpZFwiOiBcImRpc3BhdGNoXCIsIFwic3Vid29ya2Zsb3dcIjogXCJqcy9jdHgucmVxdWVzdC5ib2R5LnR5cGVcIiwgXCJpbnB1dHNcIjogeyBcInN0cmlwZUV2ZW50XCI6IFwianMvY3R4LnJlcXVlc3QuYm9keVwiIH0gfVxuICogICBdXG4gKiB9XG4gKiBgYGBcbiAqXG4gKiBTZWUgW2FkZGl0aW9uYWwtdHJpZ2dlcnMtcGxhbi5tZHhdKC4uLy4uLy4uL2RvY3MvYy9kZXZ0b29scy9hZGRpdGlvbmFsLXRyaWdnZXJzLXBsYW4ubWR4I3dlYmhvb2stdHJpZ2dlcilcbiAqIGZvciB0aGUgZnVsbCBkZXNpZ24uXG4gKi9cblxuaW1wb3J0IFdlYmhvb2tUcmlnZ2VyLCB7IF9nZXRBY3RpdmVXZWJob29rVHJpZ2dlciwgX3NldEFjdGl2ZVdlYmhvb2tUcmlnZ2VyIH0gZnJvbSBcIi4vV2ViaG9va1RyaWdnZXJcIjtcblxuZXhwb3J0IGRlZmF1bHQgV2ViaG9va1RyaWdnZXI7XG5leHBvcnQgeyBXZWJob29rVHJpZ2dlciwgX2dldEFjdGl2ZVdlYmhvb2tUcmlnZ2VyLCBfc2V0QWN0aXZlV2ViaG9va1RyaWdnZXIgfTtcbmV4cG9ydCB0eXBlIHsgV2ViaG9va1RyaWdnZXJDb25maWcgfSBmcm9tIFwiLi9XZWJob29rVHJpZ2dlclwiO1xuZXhwb3J0IHR5cGUgeyBXZWJob29rVHJpZ2dlck9wdHMgfSBmcm9tIFwiQGJsb2tqcy9oZWxwZXJcIjtcbmV4cG9ydCB7XG5cdEJVSUxUSU5fVkVSSUZJRVJTLFxuXHRidWlsZEN1c3RvbVZlcmlmaWVyLFxuXHRnaXRodWJWZXJpZmllcixcblx0c2hvcGlmeVZlcmlmaWVyLFxuXHRzbGFja1ZlcmlmaWVyLFxuXHRzdHJpcGVWZXJpZmllcixcblx0c3ZpeFZlcmlmaWVyLFxufSBmcm9tIFwiLi92ZXJpZmllcnNcIjtcbmV4cG9ydCB0eXBlIHsgQ3VzdG9tU2lnbmF0dXJlQ29uZmlnLCBWZXJpZnlFcnJvciwgVmVyaWZ5SW5wdXQsIFZlcmlmeU9rLCBWZXJpZnlSZXN1bHQsIFZlcmlmaWVyIH0gZnJvbSBcIi4vdmVyaWZpZXJzXCI7XG4iXX0=
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webhook signature verifiers — one strategy per supported provider.
|
|
3
|
+
*
|
|
4
|
+
* Each verifier reads the raw request bytes (NOT the JSON-parsed body)
|
|
5
|
+
* and the provider-specific signature header, computes the expected
|
|
6
|
+
* HMAC against a shared secret, and constant-time compares. On match,
|
|
7
|
+
* returns `{ ok: true, eventId, eventType }`. On mismatch / missing
|
|
8
|
+
* header / drift, returns `{ ok: false, reason, message }`.
|
|
9
|
+
*
|
|
10
|
+
* Constant-time comparison via `crypto.timingSafeEqual` is mandatory —
|
|
11
|
+
* a naive `===` compare leaks the expected HMAC byte by byte through
|
|
12
|
+
* timing variance and a network-adjacent attacker can recover the
|
|
13
|
+
* secret in ~256 requests per byte.
|
|
14
|
+
*
|
|
15
|
+
* Built-in providers + their signature shapes:
|
|
16
|
+
*
|
|
17
|
+
* - **github**: `X-Hub-Signature-256: sha256=<hex>` over rawBody.
|
|
18
|
+
* Event id from `X-GitHub-Delivery`; event type
|
|
19
|
+
* from `X-GitHub-Event` header.
|
|
20
|
+
* - **stripe**: `Stripe-Signature: t=<ts>,v1=<hex>` over
|
|
21
|
+
* `<ts>.<rawBody>` with a 5-minute drift window.
|
|
22
|
+
* Event id + type from body.id / body.type.
|
|
23
|
+
* - **slack**: `X-Slack-Signature: v0=<hex>` over
|
|
24
|
+
* `v0:<X-Slack-Request-Timestamp>:<rawBody>` with a
|
|
25
|
+
* 5-minute drift window. Event type from
|
|
26
|
+
* body.event.type; event id from body.event_id.
|
|
27
|
+
* - **shopify**: `X-Shopify-Hmac-Sha256: <base64>` over rawBody.
|
|
28
|
+
* Event type from `X-Shopify-Topic`; event id from
|
|
29
|
+
* `X-Shopify-Webhook-Id`.
|
|
30
|
+
* - **svix**: Standard Webhooks. `webhook-signature:
|
|
31
|
+
* v1,<base64>` over `<webhook-id>.<webhook-timestamp>.<rawBody>`
|
|
32
|
+
* with a 5-minute drift window. Event id from
|
|
33
|
+
* `webhook-id`; event type from body.type.
|
|
34
|
+
*
|
|
35
|
+
* Custom (unknown provider) verifier is built dynamically by
|
|
36
|
+
* `buildCustomVerifier()` from the workflow's `signature` config.
|
|
37
|
+
*/
|
|
38
|
+
/** Successful verification result — workflow may proceed. */
|
|
39
|
+
export interface VerifyOk {
|
|
40
|
+
ok: true;
|
|
41
|
+
/** Provider-specific event id (used for replay-protection cache key). */
|
|
42
|
+
eventId: string;
|
|
43
|
+
/** Provider-specific event type (used for the allowlist check). */
|
|
44
|
+
eventType: string;
|
|
45
|
+
}
|
|
46
|
+
/** Verification failure — trigger returns 401 with structured reason. */
|
|
47
|
+
export interface VerifyError {
|
|
48
|
+
ok: false;
|
|
49
|
+
/** Stable discriminator — log/alert dashboards branch on this. */
|
|
50
|
+
reason: "missing_signature" | "missing_timestamp" | "missing_secret" | "bad_format" | "timestamp_drift" | "signature_mismatch";
|
|
51
|
+
/** Human-readable error message. Safe to surface to the sender. */
|
|
52
|
+
message: string;
|
|
53
|
+
}
|
|
54
|
+
export type VerifyResult = VerifyOk | VerifyError;
|
|
55
|
+
/** Inputs every verifier receives. */
|
|
56
|
+
export interface VerifyInput {
|
|
57
|
+
headers: Record<string, string>;
|
|
58
|
+
rawBody: string;
|
|
59
|
+
parsedBody: unknown;
|
|
60
|
+
secret: string;
|
|
61
|
+
toleranceSec: number;
|
|
62
|
+
}
|
|
63
|
+
export interface Verifier {
|
|
64
|
+
verify(input: VerifyInput): VerifyResult;
|
|
65
|
+
}
|
|
66
|
+
export declare const githubVerifier: Verifier;
|
|
67
|
+
export declare const stripeVerifier: Verifier;
|
|
68
|
+
export declare const slackVerifier: Verifier;
|
|
69
|
+
export declare const shopifyVerifier: Verifier;
|
|
70
|
+
export declare const svixVerifier: Verifier;
|
|
71
|
+
export interface CustomSignatureConfig {
|
|
72
|
+
scheme: "hmac-sha256" | "hmac-sha1" | "hmac-sha512";
|
|
73
|
+
header: string;
|
|
74
|
+
format: string;
|
|
75
|
+
secretEnv: string;
|
|
76
|
+
tolerance: number;
|
|
77
|
+
timestampHeader?: string;
|
|
78
|
+
}
|
|
79
|
+
export declare function buildCustomVerifier(config: CustomSignatureConfig): Verifier;
|
|
80
|
+
export declare const BUILTIN_VERIFIERS: Record<string, Verifier>;
|