Close unconfirmed transfer paths and reject binary signalling envelopes
This commit is contained in:
@@ -69,6 +69,7 @@ export function createP2pHub({ getDevice, authorizeProfile = async () => '', ena
|
||||
}
|
||||
|
||||
async function message(ws, raw) {
|
||||
if (typeof raw !== 'string') return;
|
||||
const t = now();
|
||||
ws.data.budget = ws.data.budget.filter((x) => t - x < 60_000);
|
||||
if (ws.data.budget.length >= BUDGET_PER_MIN) { send(ws, { type: 'error', error: 'slow down' }); return; }
|
||||
@@ -88,11 +89,7 @@ export function createP2pHub({ getDevice, authorizeProfile = async () => '', ena
|
||||
if (!directRooms.has(profile)) directRooms.set(profile, createDirectRelay());
|
||||
directRooms.get(profile).handle(ws.data.peer, m, peers, send); return;
|
||||
}
|
||||
if (m && m.type === 'signal' && typeof m.to === 'string') {
|
||||
const target = online.get(byPeer.get(m.to));
|
||||
if (!target || target === ws) { send(ws, { type: 'error', error: 'peer offline', to: m.to }); return; }
|
||||
send(target, { type: 'signal', from: ws.data.peer, data: m.data });
|
||||
}
|
||||
if (m?.type === 'signal') send(ws, { type: 'error', error: 'use a confirmed direct-transfer invitation', to: m.to });
|
||||
}
|
||||
|
||||
function close(ws) {
|
||||
|
||||
@@ -51,7 +51,7 @@ test('auth on open, hello with opaque peer id, bad secret is closed', async () =
|
||||
expect(hub.isOnline('dev_000000000000000b')).toBe(false);
|
||||
});
|
||||
|
||||
test('signals are relayed between online peers only; close drops presence', async () => {
|
||||
test('legacy unconfirmed signalling is refused; close drops presence', async () => {
|
||||
const hub = createP2pHub({ getDevice: p2pDb.getDevice });
|
||||
const a = fakeWs({ budget: [] });
|
||||
const b = fakeWs({ budget: [] });
|
||||
@@ -60,11 +60,12 @@ test('signals are relayed between online peers only; close drops presence', asyn
|
||||
await hub.websocket.message(a, JSON.stringify({ type: 'auth', device: 'dev_000000000000000a', secret: SECRET_A }));
|
||||
await hub.websocket.message(b, JSON.stringify({ type: 'auth', device: 'dev_000000000000000b', secret: SECRET_B }));
|
||||
await hub.websocket.message(a, JSON.stringify({ type: 'signal', to: b.data.peer, data: { sdp: 'x' } }));
|
||||
expect(b.sent.at(-1)).toEqual({ type: 'signal', from: a.data.peer, data: { sdp: 'x' } });
|
||||
expect(a.sent.at(-1)).toMatchObject({ type: 'error', error: 'use a confirmed direct-transfer invitation' });
|
||||
expect(b.sent.some(m => m.type === 'signal')).toBe(false);
|
||||
hub.websocket.close(b);
|
||||
expect(hub.isOnline('dev_000000000000000b')).toBe(false);
|
||||
await hub.websocket.message(a, JSON.stringify({ type: 'signal', to: b.data.peer, data: {} }));
|
||||
expect(a.sent.at(-1)).toMatchObject({ type: 'error', error: 'peer offline' });
|
||||
expect(a.sent.at(-1)).toMatchObject({ type: 'error', error: 'use a confirmed direct-transfer invitation' });
|
||||
expect(hub.send('dev_000000000000000a', { type: 'ping' })).toBe(true);
|
||||
expect(hub.send('dev_000000000000000b', { type: 'ping' })).toBe(false);
|
||||
});
|
||||
@@ -119,3 +120,10 @@ test('direct directory and invitations are restricted to authorized profile sock
|
||||
await hub.websocket.message(b, JSON.stringify({ type: 'direct', action: 'request', to: peerIdOf('dev_000000000000000a'), id: 'localSong' }));
|
||||
expect(a.sent.at(-1).action).toBe('request');
|
||||
});
|
||||
|
||||
test('binary websocket payloads cannot serve as signalling envelopes', async () => {
|
||||
const hub = createP2pHub({ getDevice: p2pDb.getDevice });
|
||||
const ws = fakeWs({ budget: [] });
|
||||
await hub.websocket.message(ws, Buffer.from(JSON.stringify({ type: 'auth', device: 'dev_000000000000000a', secret: SECRET_A })));
|
||||
expect(ws.sent).toEqual([]); expect(ws.data.authed).not.toBe(true);
|
||||
});
|
||||
|
||||
@@ -170,7 +170,7 @@ export function createPartyHub({ now = () => Date.now() } = {}) {
|
||||
|
||||
return {
|
||||
upgrade,
|
||||
websocket: { maxPayloadLength: MAX_MSG, idleTimeout: 120, open, message: (ws, msg) => message(ws, typeof msg === 'string' ? msg : Buffer.from(msg).toString('utf8')), close },
|
||||
websocket: { maxPayloadLength: MAX_MSG, idleTimeout: 120, open, message: (ws, msg) => message(ws, typeof msg === 'string' ? msg : null), close },
|
||||
_parties: parties,
|
||||
stop() { clearInterval(sweeper); },
|
||||
};
|
||||
|
||||
@@ -258,7 +258,7 @@ export function createRemoteHub({ requireSameNetwork = false } = {}) {
|
||||
maxPayloadLength: MAX_MSG,
|
||||
idleTimeout: 120,
|
||||
open(ws) { if (ws.data.role === 'host') openHost(ws); else openRemote(ws); },
|
||||
message(ws, msg) { onMessage(ws, typeof msg === 'string' ? msg : Buffer.from(msg).toString('utf8')); },
|
||||
message(ws, msg) { onMessage(ws, typeof msg === 'string' ? msg : null); },
|
||||
close(ws) { onClose(ws); },
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user