From e3c515e617cc2f1d111358a9bb70a1157464a823 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 26 Feb 2025 19:08:30 +0100 Subject: [PATCH 1/4] fix: filter out duplicate dns results --- src/rfc-8252-http-server.spec.ts | 68 ++++++++++++++++++++++++++++++++ src/rfc-8252-http-server.ts | 30 ++++++++++---- 2 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/rfc-8252-http-server.spec.ts b/src/rfc-8252-http-server.spec.ts index 104c9f4..4828b57 100644 --- a/src/rfc-8252-http-server.spec.ts +++ b/src/rfc-8252-http-server.spec.ts @@ -9,6 +9,7 @@ import type { SinonSandbox } from 'sinon'; import sinon from 'sinon'; import { promisify } from 'util'; import { randomBytes } from 'crypto'; +import { promises as dns } from 'dns'; // node-fetch@3 is ESM-only... // eslint-disable-next-line @typescript-eslint/consistent-type-imports @@ -485,4 +486,71 @@ describe('RFC8252HTTPServer', function () { }); }); }); + + context('with dns duplicates', function () { + const originalDnsLookup = dns.lookup; + let dnsLookupStub: sinon.SinonStub; + + this.beforeEach(function () { + dnsLookupStub = sinon.stub(); + dns.lookup = dnsLookupStub; + }); + + this.afterEach(function () { + dns.lookup = originalDnsLookup; + }); + + it('only filters exact duplicates', async function () { + dnsLookupStub.resolves([ + { address: '127.0.0.1', family: 4 }, + { address: '127.0.0.1', family: 4 }, + { address: '[::1]', family: 6 }, + { address: '[::1]', family: 6 }, + ]); + + const interfaces = await RFC8252HTTPServer['_getAllInterfaces'].call( + 'localhost' + ); + + expect(interfaces).to.have.lengthOf(2); + expect(interfaces[0].address).to.equal('127.0.0.1'); + expect(interfaces[1].address).to.equal('[::1]'); + expect(interfaces[0].family).to.equal(4); + expect(interfaces[1].family).to.equal(6); + }); + + it('keeps same addresses, different family', async function () { + dnsLookupStub.resolves([ + { address: '127.0.0.1', family: 4 }, + { address: '127.0.0.1', family: 6 }, + ]); + + const interfaces = await RFC8252HTTPServer['_getAllInterfaces'].call( + 'localhost' + ); + + expect(interfaces).to.have.lengthOf(2); + expect(interfaces[0].address).to.equal('127.0.0.1'); + expect(interfaces[1].address).to.equal('127.0.0.1'); + expect(interfaces[0].family).to.equal(4); + expect(interfaces[1].family).to.equal(6); + }); + + it('keeps same familes, different address', async function () { + dnsLookupStub.resolves([ + { address: '127.0.0.1', family: 4 }, + { address: '192.168.1.15', family: 4 }, + ]); + + const interfaces = await RFC8252HTTPServer['_getAllInterfaces'].call( + 'localhost' + ); + + expect(interfaces).to.have.lengthOf(2); + expect(interfaces[0].address).to.equal('127.0.0.1'); + expect(interfaces[1].address).to.equal('192.168.1.15'); + expect(interfaces[0].family).to.equal(4); + expect(interfaces[1].family).to.equal(4); + }); + }); }); diff --git a/src/rfc-8252-http-server.ts b/src/rfc-8252-http-server.ts index d937367..11a9e57 100644 --- a/src/rfc-8252-http-server.ts +++ b/src/rfc-8252-http-server.ts @@ -272,6 +272,25 @@ export class RFC8252HTTPServer { }); }; + private static async _getAllInterfaces( + hostname: string + ): Promise<{ address: string; family: number }[]> { + const dnsResults = await dns.lookup(hostname, { + all: true, + hints: ADDRCONFIG, + }); + + return dnsResults + .filter( + (dns, index, arr) => + arr.findIndex( + (otherDns) => + dns.address === otherDns.address && dns.family === otherDns.family + ) === index + ) + .map(({ address, family }) => ({ address, family })); + } + /** * Add a redirect from a local URL served on the server to an external URL. */ @@ -368,14 +387,11 @@ export class RFC8252HTTPServer { // to do what Node.js does by default when only a host is provided, // namely listening on all interfaces. let hostname = this.redirectUrl.hostname; - if (hostname.startsWith('[') && hostname.endsWith(']')) + if (hostname.startsWith('[') && hostname.endsWith(']')) { hostname = hostname.slice(1, -1); - const dnsResults = ( - await dns.lookup(hostname, { - all: true, - hints: ADDRCONFIG, - }) - ).map(({ address, family }) => ({ address, family })); + } + + const dnsResults = await RFC8252HTTPServer._getAllInterfaces(hostname); this.logger.emit('mongodb-oidc-plugin:local-listen-resolved-hostname', { url: this.redirectUrl.toString(), From 0204e18de4d24a6eebe47aa84c300c8ce1393593 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Thu, 27 Feb 2025 10:52:20 +0100 Subject: [PATCH 2/4] address comments --- src/rfc-8252-http-server.spec.ts | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/rfc-8252-http-server.spec.ts b/src/rfc-8252-http-server.spec.ts index 4828b57..b09a298 100644 --- a/src/rfc-8252-http-server.spec.ts +++ b/src/rfc-8252-http-server.spec.ts @@ -488,16 +488,15 @@ describe('RFC8252HTTPServer', function () { }); context('with dns duplicates', function () { - const originalDnsLookup = dns.lookup; let dnsLookupStub: sinon.SinonStub; + const _getAllInterfaces = RFC8252HTTPServer['_getAllInterfaces']; this.beforeEach(function () { - dnsLookupStub = sinon.stub(); - dns.lookup = dnsLookupStub; + dnsLookupStub = sinon.stub(dns, 'lookup'); }); this.afterEach(function () { - dns.lookup = originalDnsLookup; + sinon.restore(); }); it('only filters exact duplicates', async function () { @@ -508,9 +507,7 @@ describe('RFC8252HTTPServer', function () { { address: '[::1]', family: 6 }, ]); - const interfaces = await RFC8252HTTPServer['_getAllInterfaces'].call( - 'localhost' - ); + const interfaces = await _getAllInterfaces('localhost'); expect(interfaces).to.have.lengthOf(2); expect(interfaces[0].address).to.equal('127.0.0.1'); @@ -525,9 +522,7 @@ describe('RFC8252HTTPServer', function () { { address: '127.0.0.1', family: 6 }, ]); - const interfaces = await RFC8252HTTPServer['_getAllInterfaces'].call( - 'localhost' - ); + const interfaces = await _getAllInterfaces('localhost'); expect(interfaces).to.have.lengthOf(2); expect(interfaces[0].address).to.equal('127.0.0.1'); @@ -542,9 +537,7 @@ describe('RFC8252HTTPServer', function () { { address: '192.168.1.15', family: 4 }, ]); - const interfaces = await RFC8252HTTPServer['_getAllInterfaces'].call( - 'localhost' - ); + const interfaces = await _getAllInterfaces('localhost'); expect(interfaces).to.have.lengthOf(2); expect(interfaces[0].address).to.equal('127.0.0.1'); From 0c5bd242f28305661ce9c42df88ba23e3b2af5f8 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Thu, 27 Feb 2025 13:44:28 +0100 Subject: [PATCH 3/4] Rearrange some code --- src/rfc-8252-http-server.spec.ts | 20 ++++++---------- src/rfc-8252-http-server.ts | 41 ++++++++++++++++---------------- 2 files changed, 28 insertions(+), 33 deletions(-) diff --git a/src/rfc-8252-http-server.spec.ts b/src/rfc-8252-http-server.spec.ts index b09a298..a784cc0 100644 --- a/src/rfc-8252-http-server.spec.ts +++ b/src/rfc-8252-http-server.spec.ts @@ -1,4 +1,4 @@ -import { RFC8252HTTPServer } from './rfc-8252-http-server'; +import { getAllInterfaces, RFC8252HTTPServer } from './rfc-8252-http-server'; import { expect } from 'chai'; import type { Server as HTTPServer } from 'http'; import { createServer as createHTTPServer } from 'http'; @@ -487,19 +487,13 @@ describe('RFC8252HTTPServer', function () { }); }); - context('with dns duplicates', function () { + context('getAllInterfaces', function () { let dnsLookupStub: sinon.SinonStub; - const _getAllInterfaces = RFC8252HTTPServer['_getAllInterfaces']; - this.beforeEach(function () { - dnsLookupStub = sinon.stub(dns, 'lookup'); - }); - - this.afterEach(function () { - sinon.restore(); + dnsLookupStub = sinon.stub(); }); - it('only filters exact duplicates', async function () { + it('filters out exact duplicates', async function () { dnsLookupStub.resolves([ { address: '127.0.0.1', family: 4 }, { address: '127.0.0.1', family: 4 }, @@ -507,7 +501,7 @@ describe('RFC8252HTTPServer', function () { { address: '[::1]', family: 6 }, ]); - const interfaces = await _getAllInterfaces('localhost'); + const interfaces = await getAllInterfaces('localhost', dnsLookupStub); expect(interfaces).to.have.lengthOf(2); expect(interfaces[0].address).to.equal('127.0.0.1'); @@ -522,7 +516,7 @@ describe('RFC8252HTTPServer', function () { { address: '127.0.0.1', family: 6 }, ]); - const interfaces = await _getAllInterfaces('localhost'); + const interfaces = await getAllInterfaces('localhost', dnsLookupStub); expect(interfaces).to.have.lengthOf(2); expect(interfaces[0].address).to.equal('127.0.0.1'); @@ -537,7 +531,7 @@ describe('RFC8252HTTPServer', function () { { address: '192.168.1.15', family: 4 }, ]); - const interfaces = await _getAllInterfaces('localhost'); + const interfaces = await getAllInterfaces('localhost', dnsLookupStub); expect(interfaces).to.have.lengthOf(2); expect(interfaces[0].address).to.equal('127.0.0.1'); diff --git a/src/rfc-8252-http-server.ts b/src/rfc-8252-http-server.ts index 11a9e57..b20459d 100644 --- a/src/rfc-8252-http-server.ts +++ b/src/rfc-8252-http-server.ts @@ -29,6 +29,26 @@ export interface RFC8252HTTPServerOptions { redirectServerRequestHandler?: RedirectServerRequestHandler; } +export async function getAllInterfaces( + hostname: string, + lookup: typeof dns.lookup = dns.lookup +): Promise<{ address: string; family: number }[]> { + const dnsResults = await lookup(hostname, { + all: true, + hints: ADDRCONFIG, + }); + + return dnsResults + .filter( + (dns, index, arr) => + arr.findIndex( + (otherDns) => + dns.address === otherDns.address && dns.family === otherDns.family + ) === index + ) + .map(({ address, family }) => ({ address, family })); +} + /** @internal */ export class RFC8252HTTPServer { private readonly redirectUrl: URL; @@ -272,25 +292,6 @@ export class RFC8252HTTPServer { }); }; - private static async _getAllInterfaces( - hostname: string - ): Promise<{ address: string; family: number }[]> { - const dnsResults = await dns.lookup(hostname, { - all: true, - hints: ADDRCONFIG, - }); - - return dnsResults - .filter( - (dns, index, arr) => - arr.findIndex( - (otherDns) => - dns.address === otherDns.address && dns.family === otherDns.family - ) === index - ) - .map(({ address, family }) => ({ address, family })); - } - /** * Add a redirect from a local URL served on the server to an external URL. */ @@ -391,7 +392,7 @@ export class RFC8252HTTPServer { hostname = hostname.slice(1, -1); } - const dnsResults = await RFC8252HTTPServer._getAllInterfaces(hostname); + const dnsResults = await getAllInterfaces(hostname); this.logger.emit('mongodb-oidc-plugin:local-listen-resolved-hostname', { url: this.redirectUrl.toString(), From 9569f7591e2fa5dae30ce59e3158279f510edd57 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Thu, 27 Feb 2025 13:50:58 +0100 Subject: [PATCH 4/4] fix lint --- src/rfc-8252-http-server.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rfc-8252-http-server.spec.ts b/src/rfc-8252-http-server.spec.ts index a784cc0..15de661 100644 --- a/src/rfc-8252-http-server.spec.ts +++ b/src/rfc-8252-http-server.spec.ts @@ -9,7 +9,6 @@ import type { SinonSandbox } from 'sinon'; import sinon from 'sinon'; import { promisify } from 'util'; import { randomBytes } from 'crypto'; -import { promises as dns } from 'dns'; // node-fetch@3 is ESM-only... // eslint-disable-next-line @typescript-eslint/consistent-type-imports