@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.
Files changed (73) hide show
  1. package/README.md +76 -25
  2. package/dist/adapters/fetch-http-client.d.ts.map +1 -1
  3. package/dist/adapters/fetch-http-client.js +1 -1
  4. package/dist/adapters/fetch-http-client.js.map +1 -1
  5. package/dist/adapters/plaintext-secret-vault.d.ts.map +1 -1
  6. package/dist/adapters/plaintext-secret-vault.js +12 -2
  7. package/dist/adapters/plaintext-secret-vault.js.map +1 -1
  8. package/dist/adapters/prisma-delivery.repository.d.ts +12 -6
  9. package/dist/adapters/prisma-delivery.repository.d.ts.map +1 -1
  10. package/dist/adapters/prisma-delivery.repository.js +234 -46
  11. package/dist/adapters/prisma-delivery.repository.js.map +1 -1
  12. package/dist/adapters/prisma-endpoint.repository.d.ts +4 -3
  13. package/dist/adapters/prisma-endpoint.repository.d.ts.map +1 -1
  14. package/dist/adapters/prisma-endpoint.repository.js +41 -6
  15. package/dist/adapters/prisma-endpoint.repository.js.map +1 -1
  16. package/dist/index.d.ts +11 -6
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +9 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/interfaces/webhook-delivery.interface.d.ts +17 -0
  21. package/dist/interfaces/webhook-delivery.interface.d.ts.map +1 -1
  22. package/dist/interfaces/webhook-endpoint.interface.d.ts +11 -1
  23. package/dist/interfaces/webhook-endpoint.interface.d.ts.map +1 -1
  24. package/dist/interfaces/webhook-options.interface.d.ts +8 -6
  25. package/dist/interfaces/webhook-options.interface.d.ts.map +1 -1
  26. package/dist/ports/webhook-delivery.repository.d.ts +30 -10
  27. package/dist/ports/webhook-delivery.repository.d.ts.map +1 -1
  28. package/dist/ports/webhook-endpoint.repository.d.ts +24 -4
  29. package/dist/ports/webhook-endpoint.repository.d.ts.map +1 -1
  30. package/dist/ports/webhook-event.repository.d.ts +3 -1
  31. package/dist/ports/webhook-event.repository.d.ts.map +1 -1
  32. package/dist/ports/webhook-http-client.d.ts +4 -0
  33. package/dist/ports/webhook-http-client.d.ts.map +1 -1
  34. package/dist/ports/webhook-secret-vault.d.ts +1 -0
  35. package/dist/ports/webhook-secret-vault.d.ts.map +1 -1
  36. package/dist/webhook.admin.service.d.ts +6 -4
  37. package/dist/webhook.admin.service.d.ts.map +1 -1
  38. package/dist/webhook.admin.service.js +8 -2
  39. package/dist/webhook.admin.service.js.map +1 -1
  40. package/dist/webhook.circuit-breaker.d.ts +6 -4
  41. package/dist/webhook.circuit-breaker.d.ts.map +1 -1
  42. package/dist/webhook.circuit-breaker.js +10 -8
  43. package/dist/webhook.circuit-breaker.js.map +1 -1
  44. package/dist/webhook.constants.d.ts +9 -2
  45. package/dist/webhook.constants.d.ts.map +1 -1
  46. package/dist/webhook.constants.js +11 -4
  47. package/dist/webhook.constants.js.map +1 -1
  48. package/dist/webhook.delivery-admin.service.d.ts +2 -1
  49. package/dist/webhook.delivery-admin.service.d.ts.map +1 -1
  50. package/dist/webhook.delivery-admin.service.js +3 -0
  51. package/dist/webhook.delivery-admin.service.js.map +1 -1
  52. package/dist/webhook.delivery-worker.d.ts +4 -0
  53. package/dist/webhook.delivery-worker.d.ts.map +1 -1
  54. package/dist/webhook.delivery-worker.js +49 -22
  55. package/dist/webhook.delivery-worker.js.map +1 -1
  56. package/dist/webhook.dispatcher.d.ts +1 -0
  57. package/dist/webhook.dispatcher.d.ts.map +1 -1
  58. package/dist/webhook.dispatcher.js +13 -6
  59. package/dist/webhook.dispatcher.js.map +1 -1
  60. package/dist/webhook.endpoint-admin.service.d.ts +4 -1
  61. package/dist/webhook.endpoint-admin.service.d.ts.map +1 -1
  62. package/dist/webhook.endpoint-admin.service.js +36 -9
  63. package/dist/webhook.endpoint-admin.service.js.map +1 -1
  64. package/dist/webhook.module.d.ts.map +1 -1
  65. package/dist/webhook.module.js +22 -13
  66. package/dist/webhook.module.js.map +1 -1
  67. package/dist/webhook.signer.d.ts +2 -0
  68. package/dist/webhook.signer.d.ts.map +1 -1
  69. package/dist/webhook.signer.js +28 -14
  70. package/dist/webhook.signer.js.map +1 -1
  71. package/package.json +4 -4
  72. package/src/sql/create-webhook-tables.sql +27 -1
  73. package/src/sql/migrations/v0.9.0.sql +32 -0
@@ -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
- sign(eventId, timestamp, body, secret) {
47
- const toSign = `${eventId}.${timestamp}.${body}`;
48
- const hmac = crypto
49
- .createHmac('sha256', Buffer.from(secret, 'base64'))
50
- .update(toSign)
51
- .digest('base64');
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': `v1,${hmac}`,
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.sign(eventId, timestamp, body, secret);
60
- const expectedSig = expected['webhook-signature'];
61
- const a = Buffer.from(expectedSig);
62
- const b = Buffer.from(signature);
63
- if (a.length !== b.length)
64
- return false;
65
- return crypto.timingSafeEqual(a, b);
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,IAAI,CACF,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;YACL,YAAY,EAAE,OAAO;YACrB,mBAAmB,EAAE,MAAM,CAAC,SAAS,CAAC;YACtC,mBAAmB,EAAE,MAAM,IAAI,EAAE;SAClC,CAAC;IACJ,CAAC;IAED,MAAM,CACJ,OAAe,EACf,SAAiB,EACjB,IAAY,EACZ,MAAc,EACd,SAAiB;QAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;QAC7D,MAAM,WAAW,GAAG,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QAElD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACnC,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAEjC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;YAAE,OAAO,KAAK,CAAC;QAExC,OAAO,MAAM,CAAC,eAAe,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,cAAc;QACZ,OAAO,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACnD,CAAC;CACF,CAAA;AAzCY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;GACA,aAAa,CAyCzB"}
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.8.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": "^29.5.0",
37
- "@types/node": "^20.0.0",
38
- "jest": "^29.7.0",
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 three core tables for outbound webhook delivery.
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);