Skip to content

Commit 634c947

Browse files
committed
fix: skip config validation when using connector
This changeset fixes a problem in the original custom `connector` factory method implementation that required the `server` config property to be defined even though its value is ignored. Removing the validation when `config.options.connector` is defined fixes the problem. Ref: #1540 Fixes: #1541 Signed-off-by: Ruy Adorno <ruyadorno@google.com>
1 parent b78df14 commit 634c947

File tree

3 files changed

+144
-20
lines changed

3 files changed

+144
-20
lines changed

src/connection.ts

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ interface ErrorWithCode extends Error {
331331
}
332332

333333
interface InternalConnectionConfig {
334-
server: string;
334+
server: undefined | string;
335335
authentication: DefaultAuthentication | NtlmAuthentication | AzureActiveDirectoryPasswordAuthentication | AzureActiveDirectoryMsiAppServiceAuthentication | AzureActiveDirectoryMsiVmAuthentication | AzureActiveDirectoryAccessTokenAuthentication | AzureActiveDirectoryServicePrincipalSecret | AzureActiveDirectoryDefaultAuthentication;
336336
options: InternalConnectionOptions;
337337
}
@@ -1061,7 +1061,7 @@ class Connection extends EventEmitter {
10611061
throw new TypeError('The "config" argument is required and must be of type Object.');
10621062
}
10631063

1064-
if (typeof config.server !== 'string') {
1064+
if (typeof config.server !== 'string' && !config.options!.connector) {
10651065
throw new TypeError('The "config.server" property is required and must be of type string.');
10661066
}
10671067

@@ -1352,8 +1352,15 @@ class Connection extends EventEmitter {
13521352
if (typeof config.options.connector !== 'function') {
13531353
throw new TypeError('The "config.options.connector" property must be a function.');
13541354
}
1355+
if (config.server) {
1356+
throw new Error('Server and connector are mutually exclusive, but ' + config.server + ' and a connector function were provided');
1357+
}
1358+
if (config.options.port) {
1359+
throw new Error('Port and connector are mutually exclusive, but ' + config.options.port + ' and a connector function were provided');
1360+
}
13551361

13561362
this.config.options.connector = config.options.connector;
1363+
this.config.options.port = undefined;
13571364
}
13581365

13591366
if (config.options.cryptoCredentialsDetails !== undefined) {
@@ -1917,7 +1924,10 @@ class Connection extends EventEmitter {
19171924
initialiseConnection() {
19181925
const signal = this.createConnectTimer();
19191926

1920-
if (this.config.options.port) {
1927+
if (this.config.options.connector) {
1928+
// port and multiSubnetFailover are not used when using a custom connector
1929+
return this.connectOnPort(0, false, signal, this.config.options.connector);
1930+
} else if (this.config.options.port) {
19211931
return this.connectOnPort(this.config.options.port, this.config.options.multiSubnetFailover, signal, this.config.options.connector);
19221932
} else {
19231933
return instanceLookup({
@@ -1990,7 +2000,7 @@ class Connection extends EventEmitter {
19902000
return new TokenStreamParser(message, this.debug, handler, this.config.options);
19912001
}
19922002

1993-
socketHandlingForSendPreLogin(socket: net.Socket) {
2003+
socketHandlingForSendPreLogin(socket: net.Socket, customConnector: boolean) {
19942004
socket.on('error', (error) => { this.socketError(error); });
19952005
socket.on('close', () => { this.socketClose(); });
19962006
socket.on('end', () => { this.socketEnd(); });
@@ -2002,19 +2012,24 @@ class Connection extends EventEmitter {
20022012
this.socket = socket;
20032013

20042014
this.closed = false;
2005-
this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port);
2015+
const message =
2016+
'connected to ' + this.config.server + ':' + this.config.options.port;
2017+
const customConnectorMessage =
2018+
'connected via custom connector';
2019+
this.debug.log(customConnector ? customConnectorMessage : message);
20062020

20072021
this.sendPreLogin();
20082022
this.transitionTo(this.STATE.SENT_PRELOGIN);
20092023
}
20102024

20112025
wrapWithTls(socket: net.Socket): Promise<tls.TLSSocket> {
20122026
return new Promise((resolve, reject) => {
2027+
const server = String(this.config.server);
20132028
const secureContext = tls.createSecureContext(this.secureContextOptions);
20142029
// If connect to an ip address directly,
20152030
// need to set the servername to an empty string
20162031
// if the user has not given a servername explicitly
2017-
const serverName = !net.isIP(this.config.server) ? this.config.server : '';
2032+
const serverName = !net.isIP(server) ? server : '';
20182033
const encryptOptions = {
20192034
host: this.config.server,
20202035
socket: socket,
@@ -2034,7 +2049,7 @@ class Connection extends EventEmitter {
20342049

20352050
connectOnPort(port: number, multiSubnetFailover: boolean, signal: AbortSignal, customConnector?: () => Promise<net.Socket>) {
20362051
const connectOpts = {
2037-
host: this.routingData ? this.routingData.server : this.config.server,
2052+
host: this.routingData ? this.routingData.server : String(this.config.server),
20382053
port: this.routingData ? this.routingData.port : port,
20392054
localAddress: this.config.options.localAddress
20402055
};
@@ -2055,7 +2070,7 @@ class Connection extends EventEmitter {
20552070
}
20562071
}
20572072

2058-
this.socketHandlingForSendPreLogin(socket);
2073+
this.socketHandlingForSendPreLogin(socket, Boolean(customConnector));
20592074
})().catch((err) => {
20602075
this.clearConnectTimer();
20612076

@@ -2137,8 +2152,10 @@ class Connection extends EventEmitter {
21372152
// otherwise, leave the message empty.
21382153
const routingMessage = this.routingData ? ` (redirected from ${this.config.server}${hostPostfix})` : '';
21392154
const message = `Failed to connect to ${server}${port}${routingMessage} in ${this.config.options.connectTimeout}ms`;
2140-
this.debug.log(message);
2141-
this.emit('connect', new ConnectionError(message, 'ETIMEOUT'));
2155+
const customConnectorMessage = `Failed to connect using custom connector in ${this.config.options.connectTimeout}ms`;
2156+
const errMessage = this.config.options.connector ? customConnectorMessage : message;
2157+
this.debug.log(errMessage);
2158+
this.emit('connect', new ConnectionError(errMessage, 'ETIMEOUT'));
21422159
this.connectTimer = undefined;
21432160
this.dispatchEvent('connectTimeout');
21442161
}
@@ -2273,8 +2290,10 @@ class Connection extends EventEmitter {
22732290
// otherwise, leave the message empty.
22742291
const routingMessage = this.routingData ? ` (redirected from ${this.config.server}${hostPostfix})` : '';
22752292
const message = `Failed to connect to ${server}${port}${routingMessage} - ${error.message}`;
2276-
this.debug.log(message);
2277-
this.emit('connect', new ConnectionError(message, 'ESOCKET'));
2293+
const customConnectorMessage = `Failed to connect using custom connector - ${error.message}`;
2294+
const errMessage = this.config.options.connector ? customConnectorMessage : message;
2295+
this.debug.log(errMessage);
2296+
this.emit('connect', new ConnectionError(errMessage, 'ESOCKET'));
22782297
} else {
22792298
const message = `Connection lost - ${error.message}`;
22802299
this.debug.log(message);
@@ -2299,15 +2318,21 @@ class Connection extends EventEmitter {
22992318
* @private
23002319
*/
23012320
socketClose() {
2302-
this.debug.log('connection to ' + this.config.server + ':' + this.config.options.port + ' closed');
2321+
const message = 'connection to ' + this.config.server + ':' + this.config.options.port + ' closed';
2322+
const customConnectorMessage = 'connection closed';
2323+
this.debug.log(this.config.options.connector ? customConnectorMessage : message);
23032324
if (this.state === this.STATE.REROUTING) {
2304-
this.debug.log('Rerouting to ' + this.routingData!.server + ':' + this.routingData!.port);
2325+
const message = 'Rerouting to ' + this.routingData!.server + ':' + this.routingData!.port;
2326+
const customConnectorMessage = 'Rerouting';
2327+
this.debug.log(this.config.options.connector ? customConnectorMessage : message);
23052328

23062329
this.dispatchEvent('reconnect');
23072330
} else if (this.state === this.STATE.TRANSIENT_FAILURE_RETRY) {
23082331
const server = this.routingData ? this.routingData.server : this.config.server;
23092332
const port = this.routingData ? this.routingData.port : this.config.options.port;
2310-
this.debug.log('Retry after transient failure connecting to ' + server + ':' + port);
2333+
const message = 'Retry after transient failure connecting to ' + server + ':' + port;
2334+
const customConnectorMessage = 'Retry after transient failure connecting';
2335+
this.debug.log(this.config.options.connector ? customConnectorMessage : message);
23112336

23122337
this.dispatchEvent('retry');
23132338
} else {
@@ -3254,7 +3279,8 @@ Connection.prototype.STATE = {
32543279

32553280
try {
32563281
this.transitionTo(this.STATE.SENT_TLSSSLNEGOTIATION);
3257-
await this.messageIo.startTls(this.secureContextOptions, this.config.options.serverName ? this.config.options.serverName : this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate);
3282+
const serverName = this.config.options.serverName ? this.config.options.serverName : String(this.routingData?.server ?? this.config.server);
3283+
await this.messageIo.startTls(this.secureContextOptions, serverName, this.config.options.trustServerCertificate);
32583284
} catch (err: any) {
32593285
return this.socketError(err);
32603286
}

src/instance-lookup.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ const MYSTERY_HEADER_LENGTH = 3;
1414
type LookupFunction = (hostname: string, options: dns.LookupAllOptions, callback: (err: NodeJS.ErrnoException | null, addresses: dns.LookupAddress[]) => void) => void;
1515

1616
// Most of the functionality has been determined from from jTDS's MSSqlServerInfo class.
17-
export async function instanceLookup(options: { server: string, instanceName: string, timeout?: number, retries?: number, port?: number, lookup?: LookupFunction, signal: AbortSignal }) {
17+
export async function instanceLookup(options: { server: undefined | string, instanceName: string, timeout?: number, retries?: number, port?: number, lookup?: LookupFunction, signal: AbortSignal }) {
1818
const server = options.server;
1919
if (typeof server !== 'string') {
2020
throw new TypeError('Invalid arguments: "server" must be a string');
@@ -57,7 +57,7 @@ export async function instanceLookup(options: { server: string, instanceName: st
5757
try {
5858
response = await withTimeout(timeout, async (signal) => {
5959
const request = Buffer.from([0x02]);
60-
return await sendMessage(options.server, port, lookup, signal, request);
60+
return await sendMessage(String(options.server), port, lookup, signal, request);
6161
}, signal);
6262
} catch (err) {
6363
// If the current attempt timed out, continue with the next

test/unit/custom-connector.js

Lines changed: 100 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ describe('custom connector', function() {
2828
const host = server.address().address;
2929
const port = server.address().port;
3030
const connection = new Connection({
31-
server: host,
3231
options: {
3332
connector: async () => {
3433
customConnectorCalled = true;
@@ -37,7 +36,6 @@ describe('custom connector', function() {
3736
port,
3837
});
3938
},
40-
port
4139
},
4240
});
4341

@@ -59,4 +57,104 @@ describe('custom connector', function() {
5957

6058
connection.connect();
6159
});
60+
61+
it('connection timeout using a custom connector', function(done) {
62+
const host = server.address().address;
63+
const port = server.address().port;
64+
const connection = new Connection({
65+
options: {
66+
connectTimeout: 10,
67+
connector: async () => {
68+
return net.connect({
69+
host,
70+
port,
71+
});
72+
},
73+
},
74+
});
75+
76+
// times out since no server response is defined
77+
connection.connect((err) => {
78+
assert.strictEqual(
79+
err.code,
80+
'ETIMEOUT',
81+
'should emit timeout error code'
82+
);
83+
assert.strictEqual(
84+
err.message,
85+
'Failed to connect using custom connector in 10ms',
86+
'should emit expected custom connector timeout error msg'
87+
);
88+
89+
done();
90+
});
91+
});
92+
93+
it('should emit socket error custom connector msg', function(done) {
94+
const connection = new Connection({
95+
options: {
96+
connector: async () => {
97+
throw new Error('ERR');
98+
},
99+
},
100+
});
101+
102+
connection.connect((err) => {
103+
assert.strictEqual(
104+
err.code,
105+
'ESOCKET',
106+
'should emit expected error code'
107+
);
108+
assert.strictEqual(
109+
err.message,
110+
'Failed to connect using custom connector - ERR',
111+
'should emit expected custom connector error msg'
112+
);
113+
done();
114+
});
115+
});
116+
117+
it('should only accept functions', function(done) {
118+
assert.throws(() => {
119+
new Connection({
120+
options: {
121+
connector: 'foo',
122+
},
123+
});
124+
}, Error, 'The "config.options.connector" property must be a function.');
125+
done();
126+
});
127+
128+
it('should not allow setting both server and connector options', function(done) {
129+
assert.throws(() => {
130+
new Connection({
131+
server: '0.0.0.0',
132+
options: {
133+
connector: async () => {},
134+
},
135+
});
136+
}, Error, 'Server and connector are mutually exclusive, but 0.0.0.0 and a connector function were provided');
137+
done();
138+
});
139+
140+
it('should not allow setting both port and connector options', function(done) {
141+
assert.throws(() => {
142+
new Connection({
143+
options: {
144+
connector: async () => {},
145+
port: 8080,
146+
},
147+
});
148+
}, Error, 'Port and connector are mutually exclusive, but 8080 and a connector function were provided');
149+
done();
150+
});
151+
152+
it('should require server config option if custom connector is undefined', function(done) {
153+
assert.throws(() => {
154+
new Connection({
155+
options: { port: 8080 },
156+
});
157+
}, TypeError, 'The "config.server" property is required and must be of type string.');
158+
done();
159+
});
62160
});

0 commit comments

Comments
 (0)