Files
securebit-chat/tests/ice-servers-validation.test.mjs
T
lockbitchat 7f2ecce57f feat: user-configurable STUN/TURN servers (advanced network settings)
- add iceServers.js: allowlist-based validation/normalization of user-supplied
  STUN/TURN URLs (rejects javascript:/data:/http/ws, control chars, enforces limits)
- add iceSettingsStore.js: opt-in persistence encrypted at rest with a
  non-extractable AES-GCM device key in IndexedDB; load/save/clear
- add IceServerSettings.jsx modal: public vs custom servers, JSON/line input,
  live validation, relay-only toggle, 'Test servers' connectivity check,
  save-on-device prompt, forget-saved action
- wire chosen servers/privacy mode into EnhancedSecureWebRTCManager construction
  (priority: custom > operator override > built-in defaults)
- entry point on the connection-creation screen next to the relay-only toggle
- add ice-servers-validation.test.mjs to the suite
2026-06-15 15:39:13 -04:00

122 lines
4.8 KiB
JavaScript

import assert from 'node:assert';
import {
validateIceUrl,
normalizeIceServers,
parseIceServersInput,
listHasTurn,
ICE_LIMITS
} from '../src/network/iceServers.js';
// --- validateIceUrl: accepts valid STUN/TURN URLs ---
assert.strictEqual(validateIceUrl('stun:stun.example.com:3478'), null);
assert.strictEqual(validateIceUrl('stuns:stun.example.com:5349'), null);
assert.strictEqual(validateIceUrl('turn:turn.example.com:3478?transport=udp'), null);
assert.strictEqual(validateIceUrl('turns:turn.example.com:5349?transport=tcp'), null);
assert.strictEqual(validateIceUrl('turn:[2001:db8::1]:3478'), null);
assert.strictEqual(validateIceUrl('stun:198.51.100.10'), null);
// --- validateIceUrl: rejects dangerous / malformed schemes ---
assert.ok(validateIceUrl('javascript:alert(1)'));
assert.ok(validateIceUrl('data:text/html,<script>1</script>'));
assert.ok(validateIceUrl('http://evil.example.com'));
assert.ok(validateIceUrl('https://evil.example.com'));
assert.ok(validateIceUrl('ws://evil.example.com'));
assert.ok(validateIceUrl(' '));
assert.ok(validateIceUrl(''));
assert.ok(validateIceUrl(123));
assert.ok(validateIceUrl('stun:exa mple.com')); // space in host
assert.ok(validateIceUrl('turn:"><img src=x>')); // markup attempt
assert.ok(validateIceUrl('turn:host.com?transport=quic')); // bad transport
assert.ok(validateIceUrl('stun:host.com:99999999')); // absurd port length
assert.ok(validateIceUrl('stun:' + 'a'.repeat(ICE_LIMITS.MAX_STRING_LENGTH + 10)));
console.log('validateIceUrl checks passed');
// --- normalizeIceServers: valid structured entries ---
{
const { servers, errors, warnings } = normalizeIceServers([
{ urls: 'stun:stun.example.com:3478' },
{ urls: ['turn:turn.example.com:3478?transport=udp', 'turn:turn.example.com:3478?transport=tcp'], username: 'u', credential: 'c' }
]);
assert.strictEqual(errors.length, 0, `unexpected errors: ${errors}`);
assert.strictEqual(warnings.length, 0, `unexpected warnings: ${warnings}`);
assert.strictEqual(servers.length, 2);
assert.strictEqual(servers[0].urls, 'stun:stun.example.com:3478'); // single url collapses to string
assert.ok(Array.isArray(servers[1].urls));
assert.strictEqual(servers[1].username, 'u');
assert.strictEqual(servers[1].credential, 'c');
}
// --- normalizeIceServers: TURN without credentials -> warning, still included ---
{
const { servers, warnings } = normalizeIceServers([{ urls: 'turn:turn.example.com:3478' }]);
assert.strictEqual(servers.length, 1);
assert.ok(warnings.some(w => /username and credential/i.test(w)));
}
// --- normalizeIceServers: rejects malicious url, keeps clean ones, reports error ---
{
const { servers, errors } = normalizeIceServers([
{ urls: 'javascript:alert(1)' },
{ urls: 'stun:good.example.com:3478' }
]);
assert.ok(errors.length >= 1);
assert.strictEqual(servers.length, 1);
assert.strictEqual(servers[0].urls, 'stun:good.example.com:3478');
}
// --- normalizeIceServers: enforces server count limit ---
{
const many = Array.from({ length: ICE_LIMITS.MAX_SERVERS + 1 }, () => ({ urls: 'stun:x.example.com:3478' }));
const { servers, errors } = normalizeIceServers(many);
assert.strictEqual(servers.length, 0);
assert.ok(errors.some(e => /Too many servers/i.test(e)));
}
// --- normalizeIceServers: rejects control chars in credential ---
{
const { errors } = normalizeIceServers([
{ urls: 'turn:t.example.com:3478', username: 'u', credential: 'badcred' }
]);
assert.ok(errors.some(e => /credential contains invalid characters/i.test(e)));
}
console.log('normalizeIceServers checks passed');
// --- parseIceServersInput: JSON array ---
{
const { servers, errors } = parseIceServersInput('[{"urls":"stun:stun.example.com:3478"}]');
assert.strictEqual(errors.length, 0);
assert.strictEqual(servers.length, 1);
}
// --- parseIceServersInput: line-based URLs ---
{
const { servers, errors } = parseIceServersInput('stun:a.example.com:3478\nstun:b.example.com:3478');
assert.strictEqual(errors.length, 0);
assert.strictEqual(servers.length, 2);
}
// --- parseIceServersInput: invalid JSON ---
{
const { errors } = parseIceServersInput('[{not json');
assert.ok(errors.some(e => /Invalid JSON/i.test(e)));
}
// --- parseIceServersInput: empty input is benign ---
{
const { servers, errors } = parseIceServersInput(' ');
assert.strictEqual(servers.length, 0);
assert.strictEqual(errors.length, 0);
}
console.log('parseIceServersInput checks passed');
// --- listHasTurn ---
assert.strictEqual(listHasTurn([{ urls: 'stun:s.example.com:3478' }]), false);
assert.strictEqual(listHasTurn([{ urls: ['stun:s.example.com:3478', 'turn:t.example.com:3478'] }]), true);
assert.strictEqual(listHasTurn(null), false);
console.log('listHasTurn checks passed');
console.log('ICE server validation tests passed');