Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion lib/socket-listeners.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,14 @@ export function initListeners(hsyncClient) {
port: l.port,
targetHost: l.targetHost,
targetPort: l.targetPort,
hasPassword: !!l.password,
};
});
return retVal;
}

function addSocketListener(options = {}) {
const { port, targetPort, targetHost } = options;
const { port, targetPort, targetHost, password } = options;
if (!targetHost) {
throw new Error('no targetHost');
}
Expand Down Expand Up @@ -134,6 +135,7 @@ export function initListeners(hsyncClient) {
socketId: socket.socketId,
port: targetPort || port,
hostName: rpcPeer.hostName,
password,
});
debug('connect result', result);
socket.peerConnected = true;
Expand Down Expand Up @@ -170,6 +172,7 @@ export function initListeners(hsyncClient) {
targetHost: cleanHost,
targetPort: targetPort || port,
port,
password,
};

socketListeners['p' + port] = listener;
Expand Down
19 changes: 16 additions & 3 deletions lib/socket-relays.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,13 @@ export function initRelays(hsyncClient) {
whitelist: l.whitelist || '',
blacklist: l.blacklist || '',
hostName: l.targetHost,
hasPassword: !!l.password,
};
});
return retVal;
}

function connectSocket(peer, { port, socketId, hostName }) {
function connectSocket(peer, { port, socketId, hostName, password }) {
debug('connectSocket', port, socketId, hostName);

peer.notifications.oncloseRelaySocket((peer, { socketId }) => {
Expand All @@ -51,6 +52,17 @@ export function initRelays(hsyncClient) {
throw new Error('no relay found for port: ' + port);
}

// Check password if relay requires one
if (relay.password) {
if (!password) {
throw new Error('password required for relay on port: ' + port);
}
if (relay.password !== password) {
throw new Error('invalid password for relay on port: ' + port);
}
debug('password verified for relay', port);
}

// TODO: check white and black lists on peer

// const relayDataTopic = `msg/${hostName}/${hsyncClient.myHostName}/relayData/${socketId}`;
Expand Down Expand Up @@ -92,17 +104,18 @@ export function initRelays(hsyncClient) {
});
}

function addSocketRelay({ whitelist, blacklist, port, targetPort, targetHost }) {
function addSocketRelay({ whitelist, blacklist, port, targetPort, targetHost, password }) {
targetPort = targetPort || port;
targetHost = targetHost || 'localhost';
debug('creating relay', whitelist, blacklist, port, targetPort, targetHost);
debug('creating relay', whitelist, blacklist, port, targetPort, targetHost, password ? '(password protected)' : '');
const newRelay = {
whitelist,
blacklist,
port,
targetPort,
targetHost,
hostName: targetHost,
password,
};
cachedRelays['p' + port] = newRelay;
return newRelay;
Expand Down
57 changes: 57 additions & 0 deletions test/unit/socket-listeners.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ describe('socket-listeners', () => {
port: 3000,
targetHost: 'https://remote.example.com',
targetPort: 4000,
hasPassword: false,
});
});

Expand Down Expand Up @@ -315,5 +316,61 @@ describe('socket-listeners', () => {
})
);
});

it('should pass password to connectSocket', async () => {
listeners.addSocketListener({
port: 3000,
targetPort: 4000,
targetHost: 'https://remote.example.com',
password: 'secret123',
});

// Trigger socket connection
const connectionHandler = mockNet.createServer.mock.calls[0][0];
connectionHandler(mockSocket);

// Wait for connection attempt
await vi.waitFor(() => {
expect(mockRpcPeer.methods.connectSocket).toHaveBeenCalled();
});

expect(mockRpcPeer.methods.connectSocket).toHaveBeenCalledWith(
expect.objectContaining({
password: 'secret123',
})
);
});
});

describe('addSocketListener with password', () => {
let listeners;

beforeEach(() => {
listeners = initListeners(mockHsyncClient);
});

it('should store password in listener', () => {
const listener = listeners.addSocketListener({
port: 3000,
targetHost: 'https://remote.example.com',
password: 'secret123',
});

expect(listener.password).toBe('secret123');
});

it('should indicate hasPassword in getSocketListeners', () => {
listeners.addSocketListener({
port: 3000,
targetHost: 'https://remote.example.com',
password: 'secret123',
});

const result = listeners.getSocketListeners();

expect(result[0].hasPassword).toBe(true);
// Password should NOT be exposed
expect(result[0].password).toBeUndefined();
});
});
});
92 changes: 92 additions & 0 deletions test/unit/socket-relays.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ describe('socket-relays', () => {
whitelist: 'allowed.com',
blacklist: 'blocked.com',
hostName: 'myserver.local',
hasPassword: false,
});
});

Expand Down Expand Up @@ -311,5 +312,96 @@ describe('socket-relays', () => {

await expect(connectPromise).rejects.toThrow('Connection failed');
});

it('should connect successfully with correct password', async () => {
relays.addSocketRelay({
port: 3000,
password: 'secret123',
});

const result = await relays.connectSocket(mockPeer, {
port: 3000,
socketId: 'test-socket',
hostName: 'remote.example.com',
password: 'secret123',
});

expect(result.socketId).toBe('test-socket');
});

it('should throw if password required but not provided', () => {
relays.addSocketRelay({
port: 3000,
password: 'secret123',
});

expect(() => {
relays.connectSocket(mockPeer, {
port: 3000,
socketId: 'test-socket',
hostName: 'remote.example.com',
});
}).toThrow('password required for relay on port: 3000');
});

it('should throw if password is incorrect', () => {
relays.addSocketRelay({
port: 3000,
password: 'secret123',
});

expect(() => {
relays.connectSocket(mockPeer, {
port: 3000,
socketId: 'test-socket',
hostName: 'remote.example.com',
password: 'wrongpassword',
});
}).toThrow('invalid password for relay on port: 3000');
});

it('should connect without password if relay has no password', async () => {
relays.addSocketRelay({
port: 3000,
});

const result = await relays.connectSocket(mockPeer, {
port: 3000,
socketId: 'test-socket',
hostName: 'remote.example.com',
});

expect(result.socketId).toBe('test-socket');
});
});

describe('addSocketRelay with password', () => {
let relays;

beforeEach(() => {
relays = initRelays(mockHsyncClient);
});

it('should store password in relay', () => {
const relay = relays.addSocketRelay({
port: 3000,
password: 'secret123',
});

expect(relay.password).toBe('secret123');
});

it('should indicate hasPassword in getSocketRelays', () => {
relays.addSocketRelay({
port: 3000,
password: 'secret123',
});

const result = relays.getSocketRelays();

expect(result[0].hasPassword).toBe(true);
// Password should NOT be exposed
expect(result[0].password).toBeUndefined();
});
});
});