Files
stack/packages/webui/tests/org-lan-bind-browser.test.mjs
jason.woltje 665a5148d4 Open the dev owner Console to allowed LAN names
mosaic console start --dev-local-owner --dev-insecure-bind ADDR
--dev-allow-host NAME[,NAME...] binds 0.0.0.0 or an IPv4 address and
answers only NAME:<bound port> plus the loopback names. Both flags are
refused without the dev owner, the bind without an allowed name, and a
specific ADDR that is not itself a name. Start and console status print
the no-sign-in warning; the state file records bind and allowHosts.

serve.mjs takes devAllowHosts only with the dev owner; SSO, preview and
tester listeners stay loopback. Writes need Origin http://<same Host>.
Chromium sends no Fetch Metadata from a LAN http origin, so for an
allowed name a write with that exact Origin and no Sec-Fetch-Site counts
as same-origin; an explicit header is still judged by the routes.
chat.js builds its operation keys without crypto.randomUUID, which an
insecure context lacks.
2026-11-08 21:13:53 -06:00

45 lines
3.1 KiB
JavaScript

// org-lan-bind: a browser on another machine opens the dev owner Console by an allowed LAN name
// (http://lan.test:PORT/, mapped to 127.0.0.1 for the test) and creates a project. That origin
// is not a secure context, so Chromium sends no Fetch Metadata and has no crypto.randomUUID.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { startServer } from '../src/serve.mjs';
import { createDevOwner } from '../src/dev-owner.mjs';
import { browser } from './browser.mjs';
import { close } from './fixture.mjs';
const ID = '20000000-0000-4000-8000-000000000002';
test('an allowed LAN name loads the org page and creates a project', { timeout: 45000 }, async t => {
const projects = [], created = [];
const organizations = {
list: async () => [{ id: 'alpha', label: 'alpha', busHealthy: true }],
resolve: async id => id === 'alpha' ? {
busHealthy: true, send: async () => ({}),
reads: { business: async () => ({ business: { id, projects: ['work'] } }), busStatus: async () => ({ live: true }) },
bus: { inbox: async () => [], agents: async () => [], messages: async () => ({ messages: [] }), tasks: async () => [] },
chat: {
projects: async () => projects, threads: async () => [],
createProject: async args => { created.push(args); const row = { id: ID, ...args }; projects.push(row); return row; },
},
} : null,
};
const server = await startServer({ host: '0.0.0.0', port: 0, devAllowHosts: ['lan.test'], sso: createDevOwner(), organizations, send: async () => ({}), board: 'http://127.0.0.1:9', log: () => {} });
t.after(() => close(server));
const port = server.address().port;
const b = await browser({ machine: false, args: ['--host-resolver-rules=MAP lan.test 127.0.0.1'] }); t.after(() => b.close());
const wait = expression => b.evaluate(`(async()=>{for(let i=0;i<300;i++){if(${expression})return;await new Promise(r=>setTimeout(r,30))}throw Error(${JSON.stringify(expression)})})()`);
const input = (selector, value) => b.evaluate(`(()=>{const e=document.querySelector(${JSON.stringify(selector)});e.value=${JSON.stringify(value)};e.dispatchEvent(new Event('input',{bubbles:true}));e.dispatchEvent(new Event('change',{bubbles:true}));})()`);
const click = selector => b.evaluate(`document.querySelector(${JSON.stringify(selector)}).click()`);
await b.navigate(`http://lan.test:${port}/#/orgs/alpha/projects`);
assert.deepEqual(await b.evaluate('({ host: location.host, secure: isSecureContext, uuid: typeof crypto.randomUUID })'), { host: `lan.test:${port}`, secure: false, uuid: 'undefined' });
await wait('window.MosaicOrg?.id === "alpha" && !!document.querySelector("#chat-repo option[value=work]")');
await click('.project-create summary');
await input('#chat-project-name', 'Work'); await input('#chat-repo', 'work');
await click('#chat-project-form button[type=submit]');
await wait('/\\/projects\\/[0-9a-f-]{36}/i.test(location.hash)');
assert.equal(await b.evaluate('location.hash'), `#/orgs/alpha/projects/${ID}`);
assert.deepEqual(created, [{ name: 'Work', repo: 'work' }]);
assert.equal(await b.evaluate('location.host'), `lan.test:${port}`);
});