From e488ad19b35454a9f665a696f23d58aef18dbaad Mon Sep 17 00:00:00 2001 From: Carlos Valente Date: Sat, 18 Oct 2025 11:01:17 +0200 Subject: [PATCH] fix: ensure well formed URLs for IP address destinations --- .../src/common/utils/__tests__/urlPresets.test.ts | 2 +- .../src/features/sharing/GenerateLinkFormExport.tsx | 10 ++++++++-- .../session/__tests__/session.service.test.ts | 12 ++++++++++++ apps/server/src/api-data/session/session.service.ts | 5 +++++ 4 files changed, 26 insertions(+), 3 deletions(-) diff --git a/apps/client/src/common/utils/__tests__/urlPresets.test.ts b/apps/client/src/common/utils/__tests__/urlPresets.test.ts index 896be4757..52835ed20 100644 --- a/apps/client/src/common/utils/__tests__/urlPresets.test.ts +++ b/apps/client/src/common/utils/__tests__/urlPresets.test.ts @@ -175,7 +175,7 @@ describe('generateUrlPresetOptions', () => { expect(() => generateUrlPresetOptions('test', 'invalid-url')).toThrow(); }); - it('throws on on invalid route', () => { + it('throws on invalid route', () => { expect(() => generateUrlPresetOptions('test', 'www.getontime.no/somethingelse/')).toThrow(); }); }); diff --git a/apps/client/src/features/sharing/GenerateLinkFormExport.tsx b/apps/client/src/features/sharing/GenerateLinkFormExport.tsx index 8b4acc102..21d69a90a 100644 --- a/apps/client/src/features/sharing/GenerateLinkFormExport.tsx +++ b/apps/client/src/features/sharing/GenerateLinkFormExport.tsx @@ -14,12 +14,18 @@ export default function GenerateLinkFormExport({ lockedPath }: GenerateLinkFormE const { data: infoData } = useInfo(); const { data: urlPresetData } = useUrlPresets({ skip: lockedPath === undefined }); + /** + * hostOptions are only used for local networks + * the NIF address is a local IP address: 192.168.x.x or 10.x.x.x + * We need to inject the port and protocol to create a valid URL + * eg: http://192.168.x.x:port + */ const hostOptions = useMemo(() => { return infoData.networkInterfaces.map((nif) => ({ - value: nif.address, + value: `http://${nif.address}:${infoData.serverPort}`, label: `${nif.name} - ${nif.address}`, })); - }, [infoData.networkInterfaces]); + }, [infoData.networkInterfaces, infoData.serverPort]); const pathOptions = useMemo(() => { if (lockedPath) { diff --git a/apps/server/src/api-data/session/__tests__/session.service.test.ts b/apps/server/src/api-data/session/__tests__/session.service.test.ts index 22825e00c..4224e2d55 100644 --- a/apps/server/src/api-data/session/__tests__/session.service.test.ts +++ b/apps/server/src/api-data/session/__tests__/session.service.test.ts @@ -80,6 +80,18 @@ describe('generateAuthenticatedUrl()', () => { }); expect(withAuth.toString()).toBe('http://192.168.10.173:4001/?token=1234'); }); + + it('throws if provided an IP address without protocol', () => { + expect(() => + generateShareUrl('192.168.10.173', '<>', { + lockConfig: false, + lockNav: false, + authenticate: true, + preset: undefined, + hash: '1234', + }), + ).toThrowError('Invalid URL'); + }); }); describe('for ontime-cloud URLs', () => { diff --git a/apps/server/src/api-data/session/session.service.ts b/apps/server/src/api-data/session/session.service.ts index 1d72dad65..a861f8c07 100644 --- a/apps/server/src/api-data/session/session.service.ts +++ b/apps/server/src/api-data/session/session.service.ts @@ -62,6 +62,11 @@ export function generateShareUrl( canonicalPath: string, { authenticate, lockConfig, lockNav, preset, prefix = routerPrefix, hash = hashedPassword }: LinkOptions, ): URL { + /** + * URL constructor will throw if given an IP address without protocol + * for the case of IP addresses, we expect that the base URL provides protocol and port + * eg: http://192.168.10.1:4001 + */ const url = new URL(baseUrl); // companion links point to the root