'use strict'; const fs = require('fs'); const path = require('path'); const vm = require('vm'); const test = require('node:test'); const assert = require('node:assert/strict'); const source = fs.readFileSync(path.join(__dirname, '..', 'js', 'auth-gate.js'), 'utf8'); const totpSource = fs.readFileSync(path.join(__dirname, '..', 'js', 'totp-auth.js'), 'utf8'); function buildHandoffTarget(returnUrl, token, tld = '.sami') { const start = totpSource.indexOf(' function buildSsoHandoffTarget'); const end = totpSource.indexOf('\n\n // Setup digit input UX', start); assert.notEqual(start, -1, 'handoff builder must exist'); assert.notEqual(end, -1, 'handoff builder boundary must exist'); const functionSource = totpSource.slice(start, end); const context = { URL, SITE: { tld }, window: { location: { origin: 'https://status.sami' } }, }; const sandbox = { ...context, input: returnUrl, token, result: undefined }; vm.runInNewContext(`${functionSource}\nresult = buildSsoHandoffTarget(input, token);`, sandbox); return sandbox.result; } function capturedRedirect(returnUrl, tld = '.sami') { const stored = new Map(); const query = new URLSearchParams({ auth: 'required', return: returnUrl }); const location = { origin: 'https://status.sami', pathname: '/', search: `?${query.toString()}`, reload() {}, }; const context = { URL, URLSearchParams, SITE: { tld }, sessionStorage: { setItem(key, value) { stored.set(key, value); }, }, document: { getElementById() { return null; } }, setTimeout() {}, console, window: { location, history: { replaceState() {} }, }, }; context.window.window = context.window; vm.runInNewContext(source, context, { filename: 'auth-gate.js' }); return stored.get('totp_redirect'); } test('preserves a return URL on another host under the configured private TLD', () => { assert.equal(capturedRedirect('https://plex.sami/web/'), 'https://plex.sami/web/'); }); test('preserves a same-origin return URL', () => { assert.equal(capturedRedirect('https://status.sami/settings'), 'https://status.sami/settings'); }); test('rejects lookalike domains, plaintext cross-host URLs, and non-web schemes', () => { assert.equal(capturedRedirect('https://plex.sami.evil.example/'), undefined); assert.equal(capturedRedirect('http://plex.sami/'), undefined); assert.equal(capturedRedirect('javascript:alert(1)'), undefined); }); test('accepts relative same-origin paths and protocol-relative HTTPS private hosts', () => { assert.equal(capturedRedirect('/settings'), '/settings'); assert.equal(capturedRedirect('//plex.sami/web/'), '//plex.sami/web/'); }); test('normalizes a configured TLD without a leading dot', () => { assert.equal(capturedRedirect('https://plex.sami/web/', 'sami'), 'https://plex.sami/web/'); }); test('builds the generic cross-host SSO landing URL and preserves the final path', () => { assert.equal( buildHandoffTarget('https://router.sami/config?tab=network#dns', 'one-time'), 'https://router.sami/dashcaddy-sso?token=one-time&return=%2Fconfig%3Ftab%3Dnetwork%23dns', ); }); test('does not create cross-host handoffs for plaintext or lookalike destinations', () => { assert.equal(buildHandoffTarget('http://router.sami/', 'one-time'), null); assert.equal(buildHandoffTarget('https://router.sami.evil.example/', 'one-time'), null); }); test('same-origin and tokenless destinations keep their direct URL', () => { assert.equal(buildHandoffTarget('/settings', 'one-time'), 'https://status.sami/settings'); assert.equal(buildHandoffTarget('https://router.sami/config', ''), 'https://router.sami/config'); });