@nestarc/webhook 0.12.0 → 0.13.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 +97 -0
- package/dist/adapters/fetch-http-client.d.ts +2 -2
- package/dist/adapters/fetch-http-client.d.ts.map +1 -1
- package/dist/adapters/fetch-http-client.js +165 -29
- package/dist/adapters/fetch-http-client.js.map +1 -1
- package/dist/adapters/prisma-delivery.repository.d.ts +9 -3
- package/dist/adapters/prisma-delivery.repository.d.ts.map +1 -1
- package/dist/adapters/prisma-delivery.repository.js +228 -19
- package/dist/adapters/prisma-delivery.repository.js.map +1 -1
- package/dist/adapters/prisma-endpoint.repository.d.ts.map +1 -1
- package/dist/adapters/prisma-endpoint.repository.js +6 -2
- package/dist/adapters/prisma-endpoint.repository.js.map +1 -1
- package/dist/adapters/prisma-event.repository.d.ts +3 -1
- package/dist/adapters/prisma-event.repository.d.ts.map +1 -1
- package/dist/adapters/prisma-event.repository.js +36 -2
- package/dist/adapters/prisma-event.repository.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces/webhook-delivery.interface.d.ts +26 -0
- package/dist/interfaces/webhook-delivery.interface.d.ts.map +1 -1
- package/dist/interfaces/webhook-options.interface.d.ts +27 -0
- package/dist/interfaces/webhook-options.interface.d.ts.map +1 -1
- package/dist/ports/webhook-delivery.repository.d.ts +6 -2
- package/dist/ports/webhook-delivery.repository.d.ts.map +1 -1
- package/dist/ports/webhook-event.repository.d.ts +6 -0
- package/dist/ports/webhook-event.repository.d.ts.map +1 -1
- package/dist/ports/webhook-http-client.d.ts +8 -1
- package/dist/ports/webhook-http-client.d.ts.map +1 -1
- package/dist/webhook.admin.service.d.ts +4 -2
- 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.delivery-admin.service.d.ts +4 -2
- package/dist/webhook.delivery-admin.service.d.ts.map +1 -1
- package/dist/webhook.delivery-admin.service.js +14 -2
- package/dist/webhook.delivery-admin.service.js.map +1 -1
- package/dist/webhook.dispatcher.d.ts.map +1 -1
- package/dist/webhook.dispatcher.js +5 -2
- package/dist/webhook.dispatcher.js.map +1 -1
- package/dist/webhook.module.d.ts.map +1 -1
- package/dist/webhook.module.js +6 -2
- package/dist/webhook.module.js.map +1 -1
- package/dist/webhook.retention-admin.service.d.ts +10 -0
- package/dist/webhook.retention-admin.service.d.ts.map +1 -0
- package/dist/webhook.retention-admin.service.js +50 -0
- package/dist/webhook.retention-admin.service.js.map +1 -0
- package/dist/webhook.service.d.ts +7 -4
- package/dist/webhook.service.d.ts.map +1 -1
- package/dist/webhook.service.js +40 -10
- package/dist/webhook.service.js.map +1 -1
- package/dist/webhook.signer.d.ts +5 -0
- package/dist/webhook.signer.d.ts.map +1 -1
- package/dist/webhook.signer.js +14 -0
- package/dist/webhook.signer.js.map +1 -1
- package/dist/webhook.url-validator.d.ts +1 -1
- package/dist/webhook.url-validator.d.ts.map +1 -1
- package/dist/webhook.url-validator.js +145 -51
- package/dist/webhook.url-validator.js.map +1 -1
- package/package.json +9 -3
- package/src/sql/create-webhook-tables.sql +11 -5
- package/src/sql/migrations/v0.13.0.sql +11 -0
package/dist/webhook.signer.d.ts
CHANGED
|
@@ -4,10 +4,15 @@ export interface SignatureHeaders {
|
|
|
4
4
|
'webhook-timestamp': string;
|
|
5
5
|
'webhook-signature': string;
|
|
6
6
|
}
|
|
7
|
+
export interface WebhookVerificationOptions {
|
|
8
|
+
toleranceSeconds: number;
|
|
9
|
+
now?: Date;
|
|
10
|
+
}
|
|
7
11
|
export declare class WebhookSigner {
|
|
8
12
|
signAll(eventId: string, timestamp: number, body: string, secrets: string[]): SignatureHeaders;
|
|
9
13
|
sign(eventId: string, timestamp: number, body: string, secret: string): SignatureHeaders;
|
|
10
14
|
verify(eventId: string, timestamp: number, body: string, secret: string, signature: string): boolean;
|
|
15
|
+
verifyWithTolerance(eventId: string, timestamp: number, body: string, secret: string, signature: string, options: WebhookVerificationOptions): boolean;
|
|
11
16
|
generateSecret(): string;
|
|
12
17
|
private signSingle;
|
|
13
18
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.signer.d.ts","sourceRoot":"","sources":["../src/webhook.signer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,qBACa,aAAa;IACxB,OAAO,CACL,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,GAChB,gBAAgB;IAgBnB,IAAI,CACF,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,gBAAgB;IAInB,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO;IAcV,cAAc,IAAI,MAAM;IAIxB,OAAO,CAAC,UAAU;CAcnB"}
|
|
1
|
+
{"version":3,"file":"webhook.signer.d.ts","sourceRoot":"","sources":["../src/webhook.signer.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,gBAAgB;IAC/B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,0BAA0B;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED,qBACa,aAAa;IACxB,OAAO,CACL,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EAAE,GAChB,gBAAgB;IAgBnB,IAAI,CACF,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,gBAAgB;IAInB,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,GAChB,OAAO;IAcV,mBAAmB,CACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,0BAA0B,GAClC,OAAO;IAoBV,cAAc,IAAI,MAAM;IAIxB,OAAO,CAAC,UAAU;CAcnB"}
|
package/dist/webhook.signer.js
CHANGED
|
@@ -70,6 +70,20 @@ let WebhookSigner = class WebhookSigner {
|
|
|
70
70
|
return crypto.timingSafeEqual(a, b);
|
|
71
71
|
});
|
|
72
72
|
}
|
|
73
|
+
verifyWithTolerance(eventId, timestamp, body, secret, signature, options) {
|
|
74
|
+
if (!Number.isFinite(options.toleranceSeconds) ||
|
|
75
|
+
options.toleranceSeconds < 0) {
|
|
76
|
+
throw new Error('toleranceSeconds must be a finite non-negative number');
|
|
77
|
+
}
|
|
78
|
+
if (!Number.isFinite(timestamp)) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
const nowSeconds = Math.floor((options.now ?? new Date()).getTime() / 1000);
|
|
82
|
+
if (Math.abs(nowSeconds - timestamp) > options.toleranceSeconds) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return this.verify(eventId, timestamp, body, secret, signature);
|
|
86
|
+
}
|
|
73
87
|
generateSecret() {
|
|
74
88
|
return crypto.randomBytes(32).toString('base64');
|
|
75
89
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.signer.js","sourceRoot":"","sources":["../src/webhook.signer.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,2CAA4C;
|
|
1
|
+
{"version":3,"file":"webhook.signer.js","sourceRoot":"","sources":["../src/webhook.signer.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,5 +7,5 @@ export declare class WebhookUrlValidationError extends Error {
|
|
|
7
7
|
constructor(message: string, reason: WebhookUrlValidationReason, url?: string, resolvedIp?: string);
|
|
8
8
|
}
|
|
9
9
|
export declare function validateWebhookUrl(url: string): Promise<void>;
|
|
10
|
-
export declare function resolveAndValidateHost(hostname: string, url?: string): Promise<
|
|
10
|
+
export declare function resolveAndValidateHost(hostname: string, url?: string): Promise<string[]>;
|
|
11
11
|
//# sourceMappingURL=webhook.url-validator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.url-validator.d.ts","sourceRoot":"","sources":["../src/webhook.url-validator.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"webhook.url-validator.d.ts","sourceRoot":"","sources":["../src/webhook.url-validator.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,0BAA0B,GAClC,OAAO,GACP,QAAQ,GACR,kBAAkB,GAClB,UAAU,GACV,SAAS,GACT,YAAY,GACZ,gBAAgB,CAAC;AAkCrB,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,+BAA+B;IAC5C,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAC;IAC5C,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAG3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,0BAA0B,EAClC,GAAG,CAAC,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM;CAQtB;AAED,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCnE;AAgDD,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,EAAE,CAAC,CAsDnB"}
|
|
@@ -44,6 +44,36 @@ const BLOCKED_HOSTNAMES = new Set([
|
|
|
44
44
|
'ip6-localhost',
|
|
45
45
|
'ip6-loopback',
|
|
46
46
|
]);
|
|
47
|
+
const BLOCKED_IPV4_RANGES = [
|
|
48
|
+
ipv4Range('0.0.0.0', 8, 'invalid_target', 'not a valid public target'),
|
|
49
|
+
ipv4Range('10.0.0.0', 8, 'private', 'a private address'),
|
|
50
|
+
ipv4Range('100.64.0.0', 10, 'private', 'a shared/private address'),
|
|
51
|
+
ipv4Range('127.0.0.0', 8, 'loopback', 'a loopback address'),
|
|
52
|
+
ipv4Range('169.254.0.0', 16, 'link_local', 'a link-local/metadata address'),
|
|
53
|
+
ipv4Range('172.16.0.0', 12, 'private', 'a private address'),
|
|
54
|
+
ipv4Range('192.0.0.0', 24, 'invalid_target', 'a special-use address'),
|
|
55
|
+
ipv4Range('192.0.2.0', 24, 'invalid_target', 'a documentation address'),
|
|
56
|
+
ipv4Range('192.88.99.0', 24, 'invalid_target', 'a special-use address'),
|
|
57
|
+
ipv4Range('192.168.0.0', 16, 'private', 'a private address'),
|
|
58
|
+
ipv4Range('198.18.0.0', 15, 'invalid_target', 'a benchmarking address'),
|
|
59
|
+
ipv4Range('198.51.100.0', 24, 'invalid_target', 'a documentation address'),
|
|
60
|
+
ipv4Range('203.0.113.0', 24, 'invalid_target', 'a documentation address'),
|
|
61
|
+
ipv4Range('224.0.0.0', 4, 'invalid_target', 'a multicast address'),
|
|
62
|
+
ipv4Range('240.0.0.0', 4, 'invalid_target', 'a reserved address'),
|
|
63
|
+
];
|
|
64
|
+
const BLOCKED_IPV6_RANGES = [
|
|
65
|
+
ipv6Range('::', 128, 'invalid_target', 'not a valid public target'),
|
|
66
|
+
ipv6Range('::1', 128, 'loopback', 'a loopback address'),
|
|
67
|
+
ipv6Range('::', 96, 'invalid_target', 'a special-use address'),
|
|
68
|
+
ipv6Range('100::', 64, 'invalid_target', 'a discard-only address'),
|
|
69
|
+
ipv6Range('2001::', 23, 'invalid_target', 'a special-use address'),
|
|
70
|
+
ipv6Range('2001:db8::', 32, 'invalid_target', 'a documentation address'),
|
|
71
|
+
ipv6Range('2002::', 16, 'invalid_target', 'a special-use address'),
|
|
72
|
+
ipv6Range('3fff::', 20, 'invalid_target', 'a documentation address'),
|
|
73
|
+
ipv6Range('fc00::', 7, 'private', 'a private address'),
|
|
74
|
+
ipv6Range('fe80::', 10, 'link_local', 'a link-local address'),
|
|
75
|
+
ipv6Range('ff00::', 8, 'invalid_target', 'a multicast address'),
|
|
76
|
+
];
|
|
47
77
|
class WebhookUrlValidationError extends Error {
|
|
48
78
|
name = 'WebhookUrlValidationError';
|
|
49
79
|
reason;
|
|
@@ -86,79 +116,58 @@ async function validateWebhookUrl(url) {
|
|
|
86
116
|
}
|
|
87
117
|
}
|
|
88
118
|
function validateIPv4(ip, url) {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (parts[0] === 10) {
|
|
94
|
-
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is a private address`, 'private', url, ip);
|
|
95
|
-
}
|
|
96
|
-
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) {
|
|
97
|
-
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is a private address`, 'private', url, ip);
|
|
98
|
-
}
|
|
99
|
-
if (parts[0] === 192 && parts[1] === 168) {
|
|
100
|
-
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is a private address`, 'private', url, ip);
|
|
101
|
-
}
|
|
102
|
-
if (parts[0] === 169 && parts[1] === 254) {
|
|
103
|
-
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is a link-local/metadata address`, 'link_local', url, ip);
|
|
104
|
-
}
|
|
105
|
-
if (parts[0] === 0) {
|
|
106
|
-
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is not a valid target`, 'invalid_target', url, ip);
|
|
119
|
+
const value = ipv4ToNumber(ip);
|
|
120
|
+
const blocked = BLOCKED_IPV4_RANGES.find((range) => ipv4InRange(value, range));
|
|
121
|
+
if (blocked) {
|
|
122
|
+
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is ${blocked.description}`, blocked.reason, url, ip);
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
125
|
function validateIPv6(ip, url) {
|
|
110
|
-
if (ip === '::1') {
|
|
111
|
-
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is a loopback address`, 'loopback', url, ip);
|
|
112
|
-
}
|
|
113
126
|
const lowerIp = ip.toLowerCase();
|
|
114
|
-
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
let ipv4;
|
|
118
|
-
if (net.isIPv4(suffix)) {
|
|
119
|
-
// ::ffff:10.0.0.1 form
|
|
120
|
-
ipv4 = suffix;
|
|
121
|
-
}
|
|
122
|
-
else {
|
|
123
|
-
// ::ffff:a00:1 form (hex) — convert to IPv4
|
|
124
|
-
const hexParts = suffix.split(':');
|
|
125
|
-
if (hexParts.length === 2) {
|
|
126
|
-
const hi = parseInt(hexParts[0], 16);
|
|
127
|
-
const lo = parseInt(hexParts[1], 16);
|
|
128
|
-
ipv4 = `${(hi >> 8) & 0xff}.${hi & 0xff}.${(lo >> 8) & 0xff}.${lo & 0xff}`;
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
return; // Unknown format, let it through (conservative)
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
validateIPv4(ipv4, url);
|
|
135
|
-
return;
|
|
127
|
+
const parsed = parseIPv6ToBigInt(lowerIp);
|
|
128
|
+
if (parsed === null) {
|
|
129
|
+
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is not a valid target`, 'invalid_target', url, ip);
|
|
136
130
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
131
|
+
if (isIPv4MappedIPv6(parsed)) {
|
|
132
|
+
validateIPv4(numberToIPv4(Number(parsed & 0xffffffffn)), url);
|
|
133
|
+
return;
|
|
140
134
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is
|
|
135
|
+
const blocked = BLOCKED_IPV6_RANGES.find((range) => ipv6InRange(parsed, range));
|
|
136
|
+
if (blocked) {
|
|
137
|
+
throw new WebhookUrlValidationError(`Invalid webhook URL: "${ip}" is ${blocked.description}`, blocked.reason, url, ip);
|
|
144
138
|
}
|
|
145
139
|
}
|
|
146
140
|
async function resolveAndValidateHost(hostname, url) {
|
|
141
|
+
const cleanHostname = hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
142
|
+
if (BLOCKED_HOSTNAMES.has(cleanHostname)) {
|
|
143
|
+
throw new WebhookUrlValidationError(`Invalid webhook URL: "${cleanHostname}" is not allowed (loopback address)`, 'blocked_hostname', url);
|
|
144
|
+
}
|
|
145
|
+
if (net.isIPv4(cleanHostname)) {
|
|
146
|
+
validateIPv4(cleanHostname, url);
|
|
147
|
+
return [cleanHostname];
|
|
148
|
+
}
|
|
149
|
+
if (net.isIPv6(cleanHostname)) {
|
|
150
|
+
validateIPv6(cleanHostname, url);
|
|
151
|
+
return [cleanHostname];
|
|
152
|
+
}
|
|
147
153
|
let addresses = [];
|
|
148
154
|
try {
|
|
149
|
-
const ipv4 = await dns.promises.resolve4(
|
|
155
|
+
const ipv4 = await dns.promises.resolve4(cleanHostname);
|
|
150
156
|
addresses = addresses.concat(ipv4);
|
|
151
157
|
}
|
|
152
158
|
catch {
|
|
153
159
|
// No A record — not an error yet
|
|
154
160
|
}
|
|
155
161
|
try {
|
|
156
|
-
const ipv6 = await dns.promises.resolve6(
|
|
162
|
+
const ipv6 = await dns.promises.resolve6(cleanHostname);
|
|
157
163
|
addresses = addresses.concat(ipv6);
|
|
158
164
|
}
|
|
159
165
|
catch {
|
|
160
166
|
// No AAAA record — not an error yet
|
|
161
167
|
}
|
|
168
|
+
if (addresses.length === 0) {
|
|
169
|
+
throw new WebhookUrlValidationError(`Invalid webhook URL: hostname "${cleanHostname}" did not resolve to any address`, 'invalid_target', url);
|
|
170
|
+
}
|
|
162
171
|
for (const ip of addresses) {
|
|
163
172
|
if (net.isIPv4(ip)) {
|
|
164
173
|
validateIPv4(ip, url); // throws if private
|
|
@@ -167,5 +176,90 @@ async function resolveAndValidateHost(hostname, url) {
|
|
|
167
176
|
validateIPv6(ip, url); // throws if private
|
|
168
177
|
}
|
|
169
178
|
}
|
|
179
|
+
return addresses;
|
|
180
|
+
}
|
|
181
|
+
function ipv4Range(base, prefix, reason, description) {
|
|
182
|
+
return { base: ipv4ToNumber(base), prefix, reason, description };
|
|
183
|
+
}
|
|
184
|
+
function ipv6Range(base, prefix, reason, description) {
|
|
185
|
+
const parsed = parseIPv6ToBigInt(base);
|
|
186
|
+
if (parsed === null) {
|
|
187
|
+
throw new Error(`Invalid IPv6 range base: ${base}`);
|
|
188
|
+
}
|
|
189
|
+
return { base: parsed, prefix, reason, description };
|
|
190
|
+
}
|
|
191
|
+
function ipv4ToNumber(ip) {
|
|
192
|
+
return ip
|
|
193
|
+
.split('.')
|
|
194
|
+
.map(Number)
|
|
195
|
+
.reduce((value, part) => ((value << 8) + part) >>> 0, 0);
|
|
196
|
+
}
|
|
197
|
+
function numberToIPv4(value) {
|
|
198
|
+
return [
|
|
199
|
+
(value >>> 24) & 0xff,
|
|
200
|
+
(value >>> 16) & 0xff,
|
|
201
|
+
(value >>> 8) & 0xff,
|
|
202
|
+
value & 0xff,
|
|
203
|
+
].join('.');
|
|
204
|
+
}
|
|
205
|
+
function ipv4InRange(value, range) {
|
|
206
|
+
const mask = range.prefix === 0
|
|
207
|
+
? 0
|
|
208
|
+
: (0xffffffff << (32 - range.prefix)) >>> 0;
|
|
209
|
+
return (value & mask) >>> 0 === (range.base & mask) >>> 0;
|
|
210
|
+
}
|
|
211
|
+
function ipv6InRange(value, range) {
|
|
212
|
+
const shift = BigInt(128 - range.prefix);
|
|
213
|
+
return (value >> shift) === (range.base >> shift);
|
|
214
|
+
}
|
|
215
|
+
function parseIPv6ToBigInt(ip) {
|
|
216
|
+
let normalized = ip.replace(/^\[|\]$/g, '').toLowerCase();
|
|
217
|
+
const zoneIndex = normalized.indexOf('%');
|
|
218
|
+
if (zoneIndex !== -1) {
|
|
219
|
+
normalized = normalized.slice(0, zoneIndex);
|
|
220
|
+
}
|
|
221
|
+
if (normalized.includes('.')) {
|
|
222
|
+
const lastColon = normalized.lastIndexOf(':');
|
|
223
|
+
const ipv4 = normalized.slice(lastColon + 1);
|
|
224
|
+
if (!net.isIPv4(ipv4)) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
const value = ipv4ToNumber(ipv4);
|
|
228
|
+
const high = ((value >>> 16) & 0xffff).toString(16);
|
|
229
|
+
const low = (value & 0xffff).toString(16);
|
|
230
|
+
normalized = `${normalized.slice(0, lastColon)}:${high}:${low}`;
|
|
231
|
+
}
|
|
232
|
+
const compressed = normalized.includes('::');
|
|
233
|
+
const compressionParts = normalized.split('::');
|
|
234
|
+
if (compressionParts.length > 2) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
const left = compressionParts[0]
|
|
238
|
+
? compressionParts[0].split(':').filter((part) => part.length > 0)
|
|
239
|
+
: [];
|
|
240
|
+
const right = compressionParts[1]
|
|
241
|
+
? compressionParts[1].split(':').filter((part) => part.length > 0)
|
|
242
|
+
: [];
|
|
243
|
+
const missing = compressed ? 8 - left.length - right.length : 0;
|
|
244
|
+
if (missing < 0) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
const parts = compressed
|
|
248
|
+
? [...left, ...Array(missing).fill('0'), ...right]
|
|
249
|
+
: left;
|
|
250
|
+
if (parts.length !== 8) {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
let value = 0n;
|
|
254
|
+
for (const part of parts) {
|
|
255
|
+
if (!/^[0-9a-f]{1,4}$/.test(part)) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
value = (value << 16n) + BigInt(parseInt(part, 16));
|
|
259
|
+
}
|
|
260
|
+
return value;
|
|
261
|
+
}
|
|
262
|
+
function isIPv4MappedIPv6(value) {
|
|
263
|
+
return (value >> 32n) === 0xffffn;
|
|
170
264
|
}
|
|
171
265
|
//# sourceMappingURL=webhook.url-validator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook.url-validator.js","sourceRoot":"","sources":["../src/webhook.url-validator.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"webhook.url-validator.js","sourceRoot":"","sources":["../src/webhook.url-validator.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestarc/webhook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.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",
|
|
@@ -35,13 +35,19 @@
|
|
|
35
35
|
"@prisma/client": "^6.0.0",
|
|
36
36
|
"@types/jest": "^30.0.0",
|
|
37
37
|
"@types/node": "^25.6.0",
|
|
38
|
-
"jest": "^30.
|
|
38
|
+
"jest": "^30.4.2",
|
|
39
39
|
"prisma": "^6.19.3",
|
|
40
40
|
"reflect-metadata": "^0.2.0",
|
|
41
41
|
"rxjs": "^7.8.0",
|
|
42
|
-
"ts-jest": "^29.
|
|
42
|
+
"ts-jest": "^29.4.11",
|
|
43
43
|
"typescript": "^5.4.0"
|
|
44
44
|
},
|
|
45
|
+
"overrides": {
|
|
46
|
+
"@babel/core": "^7.29.7",
|
|
47
|
+
"js-yaml": "^4.2.0",
|
|
48
|
+
"multer": "^2.2.0",
|
|
49
|
+
"qs": "^6.15.2"
|
|
50
|
+
},
|
|
45
51
|
"engines": {
|
|
46
52
|
"node": ">=20.0.0"
|
|
47
53
|
},
|
|
@@ -33,15 +33,21 @@ CREATE INDEX IF NOT EXISTS idx_webhook_endpoints_active_events
|
|
|
33
33
|
---
|
|
34
34
|
|
|
35
35
|
CREATE TABLE IF NOT EXISTS webhook_events (
|
|
36
|
-
id
|
|
37
|
-
event_type
|
|
38
|
-
payload
|
|
39
|
-
tenant_id
|
|
40
|
-
|
|
36
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
37
|
+
event_type VARCHAR(255) NOT NULL,
|
|
38
|
+
payload JSONB NOT NULL,
|
|
39
|
+
tenant_id VARCHAR(255),
|
|
40
|
+
idempotency_key VARCHAR(255),
|
|
41
|
+
correlation_id VARCHAR(255),
|
|
42
|
+
payload_purged_at TIMESTAMPTZ,
|
|
43
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
41
44
|
);
|
|
42
45
|
|
|
43
46
|
CREATE INDEX IF NOT EXISTS idx_webhook_events_type_created
|
|
44
47
|
ON webhook_events (event_type, created_at);
|
|
48
|
+
CREATE UNIQUE INDEX IF NOT EXISTS webhook_events_idempotency_key_idx
|
|
49
|
+
ON webhook_events (COALESCE(tenant_id, ''), event_type, idempotency_key)
|
|
50
|
+
WHERE idempotency_key IS NOT NULL;
|
|
45
51
|
|
|
46
52
|
---
|
|
47
53
|
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
-- @nestarc/webhook v0.13.0 - idempotent publish and retention metadata
|
|
2
|
+
-- Adds producer idempotency fields, correlation diagnostics, and payload purge metadata.
|
|
3
|
+
|
|
4
|
+
ALTER TABLE webhook_events
|
|
5
|
+
ADD COLUMN IF NOT EXISTS idempotency_key VARCHAR(255),
|
|
6
|
+
ADD COLUMN IF NOT EXISTS correlation_id VARCHAR(255),
|
|
7
|
+
ADD COLUMN IF NOT EXISTS payload_purged_at TIMESTAMPTZ;
|
|
8
|
+
|
|
9
|
+
CREATE UNIQUE INDEX IF NOT EXISTS webhook_events_idempotency_key_idx
|
|
10
|
+
ON webhook_events (COALESCE(tenant_id, ''), event_type, idempotency_key)
|
|
11
|
+
WHERE idempotency_key IS NOT NULL;
|