diff --git a/src/rfc-8252-http-server.spec.ts b/src/rfc-8252-http-server.spec.ts index 104c9f4..15de661 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'; @@ -485,4 +485,58 @@ describe('RFC8252HTTPServer', function () { }); }); }); + + context('getAllInterfaces', function () { + let dnsLookupStub: sinon.SinonStub; + this.beforeEach(function () { + dnsLookupStub = sinon.stub(); + }); + + it('filters out 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 getAllInterfaces('localhost', dnsLookupStub); + + 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 getAllInterfaces('localhost', dnsLookupStub); + + 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 getAllInterfaces('localhost', dnsLookupStub); + + 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..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; @@ -368,14 +388,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 getAllInterfaces(hostname); this.logger.emit('mongodb-oidc-plugin:local-listen-resolved-hostname', { url: this.redirectUrl.toString(),