'use strict'; const test = require('node:test'); const assert = require('node:assert/strict'); const { PassThrough, Readable } = require('node:stream'); const { BridgeAuth, BridgeAuthError, generatePairingSecret, hashPairingSecret, verifyPairingSecret, readJsonBody, createRequestLimiter } = require('../src/bridge-auth'); const EXTENSION_ORIGIN = 'chrome-extension://onbkfpbggekakjddomjjnboippimlmch'; test('pairing secrets are random, URL-safe, and stored as a hash envelope', () => { const first = generatePairingSecret(); const second = generatePairingSecret(); assert.match(first, /^[A-Za-z0-9_-]{43}$/); assert.notEqual(first, second); const envelope = hashPairingSecret(first); assert.equal(envelope.version, 1); assert.equal(envelope.algorithm, 'scrypt'); assert.equal(Object.values(envelope).includes(first), false); assert.equal(verifyPairingSecret(first, envelope), true); assert.equal(verifyPairingSecret(second, envelope), false); assert.equal(verifyPairingSecret('', envelope), false); assert.equal(verifyPairingSecret(null, envelope), false); }); test('auth rejects missing/wrong secrets and unknown or malformed origins', () => { const auth = new BridgeAuth({ expectedOrigin: EXTENSION_ORIGIN }); const { secret } = auth.rotate(); assert.equal(auth.authenticate({ origin: EXTENSION_ORIGIN, secret }), true); assert.equal(auth.authenticate({ origin: EXTENSION_ORIGIN }), false); assert.equal(auth.authenticate({ origin: EXTENSION_ORIGIN, secret: 'wrong' }), false); assert.equal(auth.authenticate({ origin: 'chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', secret }), false); assert.equal(auth.authenticate({ origin: `${EXTENSION_ORIGIN}/`, secret }), false); assert.equal(auth.authenticate({ origin: `${EXTENSION_ORIGIN}\r\nX-Evil: yes`, secret }), false); assert.equal(auth.authenticate({ origin: EXTENSION_ORIGIN, secret: `${secret}\r\n` }), false); }); test('rotation invalidates the previous secret and revoke invalidates the current secret', () => { const auth = new BridgeAuth({ expectedOrigin: EXTENSION_ORIGIN }); const first = auth.rotate().secret; const second = auth.rotate().secret; assert.equal(auth.authenticate({ origin: EXTENSION_ORIGIN, secret: first }), false); assert.equal(auth.authenticate({ origin: EXTENSION_ORIGIN, secret: second }), true); auth.revoke(); assert.equal(auth.authenticate({ origin: EXTENSION_ORIGIN, secret: second }), false); assert.equal(auth.envelope, null); }); test('an envelope can be safely persisted and loaded by a future desktop wiring', () => { const first = new BridgeAuth({ expectedOrigin: EXTENSION_ORIGIN }); const { secret, envelope } = first.rotate(); const persisted = JSON.parse(JSON.stringify(envelope)); const restored = new BridgeAuth({ expectedOrigin: EXTENSION_ORIGIN, envelope: persisted }); assert.equal(restored.authenticate({ origin: EXTENSION_ORIGIN, secret }), true); assert.throws( () => new BridgeAuth({ expectedOrigin: 'https://example.test' }), (error) => error instanceof BridgeAuthError && error.code === 'INVALID_EXTENSION_ORIGIN' ); assert.throws( () => new BridgeAuth({ expectedOrigin: 'chrome-extension://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' }), (error) => error instanceof BridgeAuthError && error.code === 'INVALID_EXTENSION_ORIGIN' ); }); test('readJsonBody accepts a bounded JSON object and rejects malformed or oversized input', async () => { assert.deepEqual( await readJsonBody(Readable.from(['{"ok":true}']), { maxBytes: 64 }), { ok: true } ); await assert.rejects( readJsonBody(Readable.from(['{"nope"']), { maxBytes: 64 }), (error) => error.code === 'MALFORMED_JSON' ); await assert.rejects( readJsonBody(Readable.from(['{"value":"', 'x'.repeat(100), '"}']), { maxBytes: 32 }), (error) => error.code === 'BODY_TOO_LARGE' ); await assert.rejects( readJsonBody(Readable.from(['[]']), { maxBytes: 64 }), (error) => error.code === 'INVALID_JSON_BODY' ); }); test('readJsonBody aborts a slow body at its deadline', async () => { const stream = new PassThrough(); const pending = readJsonBody(stream, { maxBytes: 64, deadlineMs: 15 }); stream.write('{'); await assert.rejects(pending, (error) => error.code === 'BODY_DEADLINE_EXCEEDED'); }); test('request limiter rejects excess concurrent bodies before work begins', async () => { const limiter = createRequestLimiter({ maxConcurrent: 1 }); let release; const active = limiter.run(() => new Promise((resolve) => { release = resolve; })); await assert.rejects( limiter.run(async () => 'never'), (error) => error.code === 'TOO_MANY_REQUESTS' ); release('done'); assert.equal(await active, 'done'); assert.equal(limiter.active, 0); });