diff --git a/src_js/index.js b/src_js/index.js index 21d28e0..bc5a480 100644 --- a/src_js/index.js +++ b/src_js/index.js @@ -1,17 +1,18 @@ -'use strict'; - var os = require('os'); -// require and export only for platforms where the module is supported. -// Individual module require'ed use features of node.js that would trigger -// syntax errors in older versions. This allows for the module to be included -// in applications like Tedious which supports older version of node.js, even -// if the functionality itself won't be available. The application can decide -// what to do if the module is not supported on the platform where it's running. -if (require('semver').gte(process.version, '4.0.0') && os.type() === 'Windows_NT') { - module.exports.ModuleSupported = true; +const NativeAuthProvider = require('./native.js').NativeAuthProvider; - module.exports.SspiClientApi = require('./sspi_client'); - module.exports.Fqdn = require('./fqdn'); - module.exports.MakeSpn = require('./make_spn'); -} +module.exports = function(options) { + // require and export only for platforms where the module is supported. + // Individual module require'ed use features of node.js that would trigger + // syntax errors in older versions. This allows for the module to be included + // in applications like Tedious which supports older version of node.js, even + // if the functionality itself won't be available. The application can decide + // what to do if the module is not supported on the platform where it's running. + if (require('semver').gte(process.version, '4.0.0') && os.type() === 'Windows_NT') { + return function(connection) { + return new NativeAuthProvider(connection, options); + }; + } + return undefined; +}; diff --git a/src_js/native.js b/src_js/native.js new file mode 100644 index 0000000..03b3b27 --- /dev/null +++ b/src_js/native.js @@ -0,0 +1,78 @@ + +const SspiClientApi = require('./sspi_client'); +const Fqdn = require('./fqdn'); +const MakeSpn = require('./make_spn'); + +/** + Authenticate to SQL Server via Windows Native SSPI. + +This class allows authentication to SQL Server via native APIs provided +by Windows. This allows client authentication without providing a +username/password. + +Optionally, the `securityPackage` to be used can be specified. SQL Server +understands `'kerberos'`, `'ntlm'` or `'negotiate'` (default). + +`'negotiate'` will automatically decide whether to use Kerberos or NTLM based +authentication, preferring Kerberos if that is supported by the target server +as it's more secure than NTLM. + +@param {Connection} connection +@param {Object} options +@param {?String} options.securityPackage +Can be one of `'ntlm'`, `'kerberos'` or `'negotiate'`. +*/ +class NativeAuthProvider { + constructor(connection, options) { + this.connection = connection; + this.options = options; + + this.client = undefined; + } + + handshake(data, callback) { + if (this.sspiClientResponsePending) { + // We got data from the server while we're waiting for getNextBlob() + // call to complete on the client. We cannot process server data + // until this call completes as the state can change on completion of + // the call. Queue it for later. + const boundDispatchEvent = this.connection.dispatchEvent.bind(this.connection); + return setImmediate(boundDispatchEvent, 'message'); + } + if (data) { + this.sspiClientResponsePending = true; + return this.client.getNextBlob(data, 0, data.length, (responseBuffer, isDone, errorCode, errorString) => { + if (errorCode) { + return callback(new Error(errorString)); + } + this.sspiClientResponsePending = false; + callback(null, responseBuffer); + }); + } else { + const server = this.connection.routingData ? this.connection.routingData.server : this.connection.config.server; + + Fqdn.getFqdn(server, (err, fqdn) => { + if (err) { + return callback(new Error('Error getting Fqdn. Error details: ' + err.message)); + } + + const spn = MakeSpn.makeSpn('MSSQLSvc', fqdn, this.connection.config.options.port); + this.client = new SspiClientApi.SspiClient(spn, this.options.securityPackage); + this.sspiClientResponsePending = true; + this.client.getNextBlob(null, 0, 0, (responseBuffer, isDone, errorCode, errorString) => { + if (errorCode) { + return callback(new Error(errorString)); + } + + if (isDone) { + return callback(new Error('Unexpected isDone=true on getNextBlob in sendLogin7Packet.')); + } + this.sspiClientResponsePending = false; + callback(null, responseBuffer); + }); + }); + } + } +} + +module.exports.NativeAuthProvider = NativeAuthProvider; diff --git a/test/integration/sspi-client-test.js b/test/integration/sspi-client-test.js index 38721a0..34f0142 100644 --- a/test/integration/sspi-client-test.js +++ b/test/integration/sspi-client-test.js @@ -2,9 +2,9 @@ const net = require('net'); -const SspiClientApi = require('../../src_js/index.js').SspiClientApi; -const Fqdn = require('../../src_js/index.js').Fqdn; -const MakeSpn = require('../../src_js/index.js').MakeSpn; +const SspiClientApi = require('../../src_js/sspi_client.js'); +const Fqdn = require('../../src_js/fqdn.js'); +const MakeSpn = require('../../src_js/make_spn.js'); let sspiClient = null; let serverResponse = null; diff --git a/test/unit/fqdn_tests.js b/test/unit/fqdn_tests.js index 5a6a43b..ab14c04 100644 --- a/test/unit/fqdn_tests.js +++ b/test/unit/fqdn_tests.js @@ -1,7 +1,7 @@ 'use strict'; const ConfigUtils = require('../utils/config.js'); -const Fqdn = require('../../src_js/index.js').Fqdn; +const Fqdn = require('../../src_js/fqdn.js'); function getLocalhostFqdnPattern() { return new RegExp("^" + ConfigUtils.getLocalhostFqdn() + "$"); diff --git a/test/unit/make_spn_tests.js b/test/unit/make_spn_tests.js index 097fabe..1c3f50d 100644 --- a/test/unit/make_spn_tests.js +++ b/test/unit/make_spn_tests.js @@ -1,6 +1,6 @@ 'use strict'; -const MakeSpn = require('../../src_js/index.js').MakeSpn; +const MakeSpn = require('../../src_js/make_spn.js'); exports.makeSpnPort = function(test) { const spn = MakeSpn.makeSpn('MSSQLSvc', 'www.example.com', 1433); diff --git a/test/unit/sspi_client_tests.js b/test/unit/sspi_client_tests.js index 536ab3a..4e7dedb 100644 --- a/test/unit/sspi_client_tests.js +++ b/test/unit/sspi_client_tests.js @@ -1,6 +1,6 @@ 'use strict'; -const SspiClientApi = require('../../src_js/index.js').SspiClientApi; +const SspiClientApi = require('../../src_js/sspi_client.js'); // Comment/Uncomment to enable/disable debug logging in native code. // SspiClientApi.enableNativeDebugLogging();