From 4b27035bc7095bef861e6cf645be1a0762183be7 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Tue, 19 Jul 2022 13:04:26 -0700 Subject: [PATCH 01/30] feat: add support for TDS8.0 --- src/connection.ts | 139 ++++++++++++++++++++++++++++++++------ src/message-io.ts | 1 + src/rpcrequest-payload.ts | 5 +- src/tds-versions.ts | 3 +- 4 files changed, 124 insertions(+), 24 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 4ff7146bd..0b3f10a8d 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -1,6 +1,8 @@ import crypto from 'crypto'; import os from 'os'; +import * as tls from 'tls'; import { Socket } from 'net'; +const fs = require('fs'); import constants from 'constants'; import { createSecureContext, SecureContext, SecureContextOptions } from 'tls'; @@ -47,6 +49,7 @@ import AggregateError from 'es-aggregate-error'; import { version } from '../package.json'; import { URL } from 'url'; import { AttentionTokenHandler, InitialSqlTokenHandler, Login7TokenHandler, RequestTokenHandler, TokenHandler } from './token/handler'; +import Procedures from './special-stored-procedure'; let trustServerWarningEmitted = false; @@ -213,6 +216,10 @@ const DEFAULT_LANGUAGE = 'us_english'; * @private */ const DEFAULT_DATEFORMAT = 'mdy'; +/** + * @private + */ + const DEFAULT_ENCRYPT = 'mandatory'; interface AzureActiveDirectoryMsiAppServiceAuthentication { type: 'azure-active-directory-msi-app-service'; @@ -383,7 +390,7 @@ export interface InternalConnectionOptions { enableImplicitTransactions: null | boolean; enableNumericRoundabort: null | boolean; enableQuotedIdentifier: null | boolean; - encrypt: boolean; + encrypt: string | boolean; encryptionKeyStoreProviders: KeyStoreProviderMap | undefined; fallbackToDefaultDb: boolean; instanceName: undefined | string; @@ -404,6 +411,7 @@ export interface InternalConnectionOptions { textsize: number; trustedServerNameAE: string | undefined; trustServerCertificate: boolean; + hostNameInCertificate:string | undefined; useColumnNames: boolean; useUTC: boolean; workstationId: undefined | string; @@ -828,7 +836,15 @@ export interface ConnectionOptions { * (default: `true`) */ trustServerCertificate?: boolean; + /** + * + */ + hostNameInCertificate ?: string; + /** + * + */ + serverName ?: string; /** * A boolean determining whether to return rows as arrays or key-value collections. * @@ -1269,7 +1285,7 @@ class Connection extends EventEmitter { enableImplicitTransactions: false, enableNumericRoundabort: false, enableQuotedIdentifier: true, - encrypt: true, + encrypt: DEFAULT_ENCRYPT, fallbackToDefaultDb: false, encryptionKeyStoreProviders: undefined, instanceName: undefined, @@ -1291,6 +1307,7 @@ class Connection extends EventEmitter { trustedServerNameAE: undefined, trustServerCertificate: true, useColumnNames: false, + hostNameInCertificate :undefined, useUTC: true, workstationId: undefined, lowerCaseGuids: false @@ -1515,11 +1532,37 @@ class Connection extends EventEmitter { } if (config.options.encrypt !== undefined) { - if (typeof config.options.encrypt !== 'boolean') { - throw new TypeError('The "config.options.encrypt" property must be of type boolean.'); + if (typeof config.options.encrypt == 'string') + { + (config.options.encrypt) + { + switch (config.options.encrypt) { + case 'strict': + this.config.options.encrypt = config.options.encrypt; + break; + case 'mandatory': + case 'true': + case 'yes': + this.config.options.encrypt = 'mandatory'; + break; + case 'optional': + case 'false': + case 'no': + this.config.options.encrypt = 'optional'; + break; + default: + throw new TypeError('The "encrypt" property must one of "strict","mandatory","true", "yes" or "optional", "false", "no".'); + } + } + } + else if(typeof config.options.encrypt == 'boolean' && !config.options.encrypt) + { + this.config.options.encrypt = 'optional'; + } + else + { + throw new TypeError('The "config.options.encrypt" property must be of type string or boolean.'); } - - this.config.options.encrypt = config.options.encrypt; } if (config.options.fallbackToDefaultDb !== undefined) { @@ -1678,6 +1721,20 @@ class Connection extends EventEmitter { emitTrustServerCertificateWarning(); } + if (config.options.serverName !== undefined) { + if (typeof config.options.serverName !== 'string') { + throw new TypeError('The "config.options.serverName" property must be of type string.'); + } + this.config.options.serverName = config.options.serverName ; + if (config.options.hostNameInCertificate !== undefined) { + if (typeof config.options.hostNameInCertificate !== 'string') { + throw new TypeError('The "config.options.hostNameInCertificate" property must be of type string.'); + } + + this.config.options.serverName = config.options.hostNameInCertificate; + } + } + if (config.options.useColumnNames !== undefined) { if (typeof config.options.useColumnNames !== 'boolean') { throw new TypeError('The "config.options.useColumnNames" property must be of type boolean.'); @@ -2006,22 +2063,54 @@ class Connection extends EventEmitter { return this.socketError(err); } + // Make socket not type of undefined socket = socket!; - socket.on('error', (error) => { this.socketError(error); }); - socket.on('close', () => { this.socketClose(); }); - socket.on('end', () => { this.socketEnd(); }); - socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); + if(this.config.options.encrypt === 'strict') + { + const encryptOptions = { + host: this.config.server, + socket: socket, + ALPNProtocols: ['tds/8.0'], + servername: this.config.options.serverName ? this.config.options.serverName : this.config.server, + }; + const encryptsocket = tls.connect(encryptOptions, () => { + socket = encryptsocket; + + socket.on('error', (error) => { this.socketError(error); }); + socket.on('close', () => { this.socketClose(); }); + socket.on('end', () => { this.socketEnd(); }); + socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); + + this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); + this.messageIo.on('secure', (cleartext) => { this.emit('secure', cleartext); }); + + this.socket = socket; + + this.closed = false; + this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); - this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); - this.messageIo.on('secure', (cleartext) => { this.emit('secure', cleartext); }); + this.sendPreLogin(); + this.transitionTo(this.STATE.SENT_PRELOGIN); + }) - this.socket = socket; + console.log('using TDS 8.0 strict TLS encryption') + } else { + socket.on('error', (error) => { this.socketError(error); }); + socket.on('close', () => { this.socketClose(); }); + socket.on('end', () => { this.socketEnd(); }); + socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); + + this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); + this.messageIo.on('secure', (cleartext) => { this.emit('secure', cleartext); }); - this.closed = false; - this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); + this.socket = socket; - this.sendPreLogin(); - this.transitionTo(this.STATE.SENT_PRELOGIN); + this.closed = false; + this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); + + this.sendPreLogin(); + this.transitionTo(this.STATE.SENT_PRELOGIN); + } }); } @@ -2264,9 +2353,16 @@ class Connection extends EventEmitter { */ sendPreLogin() { const [ , major, minor, build ] = /^(\d+)\.(\d+)\.(\d+)/.exec(version) ?? [ '0.0.0', '0', '0', '0' ]; - + let encryptSetting = false; + if("strict" !== this.config.options.encrypt) + { + if("mandatory" === this.config.options.encrypt) + { + encryptSetting= true; + } + } const payload = new PreloginPayload({ - encrypt: this.config.options.encrypt, + encrypt: encryptSetting, version: { major: Number(major), minor: Number(minor), build: Number(build), subbuild: 0 } }); @@ -3217,14 +3313,13 @@ Connection.prototype.STATE = { if (preloginPayload.fedAuthRequired === 1) { this.fedAuthRequired = true; } - - if (preloginPayload.encryptionString === 'ON' || preloginPayload.encryptionString === 'REQ') { + if ("strict" != this.config.options.encrypt && (preloginPayload.encryptionString === 'ON' || preloginPayload.encryptionString === 'REQ')) { if (!this.config.options.encrypt) { this.emit('connect', new ConnectionError("Server requires encryption, set 'encrypt' config option to true.", 'EENCRYPT')); return this.close(); } - this.messageIo.startTls(this.secureContext, this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate); + this.messageIo.startTls(this.secureContext, this.config.options.serverName? this.config.options.serverName : this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate); this.transitionTo(this.STATE.SENT_TLSSSLNEGOTIATION); } else { this.sendLogin7Packet(); diff --git a/src/message-io.ts b/src/message-io.ts index 4fba96dcd..e86f6d733 100644 --- a/src/message-io.ts +++ b/src/message-io.ts @@ -65,6 +65,7 @@ class MessageIO extends EventEmitter { const duplexpair = new DuplexPair(); const securePair = this.securePair = { cleartext: tls.connect({ + host:hostname, socket: duplexpair.socket1 as Socket, servername: hostname, secureContext: secureContext, diff --git a/src/rpcrequest-payload.ts b/src/rpcrequest-payload.ts index c19f8ecb3..20ca830ca 100644 --- a/src/rpcrequest-payload.ts +++ b/src/rpcrequest-payload.ts @@ -67,7 +67,10 @@ class RpcRequestPayload implements Iterable { } * generateParameterData(parameter: Parameter) { - const buffer = new WritableTrackingBuffer(1 + 2 + Buffer.byteLength(parameter.name, 'ucs-2') + 1); + const buffer = new WritableTrackingBuffer(1 + 2 + Buffer.byteLength(parameter.name) + 1); + + console.log(parameter); + buffer.writeBVarchar('@' + parameter.name); let statusFlags = 0; diff --git a/src/tds-versions.ts b/src/tds-versions.ts index 10e747997..4b1d485ab 100644 --- a/src/tds-versions.ts +++ b/src/tds-versions.ts @@ -3,7 +3,8 @@ export const versions: { [key: string]: number } = { '7_2': 0x72090002, '7_3_A': 0x730A0003, '7_3_B': 0x730B0003, - '7_4': 0x74000004 + '7_4': 0x74000004, + '8_0': 0x80000005 }; export const versionsByValue: { [key: number]: string } = {}; From 60c1a381aa3d9b3b88e0f9626a9b58ce06b10021 Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Wed, 8 Feb 2023 14:18:30 -0800 Subject: [PATCH 02/30] fix: fix type check for `options.encrypt` (#1517) * fix type check for `options.encrypt` * syntax fix --- src/connection.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 97b9fc447..44b8498d6 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -1518,8 +1518,8 @@ class Connection extends EventEmitter { throw new TypeError('The "encrypt" property must one of "strict","mandatory","true", "yes" or "optional", "false", "no".'); } } - } else if (typeof config.options.encrypt == 'boolean' && !config.options.encrypt) { - this.config.options.encrypt = 'optional'; + } else if (typeof config.options.encrypt == 'boolean') { + config.options.encrypt ? this.config.options.encrypt = 'mandatory' : this.config.options.encrypt = 'optional'; } else { throw new TypeError('The "config.options.encrypt" property must be of type string or boolean.'); } From bf32f8547763dd0fe70e58ebd8047d4ff6d88af2 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Wed, 8 Feb 2023 14:37:57 -0800 Subject: [PATCH 03/30] chore: Add logic for read secure context --- src/connection.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 44b8498d6..4881ae51f 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -49,7 +49,6 @@ import AggregateError from 'es-aggregate-error'; import { version } from '../package.json'; import { URL } from 'url'; import { AttentionTokenHandler, InitialSqlTokenHandler, Login7TokenHandler, RequestTokenHandler, TokenHandler } from './token/handler'; -import Procedures from './special-stored-procedure'; type BeginTransactionCallback = /** @@ -671,7 +670,7 @@ export interface ConnectionOptions { * * (default: `false`) */ - encrypt?: boolean; + encrypt?: string | boolean; /** * By default, if the database requested by [[database]] cannot be accessed, @@ -2018,11 +2017,13 @@ class Connection extends EventEmitter { connect(connectOpts, dns.lookup, signal).then((socket) => { process.nextTick(() => { + const secureContext = tls.createSecureContext(this.secureContextOptions); if (this.config.options.encrypt === 'strict') { const encryptOptions = { host: this.config.server, socket: socket, ALPNProtocols: ['tds/8.0'], + secureContext: secureContext, servername: this.config.options.serverName ? this.config.options.serverName : this.config.server, }; const encryptsocket = tls.connect(encryptOptions, () => { From e458d122cb3458f485987c3876944f87b6026f48 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Wed, 15 Feb 2023 14:27:43 -0800 Subject: [PATCH 04/30] chore: update the PR based on the comments --- src/connection.ts | 65 ++++++++------------------------------- src/rpcrequest-payload.ts | 5 +-- 2 files changed, 13 insertions(+), 57 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 4881ae51f..182f192df 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -196,10 +196,6 @@ const DEFAULT_LANGUAGE = 'us_english'; * @private */ const DEFAULT_DATEFORMAT = 'mdy'; -/** - * @private - */ -const DEFAULT_ENCRYPT = 'mandatory'; interface AzureActiveDirectoryMsiAppServiceAuthentication { type: 'azure-active-directory-msi-app-service'; @@ -391,7 +387,6 @@ export interface InternalConnectionOptions { textsize: number; trustedServerNameAE: string | undefined; trustServerCertificate: boolean; - hostNameInCertificate: string | undefined; useColumnNames: boolean; useUTC: boolean; workstationId: undefined | string; @@ -666,9 +661,10 @@ export interface ConnectionOptions { enableQuotedIdentifier?: boolean; /** - * A boolean determining whether or not the connection will be encrypted. Set to `true` if you're on Windows Azure. + * A string value that can be only set to 'strict', which indicates the usage TDS 8.0 protocol. Otherwise, + * a boolean determining whether or not the connection will be encrypted. * - * (default: `false`) + * (default: `true`) */ encrypt?: string | boolean; @@ -823,10 +819,6 @@ export interface ConnectionOptions { * (default: `true`) */ trustServerCertificate?: boolean; - /** - * - */ - hostNameInCertificate?: string; /** * @@ -1257,7 +1249,7 @@ class Connection extends EventEmitter { enableImplicitTransactions: false, enableNumericRoundabort: false, enableQuotedIdentifier: true, - encrypt: DEFAULT_ENCRYPT, + encrypt: true, fallbackToDefaultDb: false, encryptionKeyStoreProviders: undefined, instanceName: undefined, @@ -1279,7 +1271,6 @@ class Connection extends EventEmitter { trustedServerNameAE: undefined, trustServerCertificate: false, useColumnNames: false, - hostNameInCertificate: undefined, useUTC: true, workstationId: undefined, lowerCaseGuids: false @@ -1494,34 +1485,15 @@ class Connection extends EventEmitter { this.config.options.enableQuotedIdentifier = config.options.enableQuotedIdentifier; } - if (config.options.encrypt !== undefined) { - if (typeof config.options.encrypt == 'string') { - (config.options.encrypt); - { - switch (config.options.encrypt) { - case 'strict': - this.config.options.encrypt = config.options.encrypt; - break; - case 'mandatory': - case 'true': - case 'yes': - this.config.options.encrypt = 'mandatory'; - break; - case 'optional': - case 'false': - case 'no': - this.config.options.encrypt = 'optional'; - break; - default: - throw new TypeError('The "encrypt" property must one of "strict","mandatory","true", "yes" or "optional", "false", "no".'); - } - } - } else if (typeof config.options.encrypt == 'boolean') { - config.options.encrypt ? this.config.options.encrypt = 'mandatory' : this.config.options.encrypt = 'optional'; - } else { + if (typeof config.options.encrypt !== 'boolean' && typeof config.options.encrypt !== 'string') { throw new TypeError('The "config.options.encrypt" property must be of type string or boolean.'); } + if (typeof config.options.encrypt == 'string' && config.options.encrypt !== 'strict') { + throw new TypeError('The "encrypt" property must one of "strict",true or false.'); + } + + this.config.options.encrypt = config.options.encrypt; } if (config.options.fallbackToDefaultDb !== undefined) { @@ -1683,13 +1655,6 @@ class Connection extends EventEmitter { throw new TypeError('The "config.options.serverName" property must be of type string.'); } this.config.options.serverName = config.options.serverName; - if (config.options.hostNameInCertificate !== undefined) { - if (typeof config.options.hostNameInCertificate !== 'string') { - throw new TypeError('The "config.options.hostNameInCertificate" property must be of type string.'); - } - - this.config.options.serverName = config.options.hostNameInCertificate; - } } if (config.options.useColumnNames !== undefined) { @@ -2314,14 +2279,8 @@ class Connection extends EventEmitter { */ sendPreLogin() { const [, major, minor, build] = /^(\d+)\.(\d+)\.(\d+)/.exec(version) ?? ['0.0.0', '0', '0', '0']; - let encryptSetting = false; - if ('strict' !== this.config.options.encrypt) { - if ('mandatory' === this.config.options.encrypt) { - encryptSetting = true; - } - } const payload = new PreloginPayload({ - encrypt: encryptSetting, + encrypt: typeof this.config.options.encrypt === 'boolean' && this.config.options.encrypt, version: { major: Number(major), minor: Number(minor), build: Number(build), subbuild: 0 } }); @@ -3250,7 +3209,7 @@ Connection.prototype.STATE = { try { this.transitionTo(this.STATE.SENT_TLSSSLNEGOTIATION); - this.messageIo.startTls(this.secureContextOptions, this.config.options.serverName ? this.config.options.serverName : this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate); + await this.messageIo.startTls(this.secureContextOptions, this.config.options.serverName ? this.config.options.serverName : this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate); } catch (err: any) { return this.socketError(err); } diff --git a/src/rpcrequest-payload.ts b/src/rpcrequest-payload.ts index 20ca830ca..c19f8ecb3 100644 --- a/src/rpcrequest-payload.ts +++ b/src/rpcrequest-payload.ts @@ -67,10 +67,7 @@ class RpcRequestPayload implements Iterable { } * generateParameterData(parameter: Parameter) { - const buffer = new WritableTrackingBuffer(1 + 2 + Buffer.byteLength(parameter.name) + 1); - - console.log(parameter); - + const buffer = new WritableTrackingBuffer(1 + 2 + Buffer.byteLength(parameter.name, 'ucs-2') + 1); buffer.writeBVarchar('@' + parameter.name); let statusFlags = 0; From 2165a2a1eacf2887d1cca08c21677e57fe5446f9 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Wed, 8 Feb 2023 14:37:57 -0800 Subject: [PATCH 05/30] chore: add logic for read secure context --- src/connection.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 44b8498d6..4881ae51f 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -49,7 +49,6 @@ import AggregateError from 'es-aggregate-error'; import { version } from '../package.json'; import { URL } from 'url'; import { AttentionTokenHandler, InitialSqlTokenHandler, Login7TokenHandler, RequestTokenHandler, TokenHandler } from './token/handler'; -import Procedures from './special-stored-procedure'; type BeginTransactionCallback = /** @@ -671,7 +670,7 @@ export interface ConnectionOptions { * * (default: `false`) */ - encrypt?: boolean; + encrypt?: string | boolean; /** * By default, if the database requested by [[database]] cannot be accessed, @@ -2018,11 +2017,13 @@ class Connection extends EventEmitter { connect(connectOpts, dns.lookup, signal).then((socket) => { process.nextTick(() => { + const secureContext = tls.createSecureContext(this.secureContextOptions); if (this.config.options.encrypt === 'strict') { const encryptOptions = { host: this.config.server, socket: socket, ALPNProtocols: ['tds/8.0'], + secureContext: secureContext, servername: this.config.options.serverName ? this.config.options.serverName : this.config.server, }; const encryptsocket = tls.connect(encryptOptions, () => { From 3a9213a960e4fa0321f23fb2f1365b50f6a6bb5d Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Wed, 15 Feb 2023 14:27:43 -0800 Subject: [PATCH 06/30] chore: update the PR based on the comments --- src/connection.ts | 65 ++++++++------------------------------- src/rpcrequest-payload.ts | 5 +-- 2 files changed, 13 insertions(+), 57 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 4881ae51f..182f192df 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -196,10 +196,6 @@ const DEFAULT_LANGUAGE = 'us_english'; * @private */ const DEFAULT_DATEFORMAT = 'mdy'; -/** - * @private - */ -const DEFAULT_ENCRYPT = 'mandatory'; interface AzureActiveDirectoryMsiAppServiceAuthentication { type: 'azure-active-directory-msi-app-service'; @@ -391,7 +387,6 @@ export interface InternalConnectionOptions { textsize: number; trustedServerNameAE: string | undefined; trustServerCertificate: boolean; - hostNameInCertificate: string | undefined; useColumnNames: boolean; useUTC: boolean; workstationId: undefined | string; @@ -666,9 +661,10 @@ export interface ConnectionOptions { enableQuotedIdentifier?: boolean; /** - * A boolean determining whether or not the connection will be encrypted. Set to `true` if you're on Windows Azure. + * A string value that can be only set to 'strict', which indicates the usage TDS 8.0 protocol. Otherwise, + * a boolean determining whether or not the connection will be encrypted. * - * (default: `false`) + * (default: `true`) */ encrypt?: string | boolean; @@ -823,10 +819,6 @@ export interface ConnectionOptions { * (default: `true`) */ trustServerCertificate?: boolean; - /** - * - */ - hostNameInCertificate?: string; /** * @@ -1257,7 +1249,7 @@ class Connection extends EventEmitter { enableImplicitTransactions: false, enableNumericRoundabort: false, enableQuotedIdentifier: true, - encrypt: DEFAULT_ENCRYPT, + encrypt: true, fallbackToDefaultDb: false, encryptionKeyStoreProviders: undefined, instanceName: undefined, @@ -1279,7 +1271,6 @@ class Connection extends EventEmitter { trustedServerNameAE: undefined, trustServerCertificate: false, useColumnNames: false, - hostNameInCertificate: undefined, useUTC: true, workstationId: undefined, lowerCaseGuids: false @@ -1494,34 +1485,15 @@ class Connection extends EventEmitter { this.config.options.enableQuotedIdentifier = config.options.enableQuotedIdentifier; } - if (config.options.encrypt !== undefined) { - if (typeof config.options.encrypt == 'string') { - (config.options.encrypt); - { - switch (config.options.encrypt) { - case 'strict': - this.config.options.encrypt = config.options.encrypt; - break; - case 'mandatory': - case 'true': - case 'yes': - this.config.options.encrypt = 'mandatory'; - break; - case 'optional': - case 'false': - case 'no': - this.config.options.encrypt = 'optional'; - break; - default: - throw new TypeError('The "encrypt" property must one of "strict","mandatory","true", "yes" or "optional", "false", "no".'); - } - } - } else if (typeof config.options.encrypt == 'boolean') { - config.options.encrypt ? this.config.options.encrypt = 'mandatory' : this.config.options.encrypt = 'optional'; - } else { + if (typeof config.options.encrypt !== 'boolean' && typeof config.options.encrypt !== 'string') { throw new TypeError('The "config.options.encrypt" property must be of type string or boolean.'); } + if (typeof config.options.encrypt == 'string' && config.options.encrypt !== 'strict') { + throw new TypeError('The "encrypt" property must one of "strict",true or false.'); + } + + this.config.options.encrypt = config.options.encrypt; } if (config.options.fallbackToDefaultDb !== undefined) { @@ -1683,13 +1655,6 @@ class Connection extends EventEmitter { throw new TypeError('The "config.options.serverName" property must be of type string.'); } this.config.options.serverName = config.options.serverName; - if (config.options.hostNameInCertificate !== undefined) { - if (typeof config.options.hostNameInCertificate !== 'string') { - throw new TypeError('The "config.options.hostNameInCertificate" property must be of type string.'); - } - - this.config.options.serverName = config.options.hostNameInCertificate; - } } if (config.options.useColumnNames !== undefined) { @@ -2314,14 +2279,8 @@ class Connection extends EventEmitter { */ sendPreLogin() { const [, major, minor, build] = /^(\d+)\.(\d+)\.(\d+)/.exec(version) ?? ['0.0.0', '0', '0', '0']; - let encryptSetting = false; - if ('strict' !== this.config.options.encrypt) { - if ('mandatory' === this.config.options.encrypt) { - encryptSetting = true; - } - } const payload = new PreloginPayload({ - encrypt: encryptSetting, + encrypt: typeof this.config.options.encrypt === 'boolean' && this.config.options.encrypt, version: { major: Number(major), minor: Number(minor), build: Number(build), subbuild: 0 } }); @@ -3250,7 +3209,7 @@ Connection.prototype.STATE = { try { this.transitionTo(this.STATE.SENT_TLSSSLNEGOTIATION); - this.messageIo.startTls(this.secureContextOptions, this.config.options.serverName ? this.config.options.serverName : this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate); + await this.messageIo.startTls(this.secureContextOptions, this.config.options.serverName ? this.config.options.serverName : this.routingData?.server ?? this.config.server, this.config.options.trustServerCertificate); } catch (err: any) { return this.socketError(err); } diff --git a/src/rpcrequest-payload.ts b/src/rpcrequest-payload.ts index 20ca830ca..c19f8ecb3 100644 --- a/src/rpcrequest-payload.ts +++ b/src/rpcrequest-payload.ts @@ -67,10 +67,7 @@ class RpcRequestPayload implements Iterable { } * generateParameterData(parameter: Parameter) { - const buffer = new WritableTrackingBuffer(1 + 2 + Buffer.byteLength(parameter.name) + 1); - - console.log(parameter); - + const buffer = new WritableTrackingBuffer(1 + 2 + Buffer.byteLength(parameter.name, 'ucs-2') + 1); buffer.writeBVarchar('@' + parameter.name); let statusFlags = 0; From b31c287bd8c0b5b8e0abbb30d73dced5e7c50032 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Fri, 17 Feb 2023 10:57:06 -0800 Subject: [PATCH 07/30] chore: changes based on comments --- src/connection.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 182f192df..73f78ee96 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -1486,11 +1486,10 @@ class Connection extends EventEmitter { this.config.options.enableQuotedIdentifier = config.options.enableQuotedIdentifier; } if (config.options.encrypt !== undefined) { - if (typeof config.options.encrypt !== 'boolean' && typeof config.options.encrypt !== 'string') { - throw new TypeError('The "config.options.encrypt" property must be of type string or boolean.'); - } - if (typeof config.options.encrypt == 'string' && config.options.encrypt !== 'strict') { - throw new TypeError('The "encrypt" property must one of "strict",true or false.'); + if (typeof config.options.encrypt !== 'boolean') { + if (config.options.encrypt !== 'strict') { + throw new TypeError('The "encrypt" property must be set to "strict", or of type boolean.'); + } } this.config.options.encrypt = config.options.encrypt; @@ -2280,6 +2279,9 @@ class Connection extends EventEmitter { sendPreLogin() { const [, major, minor, build] = /^(\d+)\.(\d+)\.(\d+)/.exec(version) ?? ['0.0.0', '0', '0', '0']; const payload = new PreloginPayload({ + // If encrypt setting is set to 'strict', then we should have already done the encryption before calling + // this function. Therefore, the encrypt will be set to false here. + // Otherwise, we will set encrypt here based on the encrypt Boolean value from the configuration. encrypt: typeof this.config.options.encrypt === 'boolean' && this.config.options.encrypt, version: { major: Number(major), minor: Number(minor), build: Number(build), subbuild: 0 } }); From 0fcbe8a5610b651de429f8340b3252a19090a503 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Thu, 23 Feb 2023 10:25:51 -0800 Subject: [PATCH 08/30] chore: add tests and fixed some minor issue --- src/connection.ts | 1 - src/tds-versions.ts | 2 +- test/integration/connection-test.js | 46 +++++++++++++++++++---- test/unit/connection-config-validation.js | 8 ++++ 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 73f78ee96..4dff2eacf 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -1999,7 +1999,6 @@ class Connection extends EventEmitter { socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); - this.messageIo.on('secure', (cleartext) => { this.emit('secure', cleartext); }); this.socket = socket; diff --git a/src/tds-versions.ts b/src/tds-versions.ts index 4b1d485ab..7a1ebd90e 100644 --- a/src/tds-versions.ts +++ b/src/tds-versions.ts @@ -4,7 +4,7 @@ export const versions: { [key: string]: number } = { '7_3_A': 0x730A0003, '7_3_B': 0x730B0003, '7_4': 0x74000004, - '8_0': 0x80000005 + '8_0': 0x08000000 }; export const versionsByValue: { [key: number]: string } = {}; diff --git a/test/integration/connection-test.js b/test/integration/connection-test.js index 2022c4588..e1c7f270e 100644 --- a/test/integration/connection-test.js +++ b/test/integration/connection-test.js @@ -9,6 +9,7 @@ const os = require('os'); import Connection from '../../src/connection'; import { ConnectionError, RequestError } from '../../src/errors'; import Request from '../../src/request'; +import { versions } from '../../src/tds-versions'; function getConfig() { const config = JSON.parse( @@ -530,16 +531,37 @@ describe('Ntlm Test', function() { }); describe('Encrypt Test', function() { - it('should encrypt', function(done) { - const config = getConfig(); - config.options.encrypt = true; - + /** + * @this {Mocha.Context} + * @param {Mocha.Done} done + * @param {import("../../src/connection").ConnectionConfiguration} config + * @param {string} tdsKey + * @returns {void} + */ + function runEncryptTest(done, config, tdsKey) { const connection = new Connection(config); connection.connect(function(err) { assert.ifError(err); + connection.execSql(request); + }); - connection.close(); + const request = new Request( + `SELECT c.protocol_version + FROM sys.dm_exec_connections AS c + JOIN sys.dm_exec_sessions AS s + ON c.session_id = s.session_id + WHERE c.session_id = @@SPID`, (err, rowCount) => { + assert.ifError(err); + assert.strictEqual(rowCount, 1); + + connection.close(); + }); + + request.on('row', function(columns) { + // console.log(versions[tdsKey],columns[0].value) + assert.strictEqual(columns.length, 1); + assert.strictEqual(versions[tdsKey], columns[0].value); }); connection.on('end', function() { @@ -547,7 +569,7 @@ describe('Encrypt Test', function() { }); connection.on('databaseChange', function(database) { - assert.strictEqual(database, config.options.database); + assert.strictEqual(database, config.options?.database); }); connection.on('secure', function(cleartext) { @@ -560,9 +582,19 @@ describe('Encrypt Test', function() { // console.log("#{info.number} : #{info.message}") }); - return connection.on('debug', function(text) { + connection.on('debug', function(text) { // console.log(text) }); + } + it('should encrypt', function(done) { + const config = getConfig(); + config.options.encrypt = true; + runEncryptTest.call(this, done, config, '7_4'); + }); + it.only('encrypt with TDS8.0', function(done) { + const config = getConfig(); + config.options.encrypt = 'strict'; + runEncryptTest.call(this, done, config, '8_0'); }); }); diff --git a/test/unit/connection-config-validation.js b/test/unit/connection-config-validation.js index df94c6c0a..3a3a0ad40 100644 --- a/test/unit/connection-config-validation.js +++ b/test/unit/connection-config-validation.js @@ -94,4 +94,12 @@ describe('Connection configuration validation', function() { new Connection(config); }); }); + + it('bad encrypt value', () => { + const numberEncrypt = 0; + config.options.encrypt = numberEncrypt; + assert.throws(() => { + new Connection(config); + }); + }); }); From ef108d6e67927a41227b31d3b8aaa96e221e6f62 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Thu, 2 Mar 2023 15:51:13 -0800 Subject: [PATCH 09/30] chore: remove it.only from test --- test/integration/connection-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/connection-test.js b/test/integration/connection-test.js index e1c7f270e..e8b80f47f 100644 --- a/test/integration/connection-test.js +++ b/test/integration/connection-test.js @@ -591,7 +591,7 @@ describe('Encrypt Test', function() { config.options.encrypt = true; runEncryptTest.call(this, done, config, '7_4'); }); - it.only('encrypt with TDS8.0', function(done) { + it('encrypt with TDS8.0', function(done) { const config = getConfig(); config.options.encrypt = 'strict'; runEncryptTest.call(this, done, config, '8_0'); From a448262c13018374962e09c6d6f443e1c0512317 Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Fri, 3 Mar 2023 09:57:53 -0800 Subject: [PATCH 10/30] test: add additional encrypt config unit tests (#1525) --- test/unit/connection-config-validation.js | 30 ++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/test/unit/connection-config-validation.js b/test/unit/connection-config-validation.js index 3a3a0ad40..5ecb0e9ea 100644 --- a/test/unit/connection-config-validation.js +++ b/test/unit/connection-config-validation.js @@ -95,11 +95,39 @@ describe('Connection configuration validation', function() { }); }); - it('bad encrypt value', () => { + it('bad encrypt value type', () => { const numberEncrypt = 0; config.options.encrypt = numberEncrypt; assert.throws(() => { new Connection(config); }); }); + + it('bad encrypt string', () => { + config.options.encrypt = 'false'; + assert.throws(() => { + new Connection(config); + }); + }); + + it('good false encrypt value', () => { + config.options.encrypt = false; + const connection = new Connection(config); + assert.strictEqual(connection.config.options.encrypt, false); + ensureConnectionIsClosed(connection, () => {}); + }); + + it('good true encrypt value', () => { + config.options.encrypt = true; + const connection = new Connection(config); + assert.strictEqual(connection.config.options.encrypt, true); + ensureConnectionIsClosed(connection, () => {}); + }); + + it('good strict encrypt value', () => { + config.options.encrypt = 'strict'; + const connection = new Connection(config); + assert.strictEqual(connection.config.options.encrypt, 'strict'); + ensureConnectionIsClosed(connection, () => {}); + }); }); From fee6ee743048716692f2118e6f72a500480232ba Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 23 Mar 2023 00:14:06 +0000 Subject: [PATCH 11/30] Add `extendedKeyUsage` option. --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 0af8a2894..66ee48447 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -78,7 +78,7 @@ jobs: - name: Generate TLS Certificate run: | - openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -keyout ./test/fixtures/localhost.key -out ./test/fixtures/localhost.crt + openssl req -x509 -newkey rsa:2048 -nodes -sha256 -addext "extendedKeyUsage = serverAuth" -subj '/CN=localhost' -keyout ./test/fixtures/localhost.key -out ./test/fixtures/localhost.crt - name: Start containers run: | From 30f0ccde6af73ace219dd39e4ee26e5964eb273d Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 23 Mar 2023 00:17:50 +0000 Subject: [PATCH 12/30] Try some other options. --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 66ee48447..ff41735ae 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -78,7 +78,7 @@ jobs: - name: Generate TLS Certificate run: | - openssl req -x509 -newkey rsa:2048 -nodes -sha256 -addext "extendedKeyUsage = serverAuth" -subj '/CN=localhost' -keyout ./test/fixtures/localhost.key -out ./test/fixtures/localhost.crt + openssl req -x509 -newkey rsa:4096 -nodes -addext "extendedKeyUsage = serverAuth" -subj '/CN=localhost' -keyout ./test/fixtures/localhost.key -out ./test/fixtures/localhost.crt - name: Start containers run: | From cbe408930c2cc4864c1775e93357d683246ffd29 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Mon, 27 Mar 2023 00:48:21 +0200 Subject: [PATCH 13/30] Add some config debug output. --- .github/workflows/nodejs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 4d847e125..91239bb38 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -249,6 +249,7 @@ jobs: $output | Out-File -FilePath test\fixtures\mssql.crt -Encoding ascii - name: Set up CI configuration + shell: bash run: | mkdir ~/.tedious @@ -270,6 +271,8 @@ jobs: } }' | jq --arg certificate "$(cat ./test/fixtures/mssql.crt)" '.config.options.cryptoCredentialsDetails.ca |= $certificate' > ~/.tedious/test-connection.json + cat ~/.tedious/test-connection.json + - name: Upgrade npm run: npm install -g npm if: ${{ matrix.node-version == '6.x' }} From 76a146afba13f63d85cc02397e57c27e023bae8d Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Mon, 27 Mar 2023 01:07:22 +0200 Subject: [PATCH 14/30] Ensure proper line endings. --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 91239bb38..af701f702 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -246,7 +246,7 @@ jobs: '-----END CERTIFICATE-----' ) # Output PEM file to the path - $output | Out-File -FilePath test\fixtures\mssql.crt -Encoding ascii + $output -join "`n" | Out-File -FilePath test\fixtures\mssql.crt -Encoding ascii -NoNewLine - name: Set up CI configuration shell: bash From 2252a4372d366ebac369f15b882ba187d7a1cc10 Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:27:12 -0700 Subject: [PATCH 15/30] ci: add latest build of sql server 2022 --- .github/workflows/nodejs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index af701f702..15fef8629 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -192,10 +192,10 @@ jobs: run: | Push-Location C:\temp - Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.exe -OutFile sqlsetup.exe - Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.box -OutFile sqlsetup.box + Invoke-WebRequest -Uri https://download.microsoft.com/download/c/c/9/cc9c6797-383c-4b24-8920-dc057c1de9d3/SQL2022-SSEI-Dev.exe -OutFile SQL2022-SSEI-Dev.exe + ./SQL2022-SSEI-Dev.exe /ACTION=Download /MEDIAPATH=C:\temp /MEDIATYPE=CAB /QUIET - Start-Process -Wait -FilePath ./sqlsetup.exe -ArgumentList /qs, /x:setup + Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-ENU.exe -ArgumentList /qs, /x:setup .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=0 /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS From 7818752ac9de374d5abf1bb0ec4b38837b381cf3 Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:37:38 -0700 Subject: [PATCH 16/30] wait for files to finish download --- .github/workflows/nodejs.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 15fef8629..3fadd8464 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -193,8 +193,7 @@ jobs: Push-Location C:\temp Invoke-WebRequest -Uri https://download.microsoft.com/download/c/c/9/cc9c6797-383c-4b24-8920-dc057c1de9d3/SQL2022-SSEI-Dev.exe -OutFile SQL2022-SSEI-Dev.exe - ./SQL2022-SSEI-Dev.exe /ACTION=Download /MEDIAPATH=C:\temp /MEDIATYPE=CAB /QUIET - + Start-Process -Wait -FilePath ./SQL2022-SSEI-Dev.exe -ArgumentList /ACTION=Download, /MEDIAPATH=C:\temp, /MEDIATYPE=CAB, /QUIET Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-ENU.exe -ArgumentList /qs, /x:setup .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=0 /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS From 53dd4868feaf5421894aa5c06ab99c040758b39b Mon Sep 17 00:00:00 2001 From: mShan0 <96149598+mShan0@users.noreply.github.com> Date: Mon, 27 Mar 2023 16:55:23 -0700 Subject: [PATCH 17/30] enable updates --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 3fadd8464..05622c526 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -196,7 +196,7 @@ jobs: Start-Process -Wait -FilePath ./SQL2022-SSEI-Dev.exe -ArgumentList /ACTION=Download, /MEDIAPATH=C:\temp, /MEDIATYPE=CAB, /QUIET Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-ENU.exe -ArgumentList /qs, /x:setup - .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=0 /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS + .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=1 /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS Set-ItemProperty -path "HKLM:\Software\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQLSERVER\" -Name LoginMode -Value 2 Restart-Service MSSQLSERVER From e5b4c6faa3457a36eae8ec658fb9d5dfb7e6c0bb Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Tue, 28 Mar 2023 23:54:41 +0200 Subject: [PATCH 18/30] ci: revert to download box files directly, also download and install latest cumulative update file --- .github/workflows/nodejs.yml | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 05622c526..ec7c62638 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -192,17 +192,14 @@ jobs: run: | Push-Location C:\temp - Invoke-WebRequest -Uri https://download.microsoft.com/download/c/c/9/cc9c6797-383c-4b24-8920-dc057c1de9d3/SQL2022-SSEI-Dev.exe -OutFile SQL2022-SSEI-Dev.exe - Start-Process -Wait -FilePath ./SQL2022-SSEI-Dev.exe -ArgumentList /ACTION=Download, /MEDIAPATH=C:\temp, /MEDIATYPE=CAB, /QUIET - Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-ENU.exe -ArgumentList /qs, /x:setup - - .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=1 /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS + Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.exe -OutFile sqlsetup.exe + Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.box -OutFile sqlsetup.box + Invoke-WebRequest -Uri https://download.microsoft.com/download/9/6/8/96819b0c-c8fb-4b44-91b5-c97015bbda9f/SQLServer2022-KB5023127-x64.exe -Outfile C:\temp\updates\SQLServer2022-KB5023127-x64.exe - Set-ItemProperty -path "HKLM:\Software\Microsoft\Microsoft SQL Server\MSSQL16.MSSQLSERVER\MSSQLSERVER\" -Name LoginMode -Value 2 - Restart-Service MSSQLSERVER + Start-Process -Wait -FilePath ./SQL2022-SSEI-Dev.exe -ArgumentList /ACTION=Download, /MEDIAPATH=C:\temp, /MEDIATYPE=CAB, /QUIET + Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-DEU.exe -ArgumentList /qs, /x:setup - sqlcmd -S localhost -q "ALTER LOGIN [sa] WITH PASSWORD=N'yourStrong(!)Password'" - sqlcmd -S localhost -q "ALTER LOGIN [sa] ENABLE" + .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=1 /UpdateSource=C:\temp\updates /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS /SECURITYMODE=SQL /SAPWD="yourStrong(!)Password" Pop-Location From d5f2bdbe1efb94c8c998c3821d5603e6f79ee83b Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Tue, 28 Mar 2023 23:58:47 +0200 Subject: [PATCH 19/30] ci: fix update download path --- .github/workflows/nodejs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ec7c62638..20a7e0d23 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -194,12 +194,12 @@ jobs: Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.exe -OutFile sqlsetup.exe Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.box -OutFile sqlsetup.box - Invoke-WebRequest -Uri https://download.microsoft.com/download/9/6/8/96819b0c-c8fb-4b44-91b5-c97015bbda9f/SQLServer2022-KB5023127-x64.exe -Outfile C:\temp\updates\SQLServer2022-KB5023127-x64.exe + Invoke-WebRequest -Uri https://download.microsoft.com/download/9/6/8/96819b0c-c8fb-4b44-91b5-c97015bbda9f/SQLServer2022-KB5023127-x64.exe -Outfile SQLServer2022-KB5023127-x64.exe Start-Process -Wait -FilePath ./SQL2022-SSEI-Dev.exe -ArgumentList /ACTION=Download, /MEDIAPATH=C:\temp, /MEDIATYPE=CAB, /QUIET Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-DEU.exe -ArgumentList /qs, /x:setup - .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=1 /UpdateSource=C:\temp\updates /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS /SECURITYMODE=SQL /SAPWD="yourStrong(!)Password" + .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=1 /UpdateSource=C:\temp /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS /SECURITYMODE=SQL /SAPWD="yourStrong(!)Password" Pop-Location From 81c52a294de268dcb9c2e40c45d76455c6b8a0b8 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Wed, 29 Mar 2023 00:04:01 +0200 Subject: [PATCH 20/30] ci: fix starting the sql server install --- .github/workflows/nodejs.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 20a7e0d23..1924e3dff 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -192,12 +192,11 @@ jobs: run: | Push-Location C:\temp - Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.exe -OutFile sqlsetup.exe - Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.box -OutFile sqlsetup.box + Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.exe -OutFile SQLServer2022-DEV-x64-ENU.exe + Invoke-WebRequest -Uri https://download.microsoft.com/download/3/8/d/38de7036-2433-4207-8eae-06e247e17b25/SQLServer2022-DEV-x64-ENU.box -OutFile SQLServer2022-DEV-x64-ENU.box Invoke-WebRequest -Uri https://download.microsoft.com/download/9/6/8/96819b0c-c8fb-4b44-91b5-c97015bbda9f/SQLServer2022-KB5023127-x64.exe -Outfile SQLServer2022-KB5023127-x64.exe - Start-Process -Wait -FilePath ./SQL2022-SSEI-Dev.exe -ArgumentList /ACTION=Download, /MEDIAPATH=C:\temp, /MEDIATYPE=CAB, /QUIET - Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-DEU.exe -ArgumentList /qs, /x:setup + Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-ENU.exe -ArgumentList /qs, /x:setup .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=1 /UpdateSource=C:\temp /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /USESQLRECOMMENDEDMEMORYLIMITS /SECURITYMODE=SQL /SAPWD="yourStrong(!)Password" From f675c79a79b6330d7573dcae50c4cf3ae1292d32 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Tue, 28 Mar 2023 16:29:49 -0700 Subject: [PATCH 21/30] chore: fix test for other protocol versions --- test/integration/connection-test.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/integration/connection-test.js b/test/integration/connection-test.js index e8b80f47f..267771405 100644 --- a/test/integration/connection-test.js +++ b/test/integration/connection-test.js @@ -535,10 +535,9 @@ describe('Encrypt Test', function() { * @this {Mocha.Context} * @param {Mocha.Done} done * @param {import("../../src/connection").ConnectionConfiguration} config - * @param {string} tdsKey * @returns {void} */ - function runEncryptTest(done, config, tdsKey) { + function runEncryptTest(done, config) { const connection = new Connection(config); connection.connect(function(err) { @@ -559,9 +558,13 @@ describe('Encrypt Test', function() { }); request.on('row', function(columns) { - // console.log(versions[tdsKey],columns[0].value) + // console.log(versions[tdsKey],columns[0].value) assert.strictEqual(columns.length, 1); - assert.strictEqual(versions[tdsKey], columns[0].value); + if ('strict' === connection.config.options.encrypt) { + assert.strictEqual(versions['8_0'], columns[0].value); + } else { + assert.strictEqual(versions[connection.config.options.tdsVersion], columns[0].value); + } }); connection.on('end', function() { @@ -589,12 +592,12 @@ describe('Encrypt Test', function() { it('should encrypt', function(done) { const config = getConfig(); config.options.encrypt = true; - runEncryptTest.call(this, done, config, '7_4'); + runEncryptTest.call(this, done, config); }); it('encrypt with TDS8.0', function(done) { const config = getConfig(); config.options.encrypt = 'strict'; - runEncryptTest.call(this, done, config, '8_0'); + runEncryptTest.call(this, done, config); }); }); From 04c76a7afadbd8196ec80f01cc5a734c0998c934 Mon Sep 17 00:00:00 2001 From: Michael Sun Date: Thu, 6 Apr 2023 10:06:57 -0700 Subject: [PATCH 22/30] chore: code style change and error handling --- src/connection.ts | 107 +++++++++++++++++++---------------- test/unit/connection-test.ts | 45 +++++++++++++++ 2 files changed, 103 insertions(+), 49 deletions(-) create mode 100644 test/unit/connection-test.ts diff --git a/src/connection.ts b/src/connection.ts index 4dff2eacf..151b64133 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -1,7 +1,7 @@ import crypto from 'crypto'; import os from 'os'; import * as tls from 'tls'; -import { Socket } from 'net'; +import * as net from 'net'; import dns from 'dns'; import constants from 'constants'; @@ -989,7 +989,7 @@ class Connection extends EventEmitter { /** * @private */ - socket: undefined | Socket; + socket: undefined | net.Socket; /** * @private */ @@ -1970,6 +1970,48 @@ class Connection extends EventEmitter { return new TokenStreamParser(message, this.debug, handler, this.config.options); } + socketHandlingForSendPreLogin(socket: net.Socket) { + socket.on('error', (error) => { this.socketError(error); }); + socket.on('close', () => { this.socketClose(); }); + socket.on('end', () => { this.socketEnd(); }); + socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); + + this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); + this.messageIo.on('secure', (cleartext) => { this.emit('secure', cleartext); }); + + this.socket = socket; + + this.closed = false; + this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); + + this.sendPreLogin(); + this.transitionTo(this.STATE.SENT_PRELOGIN); + } + + wrapWithTls(socket: net.Socket): Promise { + return new Promise((resolve, reject) => { + const secureContext = tls.createSecureContext(this.secureContextOptions); + // If connect to an ip address directly, + // need to set the servername to an empty string + // if the user has not given a servername explicitly + const serverName = !net.isIP(this.config.server) ? this.config.server : ''; + const encryptOptions = { + host: this.config.server, + socket: socket, + ALPNProtocols: ['tds/8.0'], + secureContext: secureContext, + servername: this.config.options.serverName ? this.config.options.serverName : serverName, + }; + + const encryptsocket = tls.connect(encryptOptions, () => { + encryptsocket.removeListener('error', reject); + resolve(encryptsocket); + }); + + encryptsocket.once('error', reject); + }); + } + connectOnPort(port: number, multiSubnetFailover: boolean, signal: AbortSignal) { const connectOpts = { host: this.routingData ? this.routingData.server : this.config.server, @@ -1979,57 +2021,24 @@ class Connection extends EventEmitter { const connect = multiSubnetFailover ? connectInParallel : connectInSequence; - connect(connectOpts, dns.lookup, signal).then((socket) => { - process.nextTick(() => { - const secureContext = tls.createSecureContext(this.secureContextOptions); - if (this.config.options.encrypt === 'strict') { - const encryptOptions = { - host: this.config.server, - socket: socket, - ALPNProtocols: ['tds/8.0'], - secureContext: secureContext, - servername: this.config.options.serverName ? this.config.options.serverName : this.config.server, - }; - const encryptsocket = tls.connect(encryptOptions, () => { - socket = encryptsocket; - - socket.on('error', (error) => { this.socketError(error); }); - socket.on('close', () => { this.socketClose(); }); - socket.on('end', () => { this.socketEnd(); }); - socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); - - this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); - - this.socket = socket; - - this.closed = false; - this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); - - this.sendPreLogin(); - this.transitionTo(this.STATE.SENT_PRELOGIN); - }); - - console.log('using TDS 8.0 strict TLS encryption'); - } else { - socket.on('error', (error) => { this.socketError(error); }); - socket.on('close', () => { this.socketClose(); }); - socket.on('end', () => { this.socketEnd(); }); - socket.setKeepAlive(true, KEEP_ALIVE_INITIAL_DELAY); - - this.messageIo = new MessageIO(socket, this.config.options.packetSize, this.debug); - this.messageIo.on('secure', (cleartext) => { this.emit('secure', cleartext); }); - - this.socket = socket; + (async () => { + let socket = await connect(connectOpts, dns.lookup, signal); - this.closed = false; - this.debug.log('connected to ' + this.config.server + ':' + this.config.options.port); + if (this.config.options.encrypt === 'strict') { + try { + // Wrap the socket with TLS for TDS 8.0 + socket = await this.wrapWithTls(socket); + } catch (err) { + socket.end(); - this.sendPreLogin(); - this.transitionTo(this.STATE.SENT_PRELOGIN); + throw err; } - }); - }, (err) => { + } + + this.socketHandlingForSendPreLogin(socket); + })().catch((err) => { this.clearConnectTimer(); + if (err.name === 'AbortError') { return; } diff --git a/test/unit/connection-test.ts b/test/unit/connection-test.ts new file mode 100644 index 000000000..4067fa3d2 --- /dev/null +++ b/test/unit/connection-test.ts @@ -0,0 +1,45 @@ +import * as net from 'net'; +import { Connection } from '../../src/tedious'; +import { assert } from 'chai'; + +describe('Using `strict` encryption', function() { + let server: net.Server; + + beforeEach(function(done) { + server = net.createServer(); + server.listen(0, '127.0.0.1', done); + }); + + afterEach(function(done) { + server.close(done); + }); + + it('does not throw an unhandled exception if the tls handshake fails', function(done) { + server.on('connection', (connection) => { + console.log('incoming connection'); + + connection.on('data', () => { + // Ignore all incoming data + }); + + setTimeout(() => { + connection.end(); + }, 50); + }); + + const connection = new Connection({ + server: (server.address() as net.AddressInfo).address, + options: { + port: (server.address() as net.AddressInfo).port, + encrypt: 'strict' + } + }); + + connection.connect((err) => { + assert.instanceOf(err, Error); + assert.include(err!.message, 'Client network socket disconnected before secure TLS connection was established'); + + done(); + }); + }); +}); From 3b8074d5dbb75dfdb995da14d49942a06777e568 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Sat, 8 Apr 2023 13:05:39 +0000 Subject: [PATCH 23/30] Fix incorrect merge conflict resolution. --- .github/workflows/nodejs.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 0484f65e1..ab74bff35 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -227,7 +227,7 @@ jobs: Invoke-WebRequest -Uri $box_link -OutFile sqlsetup.box Invoke-WebRequest -Uri $update_link -Outfile sqlupdate.exe - Start-Process -Wait -FilePath ./SQLServer2022-DEV-x64-ENU.exe -ArgumentList /qs, /x:setup + Start-Process -Wait -FilePath ./sqlsetup.exe -ArgumentList /qs, /x:setup .\setup\setup.exe /q /ACTION=Install /INSTANCENAME=MSSQLSERVER /FEATURES=SQLEngine /UPDATEENABLED=1 /UpdateSource=C:\temp /SQLSVCACCOUNT='NT SERVICE\MSSQLSERVER' /SQLSYSADMINACCOUNTS='BUILTIN\ADMINISTRATORS' /TCPENABLED=1 /NPENABLED=0 /IACCEPTSQLSERVERLICENSETERMS /SQLCOLLATION=SQL_Latin1_General_CP1_CI_AS /SECURITYMODE=SQL /SAPWD="yourStrong(!)Password" @@ -297,8 +297,6 @@ jobs: } }' | jq --arg certificate "$(cat ./test/fixtures/mssql.crt)" '.config.options.cryptoCredentialsDetails.ca |= $certificate' > ~/.tedious/test-connection.json - cat ~/.tedious/test-connection.json - - name: Upgrade npm run: npm install -g npm if: ${{ matrix.node-version == '6.x' }} From f2e45ad9ca7a113be517bba4a23f1accf0794ac4 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Wed, 31 May 2023 20:49:55 +0000 Subject: [PATCH 24/30] test: rework to skip if TDS 8.0 is not available --- test/integration/connection-test.js | 151 +++++++++++++++++++--------- 1 file changed, 106 insertions(+), 45 deletions(-) diff --git a/test/integration/connection-test.js b/test/integration/connection-test.js index 267771405..680c38ae0 100644 --- a/test/integration/connection-test.js +++ b/test/integration/connection-test.js @@ -532,72 +532,133 @@ describe('Ntlm Test', function() { describe('Encrypt Test', function() { /** - * @this {Mocha.Context} - * @param {Mocha.Done} done - * @param {import("../../src/connection").ConnectionConfiguration} config - * @returns {void} + * @param {any} config + * @param {(err: Error | null, supportsTds8?: boolean) => void} callback */ - function runEncryptTest(done, config) { + function supportsTds8(config, callback) { const connection = new Connection(config); - connection.connect(function(err) { - assert.ifError(err); + connection.connect((err) => { + if (err) { + return callback(err); + } + + let supportsTds8 = false; + + const request = new Request("SELECT host_platform, SERVERPROPERTY('ProductMajorVersion') FROM sys.dm_os_host_info", (err) => { + connection.close(); + + if (err) { + return callback(err); + } + + callback(null, supportsTds8); + }); + + request.on('row', (row) => { + supportsTds8 = row[0].value !== 'Linux' && row[1].value >= '2022'; + }); + connection.execSql(request); }); + } + + describe('with strict encryption enabled (TDS 8.0)', function() { + /** + * @type {Connection} + */ + let connection; + + beforeEach(function(done) { + const config = getConfig(); + + supportsTds8(config, (err, supportsTds8) => { + if (err) { + return done(err); + } + + if (!supportsTds8) { + return this.skip(); + } + + config.options.encrypt = 'strict'; + + connection = new Connection(config); + connection.connect(done); + }); + }); + + afterEach(function() { + connection && connection.close(); + }); + + it('opens an encrypted connection', function(done) { + const request = new Request(` + SELECT c.protocol_version, c.encrypt_option + FROM sys.dm_exec_connections AS c + WHERE c.session_id = @@SPID + `, (err, rowCount) => { + if (err) { + return done(err); + } - const request = new Request( - `SELECT c.protocol_version - FROM sys.dm_exec_connections AS c - JOIN sys.dm_exec_sessions AS s - ON c.session_id = s.session_id - WHERE c.session_id = @@SPID`, (err, rowCount) => { assert.ifError(err); assert.strictEqual(rowCount, 1); - connection.close(); + done(); }); - request.on('row', function(columns) { - // console.log(versions[tdsKey],columns[0].value) - assert.strictEqual(columns.length, 1); - if ('strict' === connection.config.options.encrypt) { + request.on('row', function(columns) { + assert.strictEqual(columns.length, 2); assert.strictEqual(versions['8_0'], columns[0].value); - } else { - assert.strictEqual(versions[connection.config.options.tdsVersion], columns[0].value); - } - }); + assert.strictEqual('TRUE', columns[1].value); + }); - connection.on('end', function() { - done(); + connection.execSql(request); }); + }); - connection.on('databaseChange', function(database) { - assert.strictEqual(database, config.options?.database); - }); + describe('with encryption enabled', function() { + /** + * @type {Connection} + */ + let connection; - connection.on('secure', function(cleartext) { - assert.ok(cleartext); - assert.ok(cleartext.getCipher()); - assert.ok(cleartext.getPeerCertificate()); + beforeEach(function(done) { + const config = getConfig(); + config.options.encrypt = true; + connection = new Connection(config); + connection.connect(done); }); - connection.on('infoMessage', function(info) { - // console.log("#{info.number} : #{info.message}") + afterEach(function() { + connection.close(); }); - connection.on('debug', function(text) { - // console.log(text) + it('opens an encrypted connection', function(done) { + const request = new Request(` + SELECT c.protocol_version, c.encrypt_option + FROM sys.dm_exec_connections AS c + WHERE c.session_id = @@SPID + `, (err, rowCount) => { + if (err) { + return done(err); + } + + assert.ifError(err); + assert.strictEqual(rowCount, 1); + + done(); + }); + + request.on('row', function(columns) { + assert.strictEqual(columns.length, 2); + assert.strictEqual(versions[connection.config.options.tdsVersion], columns[0].value); + assert.strictEqual('TRUE', columns[1].value); + }); + + connection.execSql(request); }); - } - it('should encrypt', function(done) { - const config = getConfig(); - config.options.encrypt = true; - runEncryptTest.call(this, done, config); - }); - it('encrypt with TDS8.0', function(done) { - const config = getConfig(); - config.options.encrypt = 'strict'; - runEncryptTest.call(this, done, config); }); }); From c2aa47f332a2bdad13317da2dedcc061227b946b Mon Sep 17 00:00:00 2001 From: Michael Sun <47126816+MichaelSun90@users.noreply.github.com> Date: Wed, 7 Jun 2023 13:20:04 -0700 Subject: [PATCH 25/30] fix: remove the unecessary error output form connection-test --- test/integration/connection-test.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/integration/connection-test.js b/test/integration/connection-test.js index 680c38ae0..1d64fded0 100644 --- a/test/integration/connection-test.js +++ b/test/integration/connection-test.js @@ -29,10 +29,6 @@ function getConfig() { return config; } -process.on('uncaughtException', function(err) { - console.error(err.stack); -}); - function getInstanceName() { return JSON.parse( fs.readFileSync(homedir + '/.tedious/test-connection.json', 'utf8') From 47bd5872f7c2dd3bc587e1ea96726876dec60fbf Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Wed, 28 Jun 2023 20:02:58 +0000 Subject: [PATCH 26/30] Only check `sys.dm_os_host_info` on SQL Server versions where it is available. --- test/integration/connection-test.js | 40 ++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/test/integration/connection-test.js b/test/integration/connection-test.js index 1d64fded0..f29944aa4 100644 --- a/test/integration/connection-test.js +++ b/test/integration/connection-test.js @@ -539,20 +539,46 @@ describe('Encrypt Test', function() { return callback(err); } - let supportsTds8 = false; - - const request = new Request("SELECT host_platform, SERVERPROPERTY('ProductMajorVersion') FROM sys.dm_os_host_info", (err) => { - connection.close(); - + /** + * @type {string | undefined} + */ + let productMajorVersion; + const request = new Request("SELECT SERVERPROPERTY('ProductMajorVersion')", (err) => { if (err) { + connection.close(); return callback(err); } - callback(null, supportsTds8); + if (!productMajorVersion || productMajorVersion < '2022') { + connection.close(); + return callback(null, false); + } + + if (productMajorVersion > '2022') { + connection.close(); + return callback(null, true); + } + + let supportsTds8 = false; + const request = new Request("SELECT host_platform FROM sys.dm_os_host_info", (err) => { + connection.close(); + + if (err) { + return callback(err); + } + + callback(null, supportsTds8); + }); + + request.on('row', (row) => { + supportsTds8 = row[0].value !== 'Linux'; + }); + + connection.execSql(request); }); request.on('row', (row) => { - supportsTds8 = row[0].value !== 'Linux' && row[1].value >= '2022'; + productMajorVersion = row[0].value; }); connection.execSql(request); From 20ef4041f5209bd36312ba868699070498e6999a Mon Sep 17 00:00:00 2001 From: Michael Sun <47126816+MichaelSun90@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:10:53 +0000 Subject: [PATCH 27/30] chore: lint issue --- test/integration/connection-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/connection-test.js b/test/integration/connection-test.js index f29944aa4..24b111a4a 100644 --- a/test/integration/connection-test.js +++ b/test/integration/connection-test.js @@ -560,7 +560,7 @@ describe('Encrypt Test', function() { } let supportsTds8 = false; - const request = new Request("SELECT host_platform FROM sys.dm_os_host_info", (err) => { + const request = new Request('SELECT host_platform FROM sys.dm_os_host_info', (err) => { connection.close(); if (err) { From c20367ba33d31a71c8adaa415d2526c8e6f01dbf Mon Sep 17 00:00:00 2001 From: Michael Sun <47126816+MichaelSun90@users.noreply.github.com> Date: Thu, 29 Jun 2023 17:21:25 +0000 Subject: [PATCH 28/30] chore: lint issue for socket --- src/connection.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/connection.ts b/src/connection.ts index 33b2b29cf..4329606c9 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -345,7 +345,7 @@ export interface InternalConnectionOptions { columnEncryptionSetting: boolean; columnNameReplacer: undefined | ((colName: string, index: number, metadata: Metadata) => string); connectionRetryInterval: number; - connector: undefined | (() => Promise); + connector: undefined | (() => Promise); connectTimeout: number; connectionIsolationLevel: typeof ISOLATION_LEVEL[keyof typeof ISOLATION_LEVEL]; cryptoCredentialsDetails: SecureContextOptions; @@ -543,7 +543,7 @@ export interface ConnectionOptions { * * (default: `undefined`) */ - connector?: () => Promise; + connector?: () => Promise; /** * The number of milliseconds before the attempt to connect is considered failed @@ -2032,7 +2032,7 @@ class Connection extends EventEmitter { }); } - connectOnPort(port: number, multiSubnetFailover: boolean, signal: AbortSignal, customConnector?: () => Promise) { + connectOnPort(port: number, multiSubnetFailover: boolean, signal: AbortSignal, customConnector?: () => Promise) { const connectOpts = { host: this.routingData ? this.routingData.server : this.config.server, port: this.routingData ? this.routingData.port : port, From a9524dc426e1dfa91712ab31434ccaefa95b7bad Mon Sep 17 00:00:00 2001 From: Michael Sun <47126816+MichaelSun90@users.noreply.github.com> Date: Thu, 29 Jun 2023 13:53:11 -0700 Subject: [PATCH 29/30] chore: connection to certain db --- test/integration/parameterised-statements-test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/parameterised-statements-test.js b/test/integration/parameterised-statements-test.js index a5a6fd0cb..5b24db83e 100644 --- a/test/integration/parameterised-statements-test.js +++ b/test/integration/parameterised-statements-test.js @@ -989,6 +989,7 @@ describe('Parameterised Statements Test', function() { it('supports TVP values', function(done) { const config = getConfig(); + config.options.database = 'tempdb'; const connection = new Connection(config); if (config.options.tdsVersion < '7_3_A') { @@ -1003,7 +1004,6 @@ describe('Parameterised Statements Test', function() { async.series([ (next) => { connection.execSqlBatch(new Request(` - USE tempdb; BEGIN TRY DROP TYPE TediousTestType END TRY @@ -1013,7 +1013,6 @@ describe('Parameterised Statements Test', function() { }, (next) => { connection.execSqlBatch(new Request(` - USE tempdb; CREATE TYPE TediousTestType AS TABLE ( a bit, b tinyint, From c3e8b67297b8c89f0814c1ca5ba3bde91f70954e Mon Sep 17 00:00:00 2001 From: Michael Sun <47126816+MichaelSun90@users.noreply.github.com> Date: Wed, 19 Jul 2023 13:09:06 -0700 Subject: [PATCH 30/30] chore: remove the db constrain --- test/integration/parameterised-statements-test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/parameterised-statements-test.js b/test/integration/parameterised-statements-test.js index 5b24db83e..e66ce2ca5 100644 --- a/test/integration/parameterised-statements-test.js +++ b/test/integration/parameterised-statements-test.js @@ -989,7 +989,6 @@ describe('Parameterised Statements Test', function() { it('supports TVP values', function(done) { const config = getConfig(); - config.options.database = 'tempdb'; const connection = new Connection(config); if (config.options.tdsVersion < '7_3_A') {