fix: honor Node all-address DNS lookup for HTTP webhooks
CSS quality / css-quality (push) Has been cancelled
CSS quality / css-quality (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
import { createServer, get, type RequestOptions } from 'node:http';
|
||||
import type { AddressInfo } from 'node:net';
|
||||
import { pinnedWebhookLookup } from './open-api.protocol';
|
||||
|
||||
describe('webhook pinned DNS lookup', () => {
|
||||
it('supports single-address and all-address callbacks without resolving another address', () => {
|
||||
const callback = jest.fn();
|
||||
const lookup = pinnedWebhookLookup('203.0.113.10', 4);
|
||||
lookup('ignored.example', {}, callback);
|
||||
expect(callback).toHaveBeenLastCalledWith(null, '203.0.113.10', 4);
|
||||
lookup('ignored.example', { all: true }, callback);
|
||||
expect(callback).toHaveBeenLastCalledWith(null, [{ address: '203.0.113.10', family: 4 }]);
|
||||
pinnedWebhookLookup('2001:db8::10', 6)('ignored.example', { all: true }, callback);
|
||||
expect(callback).toHaveBeenLastCalledWith(null, [{ address: '2001:db8::10', family: 6 }]);
|
||||
});
|
||||
|
||||
it('delivers through the real Node HTTP connector when automatic family selection requests all addresses', async () => {
|
||||
const server = createServer((_request, response) => response.end('received'));
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
try {
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
const options: RequestOptions & { autoSelectFamily: boolean } = {
|
||||
lookup: pinnedWebhookLookup('127.0.0.1', 4),
|
||||
autoSelectFamily: true,
|
||||
agent: false,
|
||||
};
|
||||
const body = await new Promise<string>((resolve, reject) => {
|
||||
const request = get(`http://webhook.invalid:${port}/`, options, (response) => {
|
||||
let received = '';
|
||||
response.setEncoding('utf8');
|
||||
response.on('data', (chunk: string) => (received += chunk));
|
||||
response.on('end', () => resolve(received));
|
||||
});
|
||||
request.setTimeout(3000, () => request.destroy(new Error('test HTTP timeout')));
|
||||
request.on('error', reject);
|
||||
});
|
||||
expect(body).toBe('received');
|
||||
} finally {
|
||||
await new Promise<void>((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,14 @@
|
||||
import { createHash, createHmac } from 'node:crypto';
|
||||
import { HttpException } from '@nestjs/common';
|
||||
import type { LookupFunction } from 'node:net';
|
||||
|
||||
/** Keep the validated address pinned while honoring Node's all-address lookup contract. */
|
||||
export function pinnedWebhookLookup(address: string, family: number): LookupFunction {
|
||||
return (_hostname, options, callback) => {
|
||||
if (options.all) callback(null, [{ address, family }]);
|
||||
else callback(null, address, family);
|
||||
};
|
||||
}
|
||||
|
||||
/** v1 compatibility: an absent parsed body hashes as {}, never try alternate hashes. */
|
||||
export function openApiBodyHash(rawBody: Buffer | undefined, body: unknown) {
|
||||
|
||||
@@ -26,7 +26,7 @@ import { SendChainService } from '../send-chain/send-chain.service';
|
||||
import { decryptSecret, encryptSecret } from './open-api.crypto';
|
||||
import type { OpenApiAuthContext } from './open-api.types';
|
||||
import { ProtocolLogsService } from '../protocol-logs/protocol-logs.service';
|
||||
import { publicOpenApiFailure, webhookJobId } from './open-api.protocol';
|
||||
import { pinnedWebhookLookup, publicOpenApiFailure, webhookJobId } from './open-api.protocol';
|
||||
import { automaticDeliveryMode } from './delivery-mode';
|
||||
|
||||
export const OPEN_API_WEBHOOK_TRANSPORT = Symbol('open-api-webhook-transport');
|
||||
@@ -934,7 +934,7 @@ async function postWebhook(
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { ...headers, 'content-length': String(Buffer.byteLength(body)) },
|
||||
lookup: (_hostname, _options, callback) => callback(null, target.address, target.family),
|
||||
lookup: pinnedWebhookLookup(target.address, target.family),
|
||||
},
|
||||
(response) => {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
Reference in New Issue
Block a user