mirror of
https://github.com/freedbygrace/DynamoDNS.git
synced 2026-07-26 11:38:13 +00:00
Add webhook delivery functionality with retry mechanism and signature verification.
Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/b326a816-600f-4a2f-a1f3-248f0b7ffba1.jpg
This commit is contained in:
Generated
+91
@@ -55,6 +55,7 @@
|
||||
"input-otp": "^1.2.4",
|
||||
"lucide-react": "^0.453.0",
|
||||
"memorystore": "^1.6.7",
|
||||
"node-fetch": "^3.3.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"react": "^18.3.1",
|
||||
@@ -4152,6 +4153,15 @@
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz",
|
||||
@@ -5168,6 +5178,29 @@
|
||||
"reusify": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
@@ -5229,6 +5262,18 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -5945,6 +5990,43 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/node-gyp-build": {
|
||||
"version": "4.8.3",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.3.tgz",
|
||||
@@ -8622,6 +8704,15 @@
|
||||
"@esbuild/win32-x64": "0.21.5"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"input-otp": "^1.2.4",
|
||||
"lucide-react": "^0.453.0",
|
||||
"memorystore": "^1.6.7",
|
||||
"node-fetch": "^3.3.2",
|
||||
"passport": "^0.7.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"react": "^18.3.1",
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
import { createHmac } from 'crypto';
|
||||
import fetch from 'node-fetch';
|
||||
import { Webhook } from '@shared/schema';
|
||||
|
||||
// Generates a signature for a webhook payload using the webhook's secret
|
||||
export function generateSignature(payload: any, secret: string): string {
|
||||
if (!secret) return '';
|
||||
|
||||
const hmac = createHmac('sha256', secret);
|
||||
hmac.update(JSON.stringify(payload));
|
||||
return hmac.digest('hex');
|
||||
}
|
||||
|
||||
// Interface for webhook delivery results
|
||||
export interface WebhookDeliveryResult {
|
||||
success: boolean;
|
||||
statusCode?: number;
|
||||
message: string;
|
||||
timestamp: Date;
|
||||
responseBody?: string;
|
||||
retryCount?: number;
|
||||
}
|
||||
|
||||
// Interface for webhook delivery logs
|
||||
export interface WebhookDeliveryLog {
|
||||
id: string;
|
||||
webhookId: string;
|
||||
event: string;
|
||||
payload: any;
|
||||
signature: string;
|
||||
result: WebhookDeliveryResult;
|
||||
createdAt: Date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delivers a webhook payload to the configured URL with retry capability
|
||||
*/
|
||||
export async function deliverWebhook(
|
||||
webhook: Webhook,
|
||||
payload: any,
|
||||
maxRetries = 3,
|
||||
initialRetryDelay = 2000
|
||||
): Promise<WebhookDeliveryResult> {
|
||||
let retryCount = 0;
|
||||
let lastError: Error | null = null;
|
||||
|
||||
// Generate HMAC signature if webhook has a secret
|
||||
const signature = webhook.secret ? generateSignature(payload, webhook.secret) : '';
|
||||
|
||||
while (retryCount <= maxRetries) {
|
||||
try {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'DNS-Manager-Webhook/1.0',
|
||||
'X-Webhook-ID': webhook.id,
|
||||
'X-Webhook-Event': payload.event
|
||||
};
|
||||
|
||||
// Add signature header if available
|
||||
if (signature) {
|
||||
headers['X-Webhook-Signature'] = signature;
|
||||
}
|
||||
|
||||
const response = await fetch(webhook.url, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(payload),
|
||||
timeout: 10000 // 10 second timeout
|
||||
});
|
||||
|
||||
const responseText = await response.text();
|
||||
|
||||
if (response.ok) {
|
||||
return {
|
||||
success: true,
|
||||
statusCode: response.status,
|
||||
message: 'Webhook delivered successfully',
|
||||
timestamp: new Date(),
|
||||
responseBody: responseText,
|
||||
retryCount
|
||||
};
|
||||
} else {
|
||||
lastError = new Error(`HTTP error ${response.status}: ${responseText}`);
|
||||
|
||||
// If we got a response but it's an error, check if it's a 5xx error that warrants retrying
|
||||
if (response.status < 500 || retryCount >= maxRetries) {
|
||||
break; // Don't retry client errors or if we've exhausted retries
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error as Error;
|
||||
}
|
||||
|
||||
// Exponential backoff for retries
|
||||
const delay = initialRetryDelay * Math.pow(2, retryCount);
|
||||
await new Promise(resolve => setTimeout(resolve, delay));
|
||||
retryCount++;
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
statusCode: lastError && 'statusCode' in lastError ? (lastError as any).statusCode : undefined,
|
||||
message: lastError ? lastError.message : 'Unknown error delivering webhook',
|
||||
timestamp: new Date(),
|
||||
retryCount
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a verification helper for clients to validate webhook signatures
|
||||
*/
|
||||
export function createSignatureVerifier(secret: string) {
|
||||
return {
|
||||
verify: (signature: string, payload: any): boolean => {
|
||||
return signature === generateSignature(payload, secret);
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user