feat: release passwordless hierarchical APT v1.2.5
This commit is contained in:
@@ -11,7 +11,7 @@ const ROOT = path.join(__dirname, '..');
|
||||
const read = (name) => fs.readFileSync(path.join(ROOT, name), 'utf8');
|
||||
const readJson = (name) => JSON.parse(read(name));
|
||||
|
||||
function writeKit(root, { version = '1.2.0', legacy = false } = {}) {
|
||||
function writeKit(root, { version = '1.2.5', legacy = false } = {}) {
|
||||
const extension = path.join(root, 'chrome-extension');
|
||||
fs.mkdirSync(extension, { recursive: true });
|
||||
fs.writeFileSync(path.join(root, 'AltaCameraProxy.exe'), 'synthetic executable');
|
||||
@@ -31,7 +31,7 @@ test('application and extension versions and supported dependencies stay coordin
|
||||
const lock = readJson('package-lock.json');
|
||||
const manifest = readJson('chrome-extension/manifest.json');
|
||||
|
||||
assert.equal(pkg.version, '1.2.0');
|
||||
assert.equal(pkg.version, '1.2.5');
|
||||
assert.equal(manifest.version, pkg.version);
|
||||
assert.equal(lock.version, pkg.version);
|
||||
assert.equal(lock.packages[''].version, pkg.version);
|
||||
|
||||
@@ -6,6 +6,7 @@ const {
|
||||
MAX_PROJECTED_PAYLOAD_BYTES,
|
||||
projectDeviceHierarchy,
|
||||
} = require('../src/device-projection');
|
||||
const { buildDeviceTree } = require('../device-tree');
|
||||
|
||||
function uuid(index) {
|
||||
return `00000000-0000-4000-8000-${index.toString(16).padStart(12, '0')}`;
|
||||
@@ -18,7 +19,7 @@ function rawDevice(index, overrides = {}) {
|
||||
type: 'camera',
|
||||
model: 'Synthetic',
|
||||
address: `10.0.${Math.floor(index / 255)}.${index % 255}`,
|
||||
server_group_id: 'site-1',
|
||||
server_group_id: 'server-group-1',
|
||||
device_group_id: 'group-1',
|
||||
capabilities: { localStorage: index % 2 === 0, secretCapability: 'drop-me' },
|
||||
live: { display_status: 'online', private: 'drop-me' },
|
||||
@@ -29,25 +30,68 @@ function rawDevice(index, overrides = {}) {
|
||||
|
||||
test('projects only allowlisted fields and honestly diagnoses ineligible devices', () => {
|
||||
const payload = projectDeviceHierarchy({
|
||||
devices: [rawDevice(1), rawDevice(2, { guid: 'not-a-uuid' })],
|
||||
devices: [
|
||||
rawDevice(1),
|
||||
rawDevice(2, { guid: 'not-a-uuid' }),
|
||||
rawDevice(4),
|
||||
],
|
||||
sites: [{ id: 'site-1', name: 'HQ', pending_deletion_start: null, secret: 'drop-me' }],
|
||||
groups: [{ id: 'group-1', name: 'Lobby', parent_id: null, pending_deletion_start: null, secret: 'drop-me' }],
|
||||
});
|
||||
|
||||
assert.deepEqual(payload.devices, [{
|
||||
id: uuid(1), name: 'Camera 1', type: 'camera', model: 'Synthetic', address: '10.0.0.1',
|
||||
siteId: 'site-1', deviceGroupId: 'group-1', displayStatus: 'online', localStorage: false,
|
||||
siteId: null, deviceGroupId: 'group-1', displayStatus: 'online', localStorage: false,
|
||||
}]);
|
||||
assert.deepEqual(payload.sites, [{ id: 'site-1', name: 'HQ', pendingDeletionStart: null }]);
|
||||
assert.deepEqual(payload.groups, [{ id: 'group-1', name: 'Lobby', parentId: null, pendingDeletionStart: null }]);
|
||||
assert.deepEqual(payload.diagnostics.devices, { received: 2, eligible: 1, invalid: 1 });
|
||||
assert.deepEqual(payload.diagnostics.devices, {
|
||||
received: 3, eligible: 1, invalid: 1, cloudNativeExcluded: 1,
|
||||
});
|
||||
assert.doesNotMatch(JSON.stringify(payload), /drop-me|cookie|secretCapability|private/);
|
||||
});
|
||||
|
||||
test('infers camera sites from device-group parents instead of server groups', () => {
|
||||
const payload = projectDeviceHierarchy({
|
||||
devices: [rawDevice(3, {
|
||||
name: 'Front Door',
|
||||
server_group_id: 'cloud-connector-server-group',
|
||||
device_group_id: 'entrances',
|
||||
})],
|
||||
sites: [{ id: 'test-site', name: 'Test Site' }],
|
||||
groups: [{ id: 'entrances', name: 'Entrances', parent_id: 'test-site' }],
|
||||
});
|
||||
const model = buildDeviceTree(payload);
|
||||
const camera = [...model.cameraByKey.values()][0];
|
||||
|
||||
assert.equal(payload.devices[0].siteId, null);
|
||||
assert.equal(camera.site.canonicalId, 'test-site');
|
||||
assert.equal(camera.group.canonicalId, 'entrances');
|
||||
assert.equal(camera.hierarchyStatus, 'inferred-site');
|
||||
assert.deepEqual(model.diagnostics, []);
|
||||
});
|
||||
|
||||
test('excludes cloud-native cameras while retaining Cloud Connector and unknown-storage devices', () => {
|
||||
const payload = projectDeviceHierarchy({
|
||||
devices: [
|
||||
rawDevice(10, { name: 'Cloud Native', capabilities: { localStorage: true } }),
|
||||
rawDevice(11, { name: 'Cloud Connector', capabilities: { localStorage: false } }),
|
||||
rawDevice(12, { name: 'Unknown Storage', capabilities: {} }),
|
||||
],
|
||||
});
|
||||
|
||||
assert.deepEqual(payload.devices.map((device) => device.name), ['Cloud Connector', 'Unknown Storage']);
|
||||
assert.deepEqual(payload.diagnostics.devices, {
|
||||
received: 3, eligible: 2, invalid: 0, cloudNativeExcluded: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('projects synthetic 1,500 and 10,000 camera deployments below the 8 MiB IPC cap', () => {
|
||||
for (const count of [1_500, 10_000]) {
|
||||
const payload = projectDeviceHierarchy({
|
||||
devices: Array.from({ length: count }, (_, index) => rawDevice(index)),
|
||||
devices: Array.from({ length: count }, (_, index) => rawDevice(index, {
|
||||
capabilities: { localStorage: false },
|
||||
})),
|
||||
sites: [],
|
||||
groups: [],
|
||||
});
|
||||
@@ -80,6 +124,7 @@ test('repairs lone UTF-16 surrogates without changing valid Unicode or hiding ca
|
||||
address: 'Hall \ud800\udc00 / \udc00',
|
||||
server_group_id: 'site-\ud800',
|
||||
device_group_id: 'group-\udc00',
|
||||
capabilities: { localStorage: false },
|
||||
})],
|
||||
sites: [{ id: 'site-\ud800', name: 'Valid \ud83d\udcf7 \udc00', pending_deletion_start: '\ud800' }],
|
||||
groups: [{ id: 'group-\udc00', name: 'Group \ud83d\udcf7 \ud800', parent_id: 'site-\ud800' }],
|
||||
@@ -90,7 +135,7 @@ test('repairs lone UTF-16 surrogates without changing valid Unicode or hiding ca
|
||||
assert.equal(payload.devices[0].name, 'Front \ufffd camera');
|
||||
assert.equal(payload.devices[0].model, 'Model \ufffd');
|
||||
assert.equal(payload.devices[0].address, 'Hall \ud800\udc00 / \ufffd');
|
||||
assert.equal(payload.devices[0].siteId, 'site-\ufffd');
|
||||
assert.equal(payload.devices[0].siteId, null);
|
||||
assert.equal(payload.devices[0].deviceGroupId, 'group-\ufffd');
|
||||
assert.deepEqual(payload.sites, [{ id: 'site-\ufffd', name: 'Valid \ud83d\udcf7 \ufffd', pendingDeletionStart: '\ufffd' }]);
|
||||
assert.deepEqual(payload.groups, [{ id: 'group-\ufffd', name: 'Group \ud83d\udcf7 \ufffd', parentId: 'site-\ufffd', pendingDeletionStart: null }]);
|
||||
@@ -101,6 +146,7 @@ test('repairs lone UTF-16 surrogates without changing valid Unicode or hiding ca
|
||||
test('rejects a projected hierarchy payload above 8 MiB', () => {
|
||||
const devices = Array.from({ length: 10_000 }, (_, index) => rawDevice(index, {
|
||||
name: `Camera ${index} ${'x'.repeat(900)}`,
|
||||
capabilities: { localStorage: false },
|
||||
}));
|
||||
assert.throws(
|
||||
() => projectDeviceHierarchy({ devices, sites: [], groups: [] }),
|
||||
|
||||
@@ -39,10 +39,11 @@ function makeElement() {
|
||||
|
||||
function makePopupHarness({ paired = true, confirmCopy = false, clipboardReject = null, validServerProof = true } = {}) {
|
||||
const elements = Object.fromEntries(
|
||||
['tabInfo', 'pairingInfo', 'sendBtn', 'copyBtn', 'statusMsg', 'copyWarning', 'confirmCopy', 'openOptionsBtn']
|
||||
['tabInfo', 'pairingInfo', 'sendBtn', 'copyBtn', 'statusMsg', 'openOptionsBtn']
|
||||
.map((id) => [id, makeElement()])
|
||||
);
|
||||
const clipboardWrites = [];
|
||||
const confirmationMessages = [];
|
||||
const fetchCalls = [];
|
||||
let cookieReads = 0;
|
||||
const chromeApi = {
|
||||
@@ -65,7 +66,7 @@ function makePopupHarness({ paired = true, confirmCopy = false, clipboardReject
|
||||
chromeApi,
|
||||
documentApi,
|
||||
navigatorApi,
|
||||
confirmCopy: () => confirmCopy,
|
||||
confirmCopy: (message) => { confirmationMessages.push(message); return confirmCopy; },
|
||||
cryptoApi: crypto.webcrypto,
|
||||
fetchImpl: async (...args) => {
|
||||
fetchCalls.push(args);
|
||||
@@ -81,7 +82,7 @@ function makePopupHarness({ paired = true, confirmCopy = false, clipboardReject
|
||||
return { ok: true, json: async () => ({ success: true }) };
|
||||
}
|
||||
});
|
||||
return { controller, elements, clipboardWrites, fetchCalls, get cookieReads() { return cookieReads; } };
|
||||
return { controller, elements, clipboardWrites, confirmationMessages, fetchCalls, get cookieReads() { return cookieReads; } };
|
||||
}
|
||||
|
||||
test('manifest commits only a public key, stable ID, local storage, and exact loopback host access', () => {
|
||||
@@ -174,11 +175,11 @@ test('copy cancellation occurs before cookie access and never writes the token',
|
||||
assert.match(harness.elements.statusMsg.textContent, /cancelled/i);
|
||||
});
|
||||
|
||||
test('confirmed copy warns about clipboard history and reports success without exposing token', async () => {
|
||||
test('confirmed copy uses one concise prompt and reports success without exposing token', async () => {
|
||||
const harness = makePopupHarness({ paired: true, confirmCopy: true });
|
||||
await harness.controller.init();
|
||||
assert.match(harness.elements.copyWarning.textContent, /clipboard (?:history|sync)/i);
|
||||
await harness.controller.copyToken();
|
||||
assert.deepEqual(harness.confirmationMessages, ['Copy VA token?']);
|
||||
assert.deepEqual(harness.clipboardWrites, ['sensitive-va-token']);
|
||||
assert.match(harness.elements.statusMsg.textContent, /copied/i);
|
||||
assert.equal(harness.elements.statusMsg.textContent.includes('sensitive-va-token'), false);
|
||||
@@ -201,6 +202,7 @@ test('options UI stores a validated pairing secret locally and can forget pairin
|
||||
const js = read('options.js');
|
||||
assert.match(html, /pairingSecret/);
|
||||
assert.match(html, /type="password"/);
|
||||
assert.doesNotMatch(html, /treat it like a password|privacy-note/i);
|
||||
assert.match(js, /chrome\.storage\.local\.set/);
|
||||
assert.match(js, /chrome\.storage\.local\.remove/);
|
||||
assert.match(js, /aptPairingSecret/);
|
||||
|
||||
+22
-22
@@ -11,7 +11,7 @@ const APPROVED_DIRECTORY = 'C:\\Program Files\\Alta Proxy Tool';
|
||||
const APPROVED_HELPER = 'C:\\Program Files\\Alta Proxy Tool\\aware-cam-proxy.exe';
|
||||
const VALID_HOST = 'tenant.avasecurity.com';
|
||||
const VALID_DEVICE_ID = '123e4567-e89b-42d3-a456-426614174000';
|
||||
const VALID_USERNAME = 'proxy.operator+apt@example.com';
|
||||
const SYNTHETIC_COOKIE = 'HERMES_SENTINEL_SESSION_COOKIE';
|
||||
|
||||
function loadModule() {
|
||||
return require(MODULE_PATH);
|
||||
@@ -67,7 +67,7 @@ function validRequest(overrides = {}) {
|
||||
return {
|
||||
deploymentHost: VALID_HOST,
|
||||
deviceId: VALID_DEVICE_ID,
|
||||
username: VALID_USERNAME,
|
||||
cookie: SYNTHETIC_COOKIE,
|
||||
...overrides
|
||||
};
|
||||
}
|
||||
@@ -76,18 +76,18 @@ test('exports the proxy manager module', () => {
|
||||
assert.doesNotThrow(() => loadModule());
|
||||
});
|
||||
|
||||
test('launches the approved helper directly in a visible interactive console', () => {
|
||||
test('launches the approved helper directly with the paired Alta session', () => {
|
||||
const { manager, calls } = createHarness();
|
||||
|
||||
const result = manager.launchProxy(validRequest());
|
||||
|
||||
assert.deepEqual(calls, [[
|
||||
APPROVED_HELPER,
|
||||
['-a', VALID_HOST, '-u', VALID_USERNAME, '-d', VALID_DEVICE_ID],
|
||||
['-a', VALID_HOST, '-d', VALID_DEVICE_ID, '-k', SYNTHETIC_COOKIE],
|
||||
{
|
||||
shell: false,
|
||||
detached: true,
|
||||
stdio: 'inherit',
|
||||
stdio: 'ignore',
|
||||
windowsHide: false
|
||||
}
|
||||
]]);
|
||||
@@ -100,16 +100,15 @@ test('launches the approved helper directly in a visible interactive console', (
|
||||
});
|
||||
});
|
||||
|
||||
test('passes username punctuation literally in argv without invoking a shell', () => {
|
||||
const username = 'proxy+apt&literal|name@example.com';
|
||||
test('passes the paired cookie literally without invoking a shell or console prompt', () => {
|
||||
const cookie = 'abc_DEF-123.456==';
|
||||
const { manager, calls } = createHarness();
|
||||
|
||||
manager.launchProxy(validRequest({ username }));
|
||||
manager.launchProxy(validRequest({ cookie }));
|
||||
|
||||
assert.equal(calls[0][1][3], username);
|
||||
assert.equal(calls[0][1][5], cookie);
|
||||
assert.equal(calls[0][2].shell, false);
|
||||
assert.equal(calls[0][2].stdio, 'inherit');
|
||||
assert.notEqual(calls[0][2].stdio, 'ignore');
|
||||
assert.equal(calls[0][2].stdio, 'ignore');
|
||||
});
|
||||
|
||||
test('rejects the CRLF calc.exe reproducer in every structured input', () => {
|
||||
@@ -117,8 +116,8 @@ test('rejects the CRLF calc.exe reproducer in every structured input', () => {
|
||||
const attacks = [
|
||||
{ deploymentHost: `${VALID_HOST}\r\ncalc.exe` },
|
||||
{ deviceId: `${VALID_DEVICE_ID}\r\ncalc.exe` },
|
||||
{ username: `${VALID_USERNAME}\r\ncalc.exe` },
|
||||
{ username: `${VALID_USERNAME}\0calc.exe` }
|
||||
{ cookie: `${SYNTHETIC_COOKIE}\r\ncalc.exe` },
|
||||
{ cookie: `${SYNTHETIC_COOKIE}\0calc.exe` }
|
||||
];
|
||||
|
||||
for (const attack of attacks) {
|
||||
@@ -191,10 +190,10 @@ test('normalizes a valid Alta hostname to lowercase', () => {
|
||||
assert.equal(calls[0][1][1], 'tenant.avigilon.com');
|
||||
});
|
||||
|
||||
test('rejects empty, oversized, non-string, and control-character usernames', () => {
|
||||
for (const username of ['', 'x'.repeat(255), 42, 'user\nname', 'user\rname', 'user\0name', 'user\u007fname']) {
|
||||
test('rejects empty, oversized, non-string, and control-character cookies', () => {
|
||||
for (const cookie of ['', 'x'.repeat(4097), 42, 'abc\nxyz', 'abc\rxyz', 'abc\0xyz', 'abc;xyz']) {
|
||||
const { manager } = createHarness();
|
||||
assert.throws(() => manager.launchProxy(validRequest({ username })), /username/i);
|
||||
assert.throws(() => manager.launchProxy(validRequest({ cookie })), /cookie/i);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -218,25 +217,27 @@ test('requires an absolute approved application directory and Windows platform',
|
||||
);
|
||||
});
|
||||
|
||||
test('reports a bounded spawn failure without credential redaction machinery', () => {
|
||||
test('redacts the cookie if spawn throws an error containing it', () => {
|
||||
const { createProxyManager } = loadModule();
|
||||
const manager = createProxyManager({
|
||||
appDirectory: APPROVED_DIRECTORY,
|
||||
fs: { existsSync: () => true },
|
||||
platform: 'win32',
|
||||
spawn: () => { throw new Error('spawn failed'); }
|
||||
spawn: () => { throw new Error(`spawn failed for ${SYNTHETIC_COOKIE}`); }
|
||||
});
|
||||
|
||||
assert.throws(
|
||||
() => manager.launchProxy(validRequest()),
|
||||
(error) => {
|
||||
assert.match(error.message, /spawn failed/);
|
||||
assert.match(error.message, /\[REDACTED\]/);
|
||||
assert.doesNotMatch(error.message, /HERMES_SENTINEL_SESSION_COOKIE/);
|
||||
return true;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('tracks only safe process metadata and never exposes usernames', () => {
|
||||
test('tracks only safe process metadata and never exposes cookies', () => {
|
||||
const { manager } = createHarness();
|
||||
manager.launchProxy(validRequest());
|
||||
|
||||
@@ -247,7 +248,7 @@ test('tracks only safe process metadata and never exposes usernames', () => {
|
||||
startedAt: 1_777_777_777_777,
|
||||
status: 'running'
|
||||
}]);
|
||||
assert.doesNotMatch(JSON.stringify(tracked), /proxy\.operator/);
|
||||
assert.doesNotMatch(JSON.stringify(tracked), /HERMES_SENTINEL_SESSION_COOKIE/);
|
||||
});
|
||||
|
||||
test('stopping one tracked process waits for confirmed exit and leaves the other alive', async () => {
|
||||
@@ -381,9 +382,8 @@ test('exit events remove only the matching owned child', () => {
|
||||
assert.deepEqual(manager.listTrackedProxies().map((item) => item.processId), [4101]);
|
||||
});
|
||||
|
||||
test('source contains no bearer argv, shell launchers, broad process killers, or persistence APIs', () => {
|
||||
test('source contains no shell launchers, broad process killers, or credential persistence APIs', () => {
|
||||
const source = fs.readFileSync(path.join(__dirname, '..', 'src', 'proxy-launch.js'), 'utf8');
|
||||
assert.doesNotMatch(source, /cookie|bearer|token|-k/i);
|
||||
assert.doesNotMatch(source, /\b(?:cmd(?:\.exe)?|powershell|taskkill|pkill|wmic)\b/i);
|
||||
assert.doesNotMatch(source, /\.(?:bat|command)\b/i);
|
||||
assert.doesNotMatch(source, /(?:writeFile|appendFile|mkdtemp|tmpdir)/);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const { createRendererController } = require('../renderer-controller');
|
||||
const { createRendererController, shouldShowPairingOnboarding } = require('../renderer-controller');
|
||||
|
||||
function harness(result) {
|
||||
const calls = [];
|
||||
@@ -39,3 +39,11 @@ test('successful disconnect renders disconnected state and clears device state',
|
||||
['status', 'Disconnected from Alta.', 'info'],
|
||||
]);
|
||||
});
|
||||
|
||||
test('pairing onboarding is temporary but remains recoverable', () => {
|
||||
assert.equal(shouldShowPairingOnboarding({ paired: false }), true);
|
||||
assert.equal(shouldShowPairingOnboarding({ paired: true, secretVisible: true }), true);
|
||||
assert.equal(shouldShowPairingOnboarding({ paired: true }), false);
|
||||
assert.equal(shouldShowPairingOnboarding({ paired: true, secretVisible: true, connected: true }), false);
|
||||
assert.equal(shouldShowPairingOnboarding({ paired: true, connected: true, manuallyOpen: true }), true);
|
||||
});
|
||||
|
||||
@@ -40,6 +40,7 @@ test('renderer uses one hierarchy request and no parallel flat discovery', () =>
|
||||
assert.match(renderer, /electronAPI\.getDeviceHierarchy\(\)/);
|
||||
assert.doesNotMatch(renderer, /electronAPI\.getDevices\(|electronAPI\.getDeviceSites\(|Promise\.all\(\s*\[\s*window\.electronAPI\.get/);
|
||||
assert.doesNotMatch(renderer, /\ballDevices\b|\ballSites\b|\bcollapsedSites\b|groupDevicesBySite/);
|
||||
assert.doesNotMatch(renderer, /Selected camera is hidden|hidden by the current view/i);
|
||||
assert.match(renderer, /loadGeneration/);
|
||||
assert.match(renderer, /generation\s*!==\s*loadGeneration/);
|
||||
});
|
||||
|
||||
@@ -92,14 +92,11 @@ test('runtime keeps Alta credentials in main-owned modules and exposes only non-
|
||||
localStorage: null,
|
||||
}],
|
||||
});
|
||||
const launched = await runtime.launchProxy(
|
||||
'550e8400-e29b-41d4-a716-446655440000',
|
||||
'proxy.operator@example.com'
|
||||
);
|
||||
const launched = await runtime.launchProxy('550e8400-e29b-41d4-a716-446655440000');
|
||||
assert.equal(launched.success, true);
|
||||
assert.deepEqual(calls[0], {
|
||||
deploymentHost: 'customer.avasecurity.com',
|
||||
username: 'proxy.operator@example.com',
|
||||
cookie: 'top-secret-cookie',
|
||||
deviceId: '550e8400-e29b-41d4-a716-446655440000',
|
||||
});
|
||||
assert.deepEqual(runtime.getConnectionState(), {
|
||||
@@ -189,7 +186,7 @@ test('newest hierarchy discovery owns the launch allowlist when completions arri
|
||||
const freshB = runtime.getDeviceHierarchy();
|
||||
pending[1]([{ guid: deviceB }]);
|
||||
assert.equal((await freshB).success, true);
|
||||
assert.equal((await runtime.launchProxy(deviceB, 'operator@example.com')).success, true);
|
||||
assert.equal((await runtime.launchProxy(deviceB)).success, true);
|
||||
pending[0]([{ guid: deviceA }]);
|
||||
assert.deepEqual(await staleA, {
|
||||
success: false,
|
||||
@@ -197,7 +194,7 @@ test('newest hierarchy discovery owns the launch allowlist when completions arri
|
||||
hierarchy: { devices: [], sites: [], groups: [] },
|
||||
message: 'Alta device discovery result is stale',
|
||||
});
|
||||
assert.equal((await runtime.launchProxy(deviceA, 'operator@example.com')).success, false);
|
||||
assert.equal((await runtime.launchProxy(deviceA)).success, false);
|
||||
assert.deepEqual(launches, [deviceB]);
|
||||
});
|
||||
|
||||
@@ -248,7 +245,7 @@ test('launch allowlist remains bound to the session origin that produced it', as
|
||||
});
|
||||
assert.equal((await runtime.getDeviceHierarchy()).success, true);
|
||||
sessionStore.establish('https://second.avasecurity.com', 'replacement-cookie');
|
||||
assert.equal((await runtime.launchProxy(deviceId, 'operator@example.com')).success, false);
|
||||
assert.equal((await runtime.launchProxy(deviceId)).success, false);
|
||||
assert.equal(launches, 0);
|
||||
});
|
||||
|
||||
@@ -256,28 +253,6 @@ test('runtime rejects discovery deadlines above 60 seconds', () => {
|
||||
assert.throws(() => new AppRuntime({ discoveryTimeoutMs: 60_001 }), /discovery timeout/i);
|
||||
});
|
||||
|
||||
test('runtime rejects invalid usernames before calling the proxy manager', async () => {
|
||||
const sessionStore = createSessionStore();
|
||||
sessionStore.establish('https://customer.avasecurity.com', 'top-secret-cookie');
|
||||
const deviceId = '550e8400-e29b-41d4-a716-446655440000';
|
||||
let launches = 0;
|
||||
const runtime = new AppRuntime({
|
||||
sessionStore,
|
||||
altaClient: { getDevices: async () => [{ guid: deviceId }] },
|
||||
proxyManager: {
|
||||
launchProxy() { launches += 1; },
|
||||
listTrackedProxies() { return []; },
|
||||
},
|
||||
});
|
||||
await runtime.getDevices();
|
||||
|
||||
for (const username of ['', 'x'.repeat(255), 'operator@example.com\r\n-k secret', null]) {
|
||||
const result = await runtime.launchProxy(deviceId, username);
|
||||
assert.equal(result.success, false);
|
||||
assert.match(result.message, /username/i);
|
||||
}
|
||||
assert.equal(launches, 0);
|
||||
});
|
||||
|
||||
test('bridge authenticates itself before accepting a one-time HMAC cookie request', async () => {
|
||||
const protect = (value) => Buffer.from(`protected:${value}`);
|
||||
@@ -403,10 +378,10 @@ test('runtime reconciles exited children and permits relaunch for the same devic
|
||||
},
|
||||
});
|
||||
await runtime.getDevices();
|
||||
assert.equal((await runtime.launchProxy(deviceId, 'operator@example.com')).processId, 4101);
|
||||
assert.equal((await runtime.launchProxy(deviceId)).processId, 4101);
|
||||
tracked.length = 0;
|
||||
assert.deepEqual(runtime.getConnectionState().activeProxies, []);
|
||||
assert.equal((await runtime.launchProxy(deviceId, 'operator@example.com')).processId, 4102);
|
||||
assert.equal((await runtime.launchProxy(deviceId)).processId, 4102);
|
||||
});
|
||||
|
||||
test('disconnect stops every owned proxy before clearing the Alta session', async () => {
|
||||
@@ -474,7 +449,7 @@ test('kill request without exit keeps proxy tracked and session connected until
|
||||
setTimeout(callback) { timeoutCallback = callback; return 1; },
|
||||
clearTimeout() {},
|
||||
});
|
||||
proxyManager.launchProxy({ deploymentHost: 'customer.avasecurity.com', username: 'operator@example.com', deviceId });
|
||||
proxyManager.launchProxy({ deploymentHost: 'customer.avasecurity.com', cookie: 'synthetic-cookie', deviceId });
|
||||
const runtime = new AppRuntime({ sessionStore, altaClient: {}, proxyManager });
|
||||
|
||||
const firstDisconnect = runtime.disconnect();
|
||||
@@ -525,13 +500,16 @@ test('preload and renderer expose only narrow, credential-free contracts', () =>
|
||||
];
|
||||
for (const method of expectedMethods) assert.match(preload, new RegExp(`\\b${method}\\b`));
|
||||
assert.doesNotMatch(preload, /downloadAndInstall|download-and-install|onUpdateDownloadProgress|onExtensionCookie/);
|
||||
assert.match(preload, /launchProxy:\s*\(deviceId, username\)/);
|
||||
assert.match(preload, /launchProxy:\s*\(deviceId\)/);
|
||||
assert.doesNotMatch(preload, /launchProxy:\s*\([^)]*(?:cookie|origin)/i);
|
||||
assert.doesNotMatch(renderer, /cookieValue|sessionData\.cookies|cookies\s*:/);
|
||||
assert.match(renderer, /state\.connected\s*&&\s*\(!wasConnected\s*\|\|\s*state\.origin\s*!==\s*previousOrigin\)/);
|
||||
assert.match(read('main.js'), /onConnectionStateChanged:\s*\(\)\s*=>\s*\{\s*runtime\.onSessionChanged\(\)/);
|
||||
assert.doesNotMatch(html, /id="cookieKey"|updateProgress|Install Update/);
|
||||
assert.match(html, /id="altaUsername"/);
|
||||
assert.doesNotMatch(html, /id="cookieKey"|id="altaUsername"|type="password"|updateProgress|Install Update/);
|
||||
assert.match(html, /paired Chrome extension session/i);
|
||||
assert.match(html, /id="pairingSection"[^>]*hidden/);
|
||||
assert.match(html, /id="managePairingBtn"/);
|
||||
assert.match(renderer, /shouldShowPairingOnboarding/);
|
||||
assert.match(renderer, /openFixedReleasesPage/);
|
||||
assert.match(html, /Bridge Pairing/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user