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 new file mode 100644 index 000000000..82b11e41f --- /dev/null +++ b/apps/server/src/api-data/session/__tests__/session.service.test.ts @@ -0,0 +1,50 @@ +import { generateAuthenticatedUrl } from '../session.service.js'; + +describe('generateAuthenticatedUrl()', () => { + describe('for local IP addresses', () => { + it('generates a link without locking or authentication', () => { + const localhostNotLocked = generateAuthenticatedUrl('http://localhost:3000', 'timer', false, false); + expect(localhostNotLocked.toString()).toBe('http://localhost:3000/timer'); + }); + + it('generates a link with IP locking enabled', () => { + const ipLocked = generateAuthenticatedUrl('http://192.168.10.173:4001', 'timer', true, false); + expect(ipLocked.toString()).toBe('http://192.168.10.173:4001/timer?locked=true'); + }); + + it('generates a link with authentication token and IP locking', () => { + const withAuth = generateAuthenticatedUrl('http://192.168.10.173:4001', 'timer', true, true, undefined, '1234'); + expect(withAuth.toString()).toBe('http://192.168.10.173:4001/timer?token=1234&locked=true'); + }); + }); + + describe('for ontime-cloud URLs', () => { + it('generates a link without locking or authentication', () => { + const cloudNotLocked = generateAuthenticatedUrl( + 'https://cloud.getontime.no/userhash', + 'timer', + false, + false, + 'prefix', + ); + expect(cloudNotLocked.toString()).toBe('https://cloud.getontime.no/prefix/timer'); + }); + + it('generates a link with IP locking enabled', () => { + const ipLocked = generateAuthenticatedUrl('https://cloud.getontime.no/prefix', 'timer', true, false, 'prefix'); + expect(ipLocked.toString()).toBe('https://cloud.getontime.no/prefix/timer?locked=true'); + }); + + it('generates a link with authentication token and IP locking', () => { + const withAuth = generateAuthenticatedUrl( + 'https://cloud.getontime.no/prefix', + 'timer', + true, + true, + 'prefix', + '1234', + ); + expect(withAuth.toString()).toBe('https://cloud.getontime.no/prefix/timer?token=1234&locked=true'); + }); + }); +}); diff --git a/apps/server/src/api-data/session/session.service.ts b/apps/server/src/api-data/session/session.service.ts index 0e7d53ad7..168121fc9 100644 --- a/apps/server/src/api-data/session/session.service.ts +++ b/apps/server/src/api-data/session/session.service.ts @@ -8,7 +8,7 @@ import { getLastLoadedProject } from '../../services/app-state-service/AppStateS import { runtimeService } from '../../services/runtime-service/RuntimeService.js'; import { getNetworkInterfaces } from '../../utils/network.js'; import { getTimezoneLabel } from '../../utils/time.js'; -import { password } from '../../externals.js'; +import { password, routerPrefix } from '../../externals.js'; import { hashPassword } from '../../utils/hash.js'; const startedAt = new Date(); @@ -55,10 +55,19 @@ export const hashedPassword = hasPassword ? hashPassword(password as string) : u /** * Generates a pre-authenticated URL by injecting a token in the URL params */ -export function generateAuthenticatedUrl(baseUrl: string, path: string, lock: boolean, authenticate: boolean): URL { - const url = new URL(path, baseUrl); - if (authenticate && hashedPassword) { - url.searchParams.append('token', hashedPassword); +export function generateAuthenticatedUrl( + baseUrl: string, + path: string, + lock: boolean, + authenticate: boolean, + prefix = routerPrefix, + hash = hashedPassword, +): URL { + const url = new URL(baseUrl); + url.pathname = prefix ? `${prefix}/${path}` : path; + + if (authenticate && hash) { + url.searchParams.append('token', hash); } if (lock) { url.searchParams.append('locked', 'true'); diff --git a/apps/server/src/externals.ts b/apps/server/src/externals.ts index 3cd9014cd..26348a0ea 100644 --- a/apps/server/src/externals.ts +++ b/apps/server/src/externals.ts @@ -16,13 +16,14 @@ export const isDocker = env === 'docker'; export const isProduction = isDocker || (env === 'production' && !isTest); export const isOntimeCloud = Boolean(process.env.IS_CLOUD); export const password = process.env.SESSION_PASSWORD; +export const routerPrefix = process.env.ROUTER_PREFIX; /** * Updates the router prefix in the index.html file * This is only needed in the cloud environment where the client is not at the root segment * ie: https://cloud.getontime.com/client-hash/timer */ -export function updateRouterPrefix(prefix: string | undefined = process.env.ROUTER_PREFIX): string { +export function updateRouterPrefix(prefix: string | undefined = routerPrefix): string { if (!prefix) { return ''; }