@nestarc/webhook 0.8.0 → 0.10.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/README.md +76 -25
- package/dist/adapters/fetch-http-client.d.ts.map +1 -1
- package/dist/adapters/fetch-http-client.js +1 -1
- package/dist/adapters/fetch-http-client.js.map +1 -1
- package/dist/adapters/plaintext-secret-vault.d.ts.map +1 -1
- package/dist/adapters/plaintext-secret-vault.js +12 -2
- package/dist/adapters/plaintext-secret-vault.js.map +1 -1
- package/dist/adapters/prisma-delivery.repository.d.ts +12 -6
- package/dist/adapters/prisma-delivery.repository.d.ts.map +1 -1
- package/dist/adapters/prisma-delivery.repository.js +234 -46
- package/dist/adapters/prisma-delivery.repository.js.map +1 -1
- package/dist/adapters/prisma-endpoint.repository.d.ts +4 -3
- package/dist/adapters/prisma-endpoint.repository.d.ts.map +1 -1
- package/dist/adapters/prisma-endpoint.repository.js +41 -6
- package/dist/adapters/prisma-endpoint.repository.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/interfaces/webhook-delivery.interface.d.ts +17 -0
- package/dist/interfaces/webhook-delivery.interface.d.ts.map +1 -1
- package/dist/interfaces/webhook-endpoint.interface.d.ts +11 -1
- package/dist/interfaces/webhook-endpoint.interface.d.ts.map +1 -1
- package/dist/interfaces/webhook-options.interface.d.ts +8 -6
- package/dist/interfaces/webhook-options.interface.d.ts.map +1 -1
- package/dist/ports/webhook-delivery.repository.d.ts +30 -10
- package/dist/ports/webhook-delivery.repository.d.ts.map +1 -1
- package/dist/ports/webhook-endpoint.repository.d.ts +24 -4
- package/dist/ports/webhook-endpoint.repository.d.ts.map +1 -1
- package/dist/ports/webhook-event.repository.d.ts +3 -1
- package/dist/ports/webhook-event.repository.d.ts.map +1 -1
- package/dist/ports/webhook-http-client.d.ts +4 -0
- package/dist/ports/webhook-http-client.d.ts.map +1 -1
- package/dist/ports/webhook-secret-vault.d.ts +1 -0
- package/dist/ports/webhook-secret-vault.d.ts.map +1 -1
- package/dist/webhook.admin.service.d.ts +6 -4
- package/dist/webhook.admin.service.d.ts.map +1 -1
- package/dist/webhook.admin.service.js +8 -2
- package/dist/webhook.admin.service.js.map +1 -1
- package/dist/webhook.circuit-breaker.d.ts +6 -4
- package/dist/webhook.circuit-breaker.d.ts.map +1 -1
- package/dist/webhook.circuit-breaker.js +10 -8
- package/dist/webhook.circuit-breaker.js.map +1 -1
- package/dist/webhook.constants.d.ts +9 -2
- package/dist/webhook.constants.d.ts.map +1 -1
- package/dist/webhook.constants.js +11 -4
- package/dist/webhook.constants.js.map +1 -1
- package/dist/webhook.delivery-admin.service.d.ts +2 -1
- package/dist/webhook.delivery-admin.service.d.ts.map +1 -1
- package/dist/webhook.delivery-admin.service.js +3 -0
- package/dist/webhook.delivery-admin.service.js.map +1 -1
- package/dist/webhook.delivery-worker.d.ts +4 -0
- package/dist/webhook.delivery-worker.d.ts.map +1 -1
- package/dist/webhook.delivery-worker.js +49 -22
- package/dist/webhook.delivery-worker.js.map +1 -1
- package/dist/webhook.dispatcher.d.ts +1 -0
- package/dist/webhook.dispatcher.d.ts.map +1 -1
- package/dist/webhook.dispatcher.js +13 -6
- package/dist/webhook.dispatcher.js.map +1 -1
- package/dist/webhook.endpoint-admin.service.d.ts +4 -1
- package/dist/webhook.endpoint-admin.service.d.ts.map +1 -1
- package/dist/webhook.endpoint-admin.service.js +36 -9
- package/dist/webhook.endpoint-admin.service.js.map +1 -1
- package/dist/webhook.module.d.ts.map +1 -1
- package/dist/webhook.module.js +22 -13
- package/dist/webhook.module.js.map +1 -1
- package/dist/webhook.signer.d.ts +2 -0
- package/dist/webhook.signer.d.ts.map +1 -1
- package/dist/webhook.signer.js +28 -14
- package/dist/webhook.signer.js.map +1 -1
- package/package.json +4 -4
- package/src/sql/create-webhook-tables.sql +27 -1
- package/src/sql/migrations/v0.9.0.sql +32 -0
package/dist/webhook.signer.js
CHANGED
|
@@ -43,30 +43,44 @@ exports.WebhookSigner = void 0;
|
|
|
43
43
|
const crypto = __importStar(require("crypto"));
|
|
44
44
|
const common_1 = require("@nestjs/common");
|
|
45
45
|
let WebhookSigner = class WebhookSigner {
|
|
46
|
-
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
.
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
signAll(eventId, timestamp, body, secrets) {
|
|
47
|
+
const signatures = secrets
|
|
48
|
+
.filter((secret) => Boolean(secret))
|
|
49
|
+
.map((secret) => this.signSingle(eventId, timestamp, body, secret));
|
|
50
|
+
if (signatures.length === 0) {
|
|
51
|
+
throw new Error('At least one signing secret is required');
|
|
52
|
+
}
|
|
52
53
|
return {
|
|
53
54
|
'webhook-id': eventId,
|
|
54
55
|
'webhook-timestamp': String(timestamp),
|
|
55
|
-
'webhook-signature':
|
|
56
|
+
'webhook-signature': signatures.join(' '),
|
|
56
57
|
};
|
|
57
58
|
}
|
|
59
|
+
sign(eventId, timestamp, body, secret) {
|
|
60
|
+
return this.signAll(eventId, timestamp, body, [secret]);
|
|
61
|
+
}
|
|
58
62
|
verify(eventId, timestamp, body, secret, signature) {
|
|
59
|
-
const expected = this.
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
const expected = this.signSingle(eventId, timestamp, body, secret);
|
|
64
|
+
const providedSignatures = signature.trim().split(/\s+/).filter(Boolean);
|
|
65
|
+
return providedSignatures.some((candidate) => {
|
|
66
|
+
const a = Buffer.from(expected);
|
|
67
|
+
const b = Buffer.from(candidate);
|
|
68
|
+
if (a.length !== b.length)
|
|
69
|
+
return false;
|
|
70
|
+
return crypto.timingSafeEqual(a, b);
|
|
71
|
+
});
|
|
66
72
|
}
|
|
67
73
|
generateSecret() {
|
|
68
74
|
return crypto.randomBytes(32).toString('base64');
|
|
69
75
|
}
|
|
76
|
+
signSingle(eventId, timestamp, body, secret) {
|
|
77
|
+
const toSign = `${eventId}.${timestamp}.${body}`;
|
|
78
|
+
const hmac = crypto
|
|
79
|
+
.createHmac('sha256', Buffer.from(secret, 'base64'))
|
|
80
|
+
.update(toSign)
|
|
81
|
+
.digest('base64');
|
|
82
|
+
return `v1,${hmac}`;
|
|
83
|
+
}
|
|
70
84
|
};
|
|
71
85
|
exports.WebhookSigner = WebhookSigner;
|
|
72
86
|
exports.WebhookSigner = WebhookSigner = __decorate([
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.signer.js","sourceRoot":"","sources":["../src/webhook.signer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,2CAA4C;AAUrC,IAAM,aAAa,GAAnB,MAAM,aAAa;IACxB,
|
|
1
|
+
{"version":3,"file":"webhook.signer.js","sourceRoot":"","sources":["../src/webhook.signer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,2CAA4C;AAUrC,IAAM,aAAa,GAAnB,MAAM,aAAa;IACxB,OAAO,CACL,OAAe,EACf,SAAiB,EACjB,IAAY,EACZ,OAAiB;QAEjB,MAAM,UAAU,GAAG,OAAO;aACvB,MAAM,CAAC,CAAC,MAAM,EAAoB,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;aACrD,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAEtE,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;QAC7D,CAAC;QAED,OAAO;YACL,YAAY,EAAE,OAAO;YACrB,mBAAmB,EAAE,MAAM,CAAC,SAAS,CAAC;YACtC,mBAAmB,EAAE,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC;SAC1C,CAAC;IACJ,CAAC;IAED,IAAI,CACF,OAAe,EACf,SAAiB,EACjB,IAAY,EACZ,MAAc;QAEd,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CACJ,OAAe,EACf,SAAiB,EACjB,IAAY,EACZ,MAAc,EACd,SAAiB;QAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QACnE,MAAM,kBAAkB,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAEzE,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE;YAC3C,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAChC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAEjC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;gBAAE,OAAO,KAAK,CAAC;YAExC,OAAO,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC;IAED,cAAc;QACZ,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnD,CAAC;IAEO,UAAU,CAChB,OAAe,EACf,SAAiB,EACjB,IAAY,EACZ,MAAc;QAEd,MAAM,MAAM,GAAG,GAAG,OAAO,IAAI,SAAS,IAAI,IAAI,EAAE,CAAC;QACjD,MAAM,IAAI,GAAG,MAAM;aAChB,UAAU,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;aACnD,MAAM,CAAC,MAAM,CAAC;aACd,MAAM,CAAC,QAAQ,CAAC,CAAC;QAEpB,OAAO,MAAM,IAAI,EAAE,CAAC;IACtB,CAAC;CACF,CAAA;AArEY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;GACA,aAAa,CAqEzB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestarc/webhook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Outbound webhook delivery for NestJS — HMAC signing, exponential retry, circuit breaker, delivery logs, fan-out, Standard Webhooks compatible",
|
|
5
5
|
"author": "nestarc",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,9 +33,9 @@
|
|
|
33
33
|
"@nestjs/schedule": "^5.0.0",
|
|
34
34
|
"@nestjs/testing": "^11.0.0",
|
|
35
35
|
"@prisma/client": "^6.0.0",
|
|
36
|
-
"@types/jest": "^
|
|
37
|
-
"@types/node": "^
|
|
38
|
-
"jest": "^
|
|
36
|
+
"@types/jest": "^30.0.0",
|
|
37
|
+
"@types/node": "^25.6.0",
|
|
38
|
+
"jest": "^30.3.0",
|
|
39
39
|
"prisma": "^6.19.3",
|
|
40
40
|
"reflect-metadata": "^0.2.0",
|
|
41
41
|
"rxjs": "^7.8.0",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
-- @nestarc/webhook — PostgreSQL migration
|
|
2
|
-
-- Creates the
|
|
2
|
+
-- Creates the four core tables for outbound webhook delivery.
|
|
3
3
|
-- Run this migration against your PostgreSQL database before using the module.
|
|
4
4
|
|
|
5
5
|
-- Required for gen_random_uuid() on PostgreSQL < 13
|
|
@@ -9,6 +9,8 @@ CREATE TABLE IF NOT EXISTS webhook_endpoints (
|
|
|
9
9
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
10
10
|
url VARCHAR(2048) NOT NULL,
|
|
11
11
|
secret VARCHAR(255) NOT NULL,
|
|
12
|
+
previous_secret TEXT,
|
|
13
|
+
previous_secret_expires_at TIMESTAMPTZ,
|
|
12
14
|
events VARCHAR(255)[] NOT NULL DEFAULT '{}',
|
|
13
15
|
active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
14
16
|
description VARCHAR(500),
|
|
@@ -47,6 +49,9 @@ CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
|
47
49
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
48
50
|
event_id UUID NOT NULL REFERENCES webhook_events(id),
|
|
49
51
|
endpoint_id UUID NOT NULL REFERENCES webhook_endpoints(id),
|
|
52
|
+
endpoint_url_snapshot TEXT,
|
|
53
|
+
signing_secret_snapshot TEXT,
|
|
54
|
+
secondary_signing_secret_snapshot TEXT,
|
|
50
55
|
status VARCHAR(20) NOT NULL DEFAULT 'PENDING'
|
|
51
56
|
CHECK (status IN ('PENDING', 'SENDING', 'SENT', 'FAILED')),
|
|
52
57
|
attempts INT NOT NULL DEFAULT 0,
|
|
@@ -76,3 +81,24 @@ CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_endpoint_status
|
|
|
76
81
|
ON webhook_deliveries (endpoint_id, status);
|
|
77
82
|
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_event
|
|
78
83
|
ON webhook_deliveries (event_id);
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
CREATE TABLE IF NOT EXISTS webhook_delivery_attempts (
|
|
88
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
89
|
+
delivery_id UUID NOT NULL REFERENCES webhook_deliveries(id) ON DELETE CASCADE,
|
|
90
|
+
attempt_number INT NOT NULL,
|
|
91
|
+
status VARCHAR(20) NOT NULL
|
|
92
|
+
CHECK (status IN ('PENDING', 'SENDING', 'SENT', 'FAILED')),
|
|
93
|
+
response_status INT,
|
|
94
|
+
response_body TEXT,
|
|
95
|
+
response_body_truncated BOOLEAN NOT NULL DEFAULT FALSE,
|
|
96
|
+
latency_ms INT,
|
|
97
|
+
last_error TEXT,
|
|
98
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
99
|
+
CONSTRAINT webhook_delivery_attempts_delivery_id_attempt_number_key
|
|
100
|
+
UNIQUE (delivery_id, attempt_number)
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
CREATE INDEX IF NOT EXISTS idx_delivery_attempts_delivery_created
|
|
104
|
+
ON webhook_delivery_attempts (delivery_id, created_at);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
-- @nestarc/webhook v0.9.0 — additive migration
|
|
2
|
+
-- Adds per-attempt audit logs, endpoint snapshots, and secret rotation overlap.
|
|
3
|
+
|
|
4
|
+
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
|
5
|
+
|
|
6
|
+
ALTER TABLE webhook_endpoints
|
|
7
|
+
ADD COLUMN IF NOT EXISTS previous_secret TEXT,
|
|
8
|
+
ADD COLUMN IF NOT EXISTS previous_secret_expires_at TIMESTAMPTZ;
|
|
9
|
+
|
|
10
|
+
ALTER TABLE webhook_deliveries
|
|
11
|
+
ADD COLUMN IF NOT EXISTS endpoint_url_snapshot TEXT,
|
|
12
|
+
ADD COLUMN IF NOT EXISTS signing_secret_snapshot TEXT,
|
|
13
|
+
ADD COLUMN IF NOT EXISTS secondary_signing_secret_snapshot TEXT;
|
|
14
|
+
|
|
15
|
+
CREATE TABLE IF NOT EXISTS webhook_delivery_attempts (
|
|
16
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
17
|
+
delivery_id UUID NOT NULL REFERENCES webhook_deliveries(id) ON DELETE CASCADE,
|
|
18
|
+
attempt_number INT NOT NULL,
|
|
19
|
+
status VARCHAR(20) NOT NULL
|
|
20
|
+
CHECK (status IN ('PENDING', 'SENDING', 'SENT', 'FAILED')),
|
|
21
|
+
response_status INT,
|
|
22
|
+
response_body TEXT,
|
|
23
|
+
response_body_truncated BOOLEAN NOT NULL DEFAULT FALSE,
|
|
24
|
+
latency_ms INT,
|
|
25
|
+
last_error TEXT,
|
|
26
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
27
|
+
CONSTRAINT webhook_delivery_attempts_delivery_id_attempt_number_key
|
|
28
|
+
UNIQUE (delivery_id, attempt_number)
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_delivery_attempts_delivery_created
|
|
32
|
+
ON webhook_delivery_attempts (delivery_id, created_at);
|