Files
RoboCommander/node_modules/neo4j-driver/lib/v1/internal/ch-node.js

430 lines
17 KiB
JavaScript
Raw Normal View History

2026-04-05 16:14:49 -04:00
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck');
var _classCallCheck3 = _interopRequireDefault(_classCallCheck2);
var _createClass2 = require('babel-runtime/helpers/createClass');
var _createClass3 = _interopRequireDefault(_createClass2);
var _net = require('net');
var _net2 = _interopRequireDefault(_net);
var _tls = require('tls');
var _tls2 = _interopRequireDefault(_tls);
var _fs = require('fs');
var _fs2 = _interopRequireDefault(_fs);
var _path = require('path');
var _path2 = _interopRequireDefault(_path);
var _os = require('os');
var _buf = require('./buf');
var _util = require('./util');
var _error = require('./../error');
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) 2002-2018 "Neo4j,"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var _CONNECTION_IDGEN = 0;
function userHome() {
// For some reason, Browserify chokes on shimming `process`. This code
// will never get executed on the browser anyway, to just hack around it
var getOutOfHereBrowserifyYoureDrunk = require;
var process = getOutOfHereBrowserifyYoureDrunk('process');
return process.env[process.platform == 'win32' ? 'USERPROFILE' : 'HOME'];
}
function mkFullPath(pathToCreate) {
try {
_fs2.default.mkdirSync(pathToCreate);
} catch (e) {
if (e.code === 'ENOENT') {
// Create parent dir
mkFullPath(_path2.default.dirname(pathToCreate));
// And now try again
mkFullPath(pathToCreate);
return;
}
if (e.code === 'EEXIST') {
return;
}
throw e;
}
}
function loadFingerprint(serverId, knownHostsPath, cb) {
try {
_fs2.default.accessSync(knownHostsPath);
} catch (e) {
return cb(null);
}
var found = false;
require('readline').createInterface({
input: _fs2.default.createReadStream(knownHostsPath)
}).on('line', function (line) {
if (!found && line.startsWith(serverId)) {
found = true;
cb(line.split(" ")[1]);
}
}).on('close', function () {
if (!found) {
cb(null);
}
});
}
var _lockFingerprintFromAppending = {};
function storeFingerprint(serverId, knownHostsPath, fingerprint, cb) {
// we check if the serverId has been appended
if (!!_lockFingerprintFromAppending[serverId]) {
// if it has, we ignore it
return cb(null);
}
// we make the line as appended
// ( 1 is more efficient to store than true because true is an oddball )
_lockFingerprintFromAppending[serverId] = 1;
// If file doesn't exist, create full path to it
try {
_fs2.default.accessSync(knownHostsPath);
} catch (_) {
mkFullPath(_path2.default.dirname(knownHostsPath));
}
_fs2.default.appendFile(knownHostsPath, serverId + " " + fingerprint + _os.EOL, "utf8", function (err) {
delete _lockFingerprintFromAppending[serverId];
if (err) {
console.log(err);
}
return cb(err);
});
}
var TrustStrategy = {
/**
* @deprecated Since version 1.0. Will be deleted in a future version. {@link #TRUST_CUSTOM_CA_SIGNED_CERTIFICATES}.
*/
TRUST_SIGNED_CERTIFICATES: function TRUST_SIGNED_CERTIFICATES(config, onSuccess, onFailure) {
console.warn('`TRUST_SIGNED_CERTIFICATES` has been deprecated as option and will be removed in a future version of ' + "the driver. Please use `TRUST_CUSTOM_CA_SIGNED_CERTIFICATES` instead.");
return TrustStrategy.TRUST_CUSTOM_CA_SIGNED_CERTIFICATES(config, onSuccess, onFailure);
},
TRUST_CUSTOM_CA_SIGNED_CERTIFICATES: function TRUST_CUSTOM_CA_SIGNED_CERTIFICATES(config, onSuccess, onFailure) {
if (!config.trustedCertificates || config.trustedCertificates.length === 0) {
onFailure((0, _error.newError)("You are using TRUST_CUSTOM_CA_SIGNED_CERTIFICATES as the method " + "to verify trust for encrypted connections, but have not configured any " + "trustedCertificates. You must specify the path to at least one trusted " + "X.509 certificate for this to work. Two other alternatives is to use " + "TRUST_ALL_CERTIFICATES or to disable encryption by setting encrypted=\"" + _util.ENCRYPTION_OFF + "\"" + "in your driver configuration."));
return;
}
var tlsOpts = {
ca: config.trustedCertificates.map(function (f) {
return _fs2.default.readFileSync(f);
}),
// Because we manually check for this in the connect callback, to give
// a more helpful error to the user
rejectUnauthorized: false
};
var socket = _tls2.default.connect(config.url.port, config.url.host, tlsOpts, function () {
if (!socket.authorized) {
onFailure((0, _error.newError)("Server certificate is not trusted. If you trust the database you are connecting to, add" + " the signing certificate, or the server certificate, to the list of certificates trusted by this driver" + " using `neo4j.v1.driver(.., { trustedCertificates:['path/to/certificate.crt']}). This " + " is a security measure to protect against man-in-the-middle attacks. If you are just trying " + " Neo4j out and are not concerned about encryption, simply disable it using `encrypted=\"" + _util.ENCRYPTION_OFF + "\"`" + " in the driver options. Socket responded with: " + socket.authorizationError));
} else {
onSuccess();
}
});
socket.on('error', onFailure);
return socket;
},
TRUST_SYSTEM_CA_SIGNED_CERTIFICATES: function TRUST_SYSTEM_CA_SIGNED_CERTIFICATES(config, onSuccess, onFailure) {
var tlsOpts = {
// Because we manually check for this in the connect callback, to give
// a more helpful error to the user
rejectUnauthorized: false
};
var socket = _tls2.default.connect(config.url.port, config.url.host, tlsOpts, function () {
if (!socket.authorized) {
onFailure((0, _error.newError)("Server certificate is not trusted. If you trust the database you are connecting to, use " + "TRUST_CUSTOM_CA_SIGNED_CERTIFICATES and add" + " the signing certificate, or the server certificate, to the list of certificates trusted by this driver" + " using `neo4j.v1.driver(.., { trustedCertificates:['path/to/certificate.crt']}). This " + " is a security measure to protect against man-in-the-middle attacks. If you are just trying " + " Neo4j out and are not concerned about encryption, simply disable it using `encrypted=\"" + _util.ENCRYPTION_OFF + "\"`" + " in the driver options. Socket responded with: " + socket.authorizationError));
} else {
onSuccess();
}
});
socket.on('error', onFailure);
return socket;
},
/**
* @deprecated in 1.1 in favour of {@link #TRUST_ALL_CERTIFICATES}. Will be deleted in a future version.
*/
TRUST_ON_FIRST_USE: function TRUST_ON_FIRST_USE(config, onSuccess, onFailure) {
console.warn('`TRUST_ON_FIRST_USE` has been deprecated as option and will be removed in a future version of ' + "the driver. Please use `TRUST_ALL_CERTIFICATES` instead.");
var tlsOpts = {
// Because we manually verify the certificate against known_hosts
rejectUnauthorized: false
};
var socket = _tls2.default.connect(config.url.port, config.url.host, tlsOpts, function () {
var serverCert = socket.getPeerCertificate( /*raw=*/true);
if (!serverCert.raw) {
// If `raw` is not available, we're on an old version of NodeJS, and
// the raw cert cannot be accessed (or, at least I couldn't find a way to)
// therefore, we can't generate a SHA512 fingerprint, meaning we can't
// do TOFU, and the safe approach is to fail.
onFailure((0, _error.newError)("You are using a version of NodeJS that does not " + "support trust-on-first use encryption. You can either upgrade NodeJS to " + "a newer version, use `trust:TRUST_CUSTOM_CA_SIGNED_CERTIFICATES` in your driver " + "config instead, or disable encryption using `encrypted:\"" + _util.ENCRYPTION_OFF + "\"`."));
return;
}
var serverFingerprint = require('crypto').createHash('sha512').update(serverCert.raw).digest("hex");
var knownHostsPath = config.knownHostsPath || _path2.default.join(userHome(), ".neo4j", "known_hosts");
var serverId = config.url.hostAndPort;
loadFingerprint(serverId, knownHostsPath, function (knownFingerprint) {
if (knownFingerprint === serverFingerprint) {
onSuccess();
} else if (knownFingerprint == null) {
storeFingerprint(serverId, knownHostsPath, serverFingerprint, function (err) {
if (err) {
return onFailure(err);
}
return onSuccess();
});
} else {
onFailure((0, _error.newError)("Database encryption certificate has changed, and no longer " + "matches the certificate stored for " + serverId + " in `" + knownHostsPath + "`. As a security precaution, this driver will not automatically trust the new " + "certificate, because doing so would allow an attacker to pretend to be the Neo4j " + "instance we want to connect to. The certificate provided by the server looks like: " + serverCert + ". If you trust that this certificate is valid, simply remove the line " + "starting with " + serverId + " in `" + knownHostsPath + "`, and the driver will " + "update the file with the new certificate. You can configure which file the driver " + "should use to store this information by setting `knownHosts` to another path in " + "your driver configuration - and you can disable encryption there as well using " + "`encrypted:\"" + _util.ENCRYPTION_OFF + "\"`."));
}
});
});
socket.on('error', onFailure);
return socket;
},
TRUST_ALL_CERTIFICATES: function TRUST_ALL_CERTIFICATES(config, onSuccess, onFailure) {
var tlsOpts = {
rejectUnauthorized: false
};
var socket = _tls2.default.connect(config.url.port, config.url.host, tlsOpts, function () {
var certificate = socket.getPeerCertificate();
if ((0, _util.isEmptyObjectOrNull)(certificate)) {
onFailure((0, _error.newError)("Secure connection was successful but server did not return any valid " + "certificates. Such connection can not be trusted. If you are just trying " + " Neo4j out and are not concerned about encryption, simply disable it using " + "`encrypted=\"" + _util.ENCRYPTION_OFF + "\"` in the driver options. " + "Socket responded with: " + socket.authorizationError));
} else {
onSuccess();
}
});
socket.on('error', onFailure);
return socket;
}
};
/**
* Connect using node socket.
* @param {ChannelConfig} config - configuration of this channel.
* @param {function} onSuccess - callback to execute on connection success.
* @param {function} onFailure - callback to execute on connection failure.
* @return {*} socket connection.
*/
function connect(config, onSuccess) {
var onFailure = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {
return null;
};
//still allow boolean for backwards compatibility
if (config.encrypted === false || config.encrypted === _util.ENCRYPTION_OFF) {
var conn = _net2.default.connect(config.url.port, config.url.host, onSuccess);
conn.on('error', onFailure);
return conn;
} else if (TrustStrategy[config.trust]) {
return TrustStrategy[config.trust](config, onSuccess, onFailure);
} else {
onFailure((0, _error.newError)("Unknown trust strategy: " + config.trust + ". Please use either " + "trust:'TRUST_CUSTOM_CA_SIGNED_CERTIFICATES' or trust:'TRUST_ALL_CERTIFICATES' in your driver " + "configuration. Alternatively, you can disable encryption by setting " + "`encrypted:\"" + _util.ENCRYPTION_OFF + "\"`. There is no mechanism to use encryption without trust verification, " + "because this incurs the overhead of encryption without improving security. If " + "the driver does not verify that the peer it is connected to is really Neo4j, it " + "is very easy for an attacker to bypass the encryption by pretending to be Neo4j."));
}
}
/**
* In a Node.js environment the 'net' module is used
* as transport.
* @access private
*/
var NodeChannel = function () {
/**
* Create new instance
* @param {ChannelConfig} config - configuration for this channel.
*/
function NodeChannel(config) {
(0, _classCallCheck3.default)(this, NodeChannel);
var self = this;
this.id = _CONNECTION_IDGEN++;
this.available = true;
this._pending = [];
this._open = true;
this._error = null;
this._handleConnectionError = this._handleConnectionError.bind(this);
this._handleConnectionTerminated = this._handleConnectionTerminated.bind(this);
this._connectionErrorCode = config.connectionErrorCode;
this._encrypted = config.encrypted;
this._conn = connect(config, function () {
if (!self._open) {
return;
}
self._conn.on('data', function (buffer) {
if (self.onmessage) {
self.onmessage(new _buf.NodeBuffer(buffer));
}
});
self._conn.on('error', self._handleConnectionError);
self._conn.on('end', self._handleConnectionTerminated);
// Drain all pending messages
var pending = self._pending;
self._pending = null;
for (var i = 0; i < pending.length; i++) {
self.write(pending[i]);
}
}, this._handleConnectionError);
this._setupConnectionTimeout(config, this._conn);
}
(0, _createClass3.default)(NodeChannel, [{
key: '_handleConnectionError',
value: function _handleConnectionError(err) {
var msg = err.message || 'Failed to connect to server';
this._error = (0, _error.newError)(msg, this._connectionErrorCode);
if (this.onerror) {
this.onerror(this._error);
}
}
}, {
key: '_handleConnectionTerminated',
value: function _handleConnectionTerminated() {
this._error = (0, _error.newError)('Connection was closed by server', this._connectionErrorCode);
if (this.onerror) {
this.onerror(this._error);
}
}
/**
* Setup connection timeout on the socket, if configured.
* @param {ChannelConfig} config - configuration of this channel.
* @param {object} socket - `net.Socket` or `tls.TLSSocket` object.
* @private
*/
}, {
key: '_setupConnectionTimeout',
value: function _setupConnectionTimeout(config, socket) {
var timeout = config.connectionTimeout;
if (timeout) {
socket.on('connect', function () {
// connected - clear connection timeout
socket.setTimeout(0);
});
socket.on('timeout', function () {
// timeout fired - not connected within configured time. cancel timeout and destroy socket
socket.setTimeout(0);
socket.destroy((0, _error.newError)('Failed to establish connection in ' + timeout + 'ms', config.connectionErrorCode));
});
socket.setTimeout(timeout);
}
}
}, {
key: 'isEncrypted',
value: function isEncrypted() {
return this._encrypted;
}
/**
* Write the passed in buffer to connection
* @param {NodeBuffer} buffer - Buffer to write
*/
}, {
key: 'write',
value: function write(buffer) {
// If there is a pending queue, push this on that queue. This means
// we are not yet connected, so we queue things locally.
if (this._pending !== null) {
this._pending.push(buffer);
} else if (buffer instanceof _buf.NodeBuffer) {
// console.log( "[Conn#"+this.id+"] SEND: ", buffer.toString() );
this._conn.write(buffer._buffer);
} else {
throw (0, _error.newError)("Don't know how to write: " + buffer);
}
}
/**
* Close the connection
* @param {function} cb - Function to call on close.
*/
}, {
key: 'close',
value: function close() {
var cb = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {
return null;
};
this._open = false;
if (this._conn) {
this._conn.end();
this._conn.removeListener('end', this._handleConnectionTerminated);
this._conn.on('end', cb);
} else {
cb();
}
}
}]);
return NodeChannel;
}();
var _nodeChannelModule = { channel: NodeChannel, available: true };
try {
// Only define this module if 'net' is available
require.resolve("net");
} catch (e) {
_nodeChannelModule = { available: false };
}
exports.default = _nodeChannelModule;