From 85e21a8dee278d3493f3f55c7bebf91faac05790 Mon Sep 17 00:00:00 2001 From: CPlusSharp Date: Sat, 9 Jul 2022 11:45:21 +0200 Subject: [PATCH 1/3] Document supported authentication methods --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index d135d34..0fb0d8a 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,12 @@ Below is a major feature comparison chart between the two drivers: | Active-Active Read Enabled |:heavy_check_mark:|:x:| | Client-Side Data Encryption |:heavy_check_mark:|:x:| | Statement Distribution |:heavy_check_mark:|:x:| +| Password/PBKDF2 Authentication |:heavy_check_mark:|:heavy_check_mark:| +| SAML Authentication |:heavy_check_mark:|:heavy_check_mark:| +| JWT Authentication |:heavy_check_mark:|:x:| +| LDAP Authentication |:heavy_check_mark:|:x:| | Kerberos Authentication |:heavy_check_mark:|:x:| +| X.509 Authentication |:heavy_check_mark:|:x:| | Secure User Store Integration (hdbuserstore) |:heavy_check_mark:|:x:| | Connections through HTTP proxy |:heavy_check_mark:|:x:| | Connections through SOCKS proxy (SAP Cloud Connector) |:heavy_check_mark:|:x:| From 830e2848553f9179238d2af977a2244703ad8201 Mon Sep 17 00:00:00 2001 From: CPlusSharp Date: Sat, 9 Jul 2022 12:31:07 +0200 Subject: [PATCH 2/3] Support authentication with JWT token --- README.md | 21 +++++++++++++++- lib/Client.js | 5 ++++ lib/protocol/auth/JWT.js | 40 +++++++++++++++++++++++++++++ lib/protocol/auth/Manager.js | 6 ++++- test/auth.Manager.js | 49 +++++++++++++++++++++++++++++++++++- test/hdb.Client.js | 13 +++++++++- 6 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 lib/protocol/auth/JWT.js diff --git a/README.md b/README.md index 0fb0d8a..f20edd5 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Below is a major feature comparison chart between the two drivers: | Statement Distribution |:heavy_check_mark:|:x:| | Password/PBKDF2 Authentication |:heavy_check_mark:|:heavy_check_mark:| | SAML Authentication |:heavy_check_mark:|:heavy_check_mark:| -| JWT Authentication |:heavy_check_mark:|:x:| +| JWT Authentication |:heavy_check_mark:|:heavy_check_mark:| | LDAP Authentication |:heavy_check_mark:|:x:| | Kerberos Authentication |:heavy_check_mark:|:x:| | X.509 Authentication |:heavy_check_mark:|:x:| @@ -217,6 +217,25 @@ client.connect({ After a successful SAML authentication, the server returns the database `user` and a `SessionCookie` which can be used for reconnecting. +#### JWT token +JWT tokens can also be used to authenticate users. + +Instead of `user` and `password` you have to provide a JWT `token`: + +```js +client.connect({ + token: 'eyJhbGciOiJSUzI1NiJ9....' +},function (err) { + if (err) { + return console.error('Error:', err); + } + console.log('User:', client.get('user')); + console.log('SessionCookie:', client.get('SessionCookie')); +}); +``` + +After a successful JWT authentication, the server returns the database `user` and a `SessionCookie` which can be used for reconnecting. + ### Encrypted network communication To establish an encrypted database connection just pass either `key`, `cert` and `ca` or a `pfx` to createClient. diff --git a/lib/Client.js b/lib/Client.js index 5b538e2..3f367cc 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -127,6 +127,11 @@ Client.prototype.connect = function connect(options, cb) { this._settings.assertion = undefined; } + // JWT token can only be used once + if (this._settings.token) { + this._settings.token = undefined; + } + var self = this; function done(err, reply) { diff --git a/lib/protocol/auth/JWT.js b/lib/protocol/auth/JWT.js new file mode 100644 index 0000000..4033ffa --- /dev/null +++ b/lib/protocol/auth/JWT.js @@ -0,0 +1,40 @@ +// Copyright 2022 SAP AG. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http: //www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, +// either express or implied. See the License for the specific +// language governing permissions and limitations under the License. +'use strict'; + +module.exports = JWT; + +function JWT(options) { + this.name = 'JWT'; + this.token = options.token || options.password; + this.user = undefined; + this.sessionCookie = undefined; +} + +JWT.prototype.initialData = function initialData() { + return this.token; +}; + +JWT.prototype.initialize = function initialize(buffer, cb) { + this.user = buffer.toString('utf8'); + cb(); +}; + +JWT.prototype.finalData = function finalData() { + return new Buffer(0); +}; + +JWT.prototype.finalize = function finalize(buffer) { + this.sessionCookie = buffer; +}; diff --git a/lib/protocol/auth/Manager.js b/lib/protocol/auth/Manager.js index b39f83c..af1adbb 100644 --- a/lib/protocol/auth/Manager.js +++ b/lib/protocol/auth/Manager.js @@ -15,6 +15,7 @@ var SCRAMSHA256 = require('./SCRAMSHA256'); var SAML = require('./SAML'); +var JWT = require('./JWT'); var SessionCookie = require('./SessionCookie'); module.exports = Manager; @@ -24,9 +25,12 @@ function Manager(options) { this.user = options.user || ''; this._authMethod = undefined; this._authMethods = []; - if (options.assertion || (!options.user && options.password)) { + if (options.assertion || (!options.user && options.password && options.password.toString().startsWith("<") )) { this._authMethods.push(new SAML(options)); } + if (options.token || (!options.user && options.password && options.password.toString().startsWith("ey"))) { + this._authMethods.push(new JWT(options)); + } if (options.sessionCookie) { this._authMethods.push(new SessionCookie(options)); } diff --git a/test/auth.Manager.js b/test/auth.Manager.js index e6fcb17..e8b784c 100644 --- a/test/auth.Manager.js +++ b/test/auth.Manager.js @@ -200,7 +200,7 @@ describe('Auth', function () { describe('#SAML', function () { var method = 'SAML'; - var assertion = new Buffer('3fca227d', 'hex'); + var assertion = new Buffer('', 'ascii'); var sessionCookie = new Buffer('fcac0f42', 'hex'); it('should get the corresponding authentication method instance', function () { @@ -244,6 +244,53 @@ describe('Auth', function () { }); + describe('#JWT', function () { + + var method = 'JWT'; + var token = new Buffer('eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.eu3buOdtT84lHs90LfmC3MJ_17Qg0FfgBke2qnW5yE-wDlEdKWWEURFoneCzMmdGtJcnVqINmZD1X8XbvoAWeWq_tH75fSKcg_1RaooYaARdtpQGF_BtjXJ9jMJHoJ9kgjO8cv06GobNaoydu2v6C8fsSIBDVw9zEApGZIwNCJztkgmEGmkQKXHHxKRISi55DgCowVYk1Obgp55KMjRqmMkAvw8qoMsAU109n26NGQNI19wOaGiPrSGKpENkgq6lWFY6visswoA8X3pYn6EXdAqEGjuFH0ADuvqUoRyrrIaaem30JgVny8LQ-t2ms7gck8jPdxS7TUjiB2hHKjRwBw', 'ascii'); + var sessionCookie = new Buffer('420facfc', 'hex'); + + it('should get the corresponding authentication method instance', function () { + var manager = auth.createManager({ + user: null, + password: token + }); + var authMethod = manager.getMethod(method); + authMethod.token.should.equal(token); + }); + + it('should authenticate and connect successfully', function () { + var manager = auth.createManager({ + token: token + }); + manager.user.should.equal(''); + manager._authMethods.should.have.length(1); + var authMethod = manager._authMethods[0]; + authMethod.name.should.equal(method); + authMethod.token.should.equal(token); + // initial data + var initialData = authMethod.initialData(); + initialData.should.equal(token); + initialData = manager.initialData(); + initialData.should.eql(['', method, token]); + // initialize manager + manager.initialize([method, new Buffer(user, 'utf8')], function(err) { + manager._authMethod.should.equal(authMethod); + // user + manager.userFromServer.should.equal(user); + // final data + var finalData = authMethod.finalData(); + finalData.should.eql(emptyBuffer); + finalData = manager.finalData(); + finalData.should.eql([user, method, emptyBuffer]); + // finalize manager + manager.finalize([method, sessionCookie]); + manager.sessionCookie.should.equal(sessionCookie); + }); + }); + + }); + describe('#SessionCookie', function () { var method = 'SessionCookie'; diff --git a/test/hdb.Client.js b/test/hdb.Client.js index 53d10f4..3300ff1 100644 --- a/test/hdb.Client.js +++ b/test/hdb.Client.js @@ -497,7 +497,7 @@ describe('hdb', function () { it('should connect with saml assertion', function (done) { var client = new TestClient({ - assertion: 'assertion' + assertion: '' }); client.connect(function (err) { should(err === null).be.ok; @@ -506,6 +506,17 @@ describe('hdb', function () { }); }); + it('should connect with jwt token', function (done) { + var client = new TestClient({ + token: 'eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.eu3buOdtT84lHs90LfmC3MJ_17Qg0FfgBke2qnW5yE-wDlEdKWWEURFoneCzMmdGtJcnVqINmZD1X8XbvoAWeWq_tH75fSKcg_1RaooYaARdtpQGF_BtjXJ9jMJHoJ9kgjO8cv06GobNaoydu2v6C8fsSIBDVw9zEApGZIwNCJztkgmEGmkQKXHHxKRISi55DgCowVYk1Obgp55KMjRqmMkAvw8qoMsAU109n26NGQNI19wOaGiPrSGKpENkgq6lWFY6visswoA8X3pYn6EXdAqEGjuFH0ADuvqUoRyrrIaaem30JgVny8LQ-t2ms7gck8jPdxS7TUjiB2hHKjRwBw' + }); + client.connect(function (err) { + should(err === null).be.ok; + should(client.get('token')).not.be.ok; + done(); + }); + }); + it('should directly connect without constructor', function (done) { var client = new TestClient(); var connection = client._connection; From c8cad8bfe7f53ac788f7ce30322980a8784230a7 Mon Sep 17 00:00:00 2001 From: CPlusSharp Date: Thu, 28 Jul 2022 17:23:46 +0200 Subject: [PATCH 3/3] Fix SAP name in Copyright of JWT.js --- lib/protocol/auth/JWT.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/protocol/auth/JWT.js b/lib/protocol/auth/JWT.js index 4033ffa..ad728ae 100644 --- a/lib/protocol/auth/JWT.js +++ b/lib/protocol/auth/JWT.js @@ -1,4 +1,4 @@ -// Copyright 2022 SAP AG. +// Copyright 2022 SAP SE. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.