Skip to content

Commit c623d8a

Browse files
committed
Warn when driver config inconsistent with web page protocol
Encryption can be explicitly turned on or off in the driver config. Driver can be used on both HTTP and HTTPS web pages. Configured encryption has to match the security protocol of the web page. Insecure WebSockets ('ws') have to be used on insecure pages ('http'). Secure WebSockets ('wss') have to be used on secure pages ('https'). WebSockets might not work in a mixed content environment, i.e. 'ws' + 'https' or 'wss' + 'http'. This commit makes driver print a warning when its configuration is inconsistent with the web page.
1 parent e8b4bcd commit c623d8a

File tree

2 files changed

+90
-5
lines changed

2 files changed

+90
-5
lines changed

src/v1/internal/ch-websocket.js

+46-5
Original file line numberDiff line numberDiff line change
@@ -228,21 +228,23 @@ function asWindowsFriendlyIPv6Address(scheme, parsedUrl) {
228228
* @return {{scheme: string|null, error: Neo4jError|null}} object containing either scheme or error.
229229
*/
230230
function determineWebSocketScheme(config, protocolSupplier) {
231-
const encrypted = config.encrypted;
231+
const encryptionOn = isEncryptionExplicitlyTurnedOn(config);
232+
const encryptionOff = isEncryptionExplicitlyTurnedOff(config);
232233
const trust = config.trust;
234+
const secureProtocol = isProtocolSecure(protocolSupplier);
235+
verifyEncryptionSettings(encryptionOn, encryptionOff, secureProtocol);
233236

234-
if (encrypted === false || encrypted === ENCRYPTION_OFF) {
237+
if (encryptionOff) {
235238
// encryption explicitly turned off in the config
236239
return {scheme: 'ws', error: null};
237240
}
238241

239-
const protocol = typeof protocolSupplier === 'function' ? protocolSupplier() : '';
240-
if (protocol && protocol.toLowerCase().indexOf('https') >= 0) {
242+
if (secureProtocol) {
241243
// driver is used in a secure https web page, use 'wss'
242244
return {scheme: 'wss', error: null};
243245
}
244246

245-
if (encrypted === true || encrypted === ENCRYPTION_ON) {
247+
if (encryptionOn) {
246248
// encryption explicitly requested in the config
247249
if (!trust || trust === 'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES') {
248250
// trust strategy not specified or the only supported strategy is specified
@@ -260,6 +262,45 @@ function determineWebSocketScheme(config, protocolSupplier) {
260262
return {scheme: 'ws', error: null};
261263
}
262264

265+
/**
266+
* @param {ChannelConfig} config - configuration for the channel.
267+
* @return {boolean} <code>true</code> if encryption enabled in the config, <code>false</code> otherwise.
268+
*/
269+
function isEncryptionExplicitlyTurnedOn(config) {
270+
return config.encrypted === true || config.encrypted === ENCRYPTION_ON;
271+
}
272+
273+
/**
274+
* @param {ChannelConfig} config - configuration for the channel.
275+
* @return {boolean} <code>true</code> if encryption disabled in the config, <code>false</code> otherwise.
276+
*/
277+
function isEncryptionExplicitlyTurnedOff(config) {
278+
return config.encrypted === false || config.encrypted === ENCRYPTION_OFF;
279+
}
280+
281+
/**
282+
* @param {function(): string} protocolSupplier - function that detects protocol of the web page.
283+
* @return {boolean} <code>true</code> if protocol returned by the given function is secure, <code>false</code> otherwise.
284+
*/
285+
function isProtocolSecure(protocolSupplier) {
286+
const protocol = typeof protocolSupplier === 'function' ? protocolSupplier() : '';
287+
return protocol && protocol.toLowerCase().indexOf('https') >= 0;
288+
}
289+
290+
function verifyEncryptionSettings(encryptionOn, encryptionOff, secureProtocol) {
291+
if (encryptionOn && !secureProtocol) {
292+
// encryption explicitly turned on for a driver used on a HTTP web page
293+
console.warn('Neo4j driver is configured to use secure WebSocket on a HTTP web page. ' +
294+
'WebSockets might not work in a mixed content environment. ' +
295+
'Please consider configuring driver to not use encryption.');
296+
} else if (encryptionOff && secureProtocol) {
297+
// encryption explicitly turned off for a driver used on a HTTPS web page
298+
console.warn('Neo4j driver is configured to use insecure WebSocket on a HTTPS web page. ' +
299+
'WebSockets might not work in a mixed content environment. ' +
300+
'Please consider configuring driver to use encryption.');
301+
}
302+
}
303+
263304
function detectWebPageProtocol() {
264305
return window && window.location ? window.location.protocol : null;
265306
}

test/internal/ch-websocket.test.js

+44
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,16 @@ describe('WebSocketChannel', () => {
3030

3131
let OriginalWebSocket;
3232
let webSocketChannel;
33+
let originalConsoleWarn;
3334

3435
beforeEach(() => {
3536
if (webSocketChannelAvailable) {
3637
OriginalWebSocket = WebSocket;
3738
}
39+
originalConsoleWarn = console.warn;
40+
console.warn = () => {
41+
// mute by default
42+
};
3843
});
3944

4045
afterEach(() => {
@@ -44,6 +49,7 @@ describe('WebSocketChannel', () => {
4449
if (webSocketChannel) {
4550
webSocketChannel.close();
4651
}
52+
console.warn = originalConsoleWarn;
4753
});
4854

4955
it('should fallback to literal IPv6 when SyntaxError is thrown', () => {
@@ -143,6 +149,16 @@ describe('WebSocketChannel', () => {
143149
expect(channel._error.name).toEqual('Neo4jError');
144150
});
145151

152+
it('should generate a warning when encryption turned on for HTTP web page', () => {
153+
testWarningInMixedEnvironment(true, 'http');
154+
testWarningInMixedEnvironment(ENCRYPTION_ON, 'http');
155+
});
156+
157+
it('should generate a warning when encryption turned off for HTTPS web page', () => {
158+
testWarningInMixedEnvironment(false, 'https');
159+
testWarningInMixedEnvironment(ENCRYPTION_OFF, 'https');
160+
});
161+
146162
function testFallbackToLiteralIPv6(boltAddress, expectedWsAddress) {
147163
if (!webSocketChannelAvailable) {
148164
return;
@@ -193,4 +209,32 @@ describe('WebSocketChannel', () => {
193209
expect(channel._ws.url).toEqual(expectedScheme + '://localhost:8989');
194210
}
195211

212+
function testWarningInMixedEnvironment(encrypted, scheme) {
213+
if (!webSocketChannelAvailable) {
214+
return;
215+
}
216+
217+
// replace real WebSocket with a function that memorizes the url
218+
WebSocket = url => {
219+
return {
220+
url: url,
221+
close: () => {
222+
}
223+
};
224+
};
225+
226+
// replace console.warn with a function that memorizes the message
227+
const warnMessages = [];
228+
console.warn = message => warnMessages.push(message);
229+
230+
const url = urlUtil.parseDatabaseUrl('bolt://localhost:8989');
231+
const config = new ChannelConfig(url, {encrypted: encrypted}, SERVICE_UNAVAILABLE);
232+
const protocolSupplier = () => scheme + ':';
233+
234+
const channel = new WebSocketChannel(config, protocolSupplier);
235+
236+
expect(channel).toBeDefined();
237+
expect(warnMessages.length).toEqual(1);
238+
}
239+
196240
});

0 commit comments

Comments
 (0)