diff --git a/README.md b/README.md index 81cae3e..c0b09dd 100644 --- a/README.md +++ b/README.md @@ -57,24 +57,16 @@ run() const { Sender } = require('@questdb/nodejs-client'); async function run() { - // construct a JsonWebKey + // authentication details const CLIENT_ID = 'testapp'; const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; - const PUBLIC_KEY = { - x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', - y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg' - }; - const JWK = { - ...PUBLIC_KEY, - d: PRIVATE_KEY, + const AUTH = { kid: CLIENT_ID, - kty: 'EC', - crv: 'P-256', + d: PRIVATE_KEY }; - // pass the JsonWebKey to the sender - // will use it for authentication - const sender = new Sender({jwk: JWK}); + // pass the authentication details to the sender + const sender = new Sender({auth: AUTH}); // connect() takes an optional second argument // if 'true' passed the connection is secured with TLS encryption @@ -99,24 +91,16 @@ run().catch(console.error); import { Sender } from '@questdb/nodejs-client'; async function run(): Promise { - // construct a JsonWebKey + // authentication details const CLIENT_ID: string = 'testapp'; const PRIVATE_KEY: string = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; - const PUBLIC_KEY: { x: string, y: string } = { - x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', - y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg' - }; - const JWK: { x: string, y: string, kid: string, kty: string, d: string, crv: string } = { - ...PUBLIC_KEY, - d: PRIVATE_KEY, + const AUTH: { kid: string, d: string } = { kid: CLIENT_ID, - kty: 'EC', - crv: 'P-256', + d: PRIVATE_KEY }; - // pass the JsonWebKey to the sender - // will use it for authentication - const sender: Sender = new Sender({jwk: JWK}); + // pass the authentication details to the sender + const sender: Sender = new Sender({auth: AUTH}); // connect() takes an optional second argument // if 'true' passed the connection is secured with TLS encryption diff --git a/docs/Sender.html b/docs/Sender.html index 4808af9..b11c33b 100644 --- a/docs/Sender.html +++ b/docs/Sender.html @@ -30,16 +30,18 @@

Class: Sender

Sender(options)

-
The QuestDB client's API provides methods to connect to the database, ingest data and close the connection. +
The QuestDB client's API provides methods to connect to the database, ingest data, and close the connection.

The client supports authentication.
-A JsonWebKey can be passed to the Sender in its constructor, the JsonWebKey will be used for authentication.
-If no JsonWebKey specified the client will not attempt to authenticate itself with the server.
-Details on how to configure QuestDB authentication: https://questdb.io/docs/reference/api/ilp/authenticate +Authentication details can be passed to the Sender in its configuration options.
+The user id and the user's private key are required for authentication.
+More details on configuration options can be found in the description of the constructor.
+Please, note that authentication is enabled by default in QuestDB Enterprise only.
+Details on how to configure authentication in the open source version of QuestDB: https://questdb.io/docs/reference/api/ilp/authenticate

The client also supports TLS encryption to provide a secure connection.
-However, QuestDB does not support TLS yet and requires an external reverse-proxy, such as Nginx to enable encryption. +Please, note that the open source version of QuestDB does not support TLS, and requires an external reverse-proxy, such as Nginx to enable encryption.

@@ -128,7 +130,10 @@
Parameters:
If the value passed is not a boolean, the setting is ignored.
  • jwk: {x: string, y: string, kid: string, kty: string, d: string, crv: string} - JsonWebKey for authentication.
    If not provided, client is not authenticated and server might reject the connection depending on configuration.
    - No type checks performed on the object passed.
  • + No type checks performed on the object passed.
    + Deprecated, please, use the auth option instead. +
  • auth: {kid: string, d: string} - Authentication details, `kid` is the username, `d` is the user's private key
    + If not provided, client is not authenticated and server might reject the connection depending on configuration.
  • log: (level: 'error'|'warn'|'info'|'debug', message: string) => void - logging function.
    If not provided, default logging is used which writes to the console with logging level info.
    If not a function passed, the setting is ignored.
  • @@ -174,7 +179,7 @@
    Parameters:
    Source:
    @@ -389,7 +394,7 @@
    Parameters:
    Source:
    @@ -478,7 +483,7 @@

    atNowSource:
    @@ -638,7 +643,7 @@
    Parameters:
    Source:
    @@ -749,7 +754,7 @@

    (async) closeSource:
    @@ -944,7 +949,7 @@
    Parameters:
    Source:
    @@ -1126,7 +1131,7 @@
    Parameters:
    Source:
    @@ -1237,7 +1242,7 @@

    (async) flushSource:
    @@ -1419,7 +1424,7 @@
    Parameters:
    Source:
    @@ -1530,7 +1535,7 @@

    resetSource:
    @@ -1691,7 +1696,7 @@
    Parameters:
    Source:
    @@ -1851,7 +1856,7 @@
    Parameters:
    Source:
    @@ -2033,7 +2038,7 @@
    Parameters:
    Source:
    @@ -2192,7 +2197,7 @@
    Parameters:
    Source:
    @@ -2444,7 +2449,7 @@
    Parameters:
    Source:
    @@ -2518,7 +2523,7 @@

    Home

    Modules

    • diff --git a/docs/index.html b/docs/index.html index 0c763b4..2cf1b92 100644 --- a/docs/index.html +++ b/docs/index.html @@ -91,24 +91,16 @@

      Authentication and secure connection

      const { Sender } = require('@questdb/nodejs-client');
       
       async function run() {
      -    // construct a JsonWebKey
      +    // authentication details
           const CLIENT_ID = 'testapp';
           const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8';
      -    const PUBLIC_KEY = {
      -        x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc',
      -        y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg'
      -    };
      -    const JWK = {
      -        ...PUBLIC_KEY,
      -        d: PRIVATE_KEY,
      +    const AUTH = {
               kid: CLIENT_ID,
      -        kty: 'EC',
      -        crv: 'P-256',
      +        d: PRIVATE_KEY
           };
       
      -    // pass the JsonWebKey to the sender
      -    // will use it for authentication
      -    const sender = new Sender({jwk: JWK});
      +    // pass the authentication details to the sender
      +    const sender = new Sender({auth: AUTH});
       
           // connect() takes an optional second argument
           // if 'true' passed the connection is secured with TLS encryption
      @@ -130,24 +122,16 @@ 

      TypeScript example

      import { Sender } from '@questdb/nodejs-client';
       
       async function run(): Promise<number> {
      -    // construct a JsonWebKey
      +    // authentication details
           const CLIENT_ID: string = 'testapp';
           const PRIVATE_KEY: string = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8';
      -    const PUBLIC_KEY: { x: string, y: string } = {
      -        x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc',
      -        y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg'
      -    };
      -    const JWK: { x: string, y: string, kid: string, kty: string, d: string, crv: string } = {
      -        ...PUBLIC_KEY,
      -        d: PRIVATE_KEY,
      +    const AUTH: { kid: string, d: string } = {
               kid: CLIENT_ID,
      -        kty: 'EC',
      -        crv: 'P-256',
      +        d: PRIVATE_KEY
           };
       
      -    // pass the JsonWebKey to the sender
      -    // will use it for authentication
      -    const sender: Sender = new Sender({jwk: JWK});
      +    // pass the authentication details to the sender
      +    const sender: Sender = new Sender({auth: AUTH});
       
           // connect() takes an optional second argument
           // if 'true' passed the connection is secured with TLS encryption
      @@ -256,7 +240,7 @@ 

      Home

      Modules

      • diff --git a/docs/index.js.html b/docs/index.js.html index 20a422d..63f67ac 100644 --- a/docs/index.js.html +++ b/docs/index.js.html @@ -53,7 +53,7 @@

        Home

        Modules

        • diff --git a/docs/module-@questdb_nodejs-client.html b/docs/module-@questdb_nodejs-client.html index de66d98..e2b76d4 100644 --- a/docs/module-@questdb_nodejs-client.html +++ b/docs/module-@questdb_nodejs-client.html @@ -120,7 +120,7 @@

          Home

          Modules

          • diff --git a/docs/src_sender.js.html b/docs/src_sender.js.html index 6efef21..e538a69 100644 --- a/docs/src_sender.js.html +++ b/docs/src_sender.js.html @@ -39,17 +39,26 @@

            Source: src/sender.js

            const DEFAULT_BUFFER_SIZE = 8192; +// an arbitrary public key, not used in authentication +// only used to construct a valid JWK token which is accepted by the crypto API +const PUBLIC_KEY = { + x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', + y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg' +}; + /** @classdesc - * The QuestDB client's API provides methods to connect to the database, ingest data and close the connection. + * The QuestDB client's API provides methods to connect to the database, ingest data, and close the connection. * <p> * The client supports authentication. <br> - * A JsonWebKey can be passed to the Sender in its constructor, the JsonWebKey will be used for authentication. <br> - * If no JsonWebKey specified the client will not attempt to authenticate itself with the server. <br> - * Details on how to configure QuestDB authentication: {@link https://questdb.io/docs/reference/api/ilp/authenticate} + * Authentication details can be passed to the Sender in its configuration options. <br> + * The user id and the user's private key are required for authentication. <br> + * More details on configuration options can be found in the description of the constructor. <br> + * Please, note that authentication is enabled by default in QuestDB Enterprise only. <br> + * Details on how to configure authentication in the open source version of QuestDB: {@link https://questdb.io/docs/reference/api/ilp/authenticate} * </p> * <p> * The client also supports TLS encryption to provide a secure connection. <br> - * However, QuestDB does not support TLS yet and requires an external reverse-proxy, such as Nginx to enable encryption. + * Please, note that the open source version of QuestDB does not support TLS, and requires an external reverse-proxy, such as Nginx to enable encryption. * </p> */ class Sender { @@ -83,7 +92,10 @@

            Source: src/sender.js

            * If the value passed is not a boolean, the setting is ignored. </li> * <li>jwk: <i>{x: string, y: string, kid: string, kty: string, d: string, crv: string}</i> - JsonWebKey for authentication. <br> * If not provided, client is not authenticated and server might reject the connection depending on configuration. <br> - * No type checks performed on the object passed. </li> + * No type checks performed on the object passed. <br> + * <b>Deprecated</b>, please, use the <i>auth</i> option instead. </li> + * <li>auth: <i>{kid: string, d: string}</i> - Authentication details, `kid` is the username, `d` is the user's private key <br> + * If not provided, client is not authenticated and server might reject the connection depending on configuration. </li> * <li>log: <i>(level: 'error'|'warn'|'info'|'debug', message: string) => void</i> - logging function. <br> * If not provided, default logging is used which writes to the console with logging level <i>info</i>. <br> * If not a function passed, the setting is ignored. </li> @@ -91,9 +103,7 @@

            Source: src/sender.js

            * </p> */ constructor(options = undefined) { - if (options) { - this.jwk = options.jwk; - } + initJwk(this, options); const noCopy = options && typeof options.copyBuffer === 'boolean' && !options.copyBuffer; this.toBuffer = noCopy ? this.toBufferView : this.toBufferNew; this.doResolve = noCopy @@ -560,6 +570,38 @@

            Source: src/sender.js

            } } +function initJwk(sender, options) { + if (options) { + if (options.auth) { + if (!options.auth.kid) { + throw new Error('Missing username, please, specify the \'kid\' property of the \'auth\' config option. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + if (typeof options.auth.kid !== 'string') { + throw new Error('Please, specify the \'kid\' property of the \'auth\' config option as a string. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + if (!options.auth.d) { + throw new Error('Missing private key, please, specify the \'d\' property of the \'auth\' config option. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + if (typeof options.auth.d !== 'string') { + throw new Error('Please, specify the \'d\' property of the \'auth\' config option as a string. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + + sender.jwk = { + ...options.auth, + ...PUBLIC_KEY, + kty: 'EC', + crv: 'P-256' + }; + } else { + sender.jwk = options.jwk; + } + } +} + exports.Sender = Sender; exports.DEFAULT_BUFFER_SIZE = DEFAULT_BUFFER_SIZE;
      @@ -578,7 +620,7 @@

      Home

      Modules

      • diff --git a/examples/auth.js b/examples/auth.js index b850efd..e7be618 100644 --- a/examples/auth.js +++ b/examples/auth.js @@ -1,24 +1,16 @@ const { Sender } = require('@questdb/nodejs-client'); async function run() { - // construct a JsonWebKey + // authentication details const CLIENT_ID = 'testapp'; const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; - const PUBLIC_KEY = { - x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', - y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg', - }; - const JWK = { - ...PUBLIC_KEY, - d: PRIVATE_KEY, + const AUTH = { kid: CLIENT_ID, - kty: 'EC', - crv: 'P-256', + d: PRIVATE_KEY }; - // pass the JsonWebKey to the sender - // will use it for authentication - const sender = new Sender({ bufferSize: 4096, jwk: JWK }); + // pass the authentication details to the sender + const sender = new Sender({ bufferSize: 4096, auth: AUTH }); // connect() takes an optional second argument // if 'true' passed the connection is secured with TLS encryption diff --git a/examples/auth_tls.js b/examples/auth_tls.js index 82524ab..7a505c0 100644 --- a/examples/auth_tls.js +++ b/examples/auth_tls.js @@ -1,24 +1,16 @@ const { Sender } = require('@questdb/nodejs-client'); async function run() { - // construct a JsonWebKey + // authentication details const CLIENT_ID = 'testapp'; const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; - const PUBLIC_KEY = { - x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', - y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg', - }; - const JWK = { - ...PUBLIC_KEY, - d: PRIVATE_KEY, + const AUTH = { kid: CLIENT_ID, - kty: 'EC', - crv: 'P-256', + d: PRIVATE_KEY }; - // pass the JsonWebKey to the sender - // will use it for authentication - const sender = new Sender({ bufferSize: 4096, jwk: JWK }); + // pass the authentication details to the sender + const sender = new Sender({ bufferSize: 4096, auth: AUTH }); // connect() takes an optional second argument // if 'true' passed the connection is secured with TLS encryption diff --git a/package-lock.json b/package-lock.json index d1fe55c..e3baece 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@questdb/nodejs-client", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@questdb/nodejs-client", - "version": "2.0.0", + "version": "2.1.0", "license": "Apache-2.0", "devDependencies": { "eslint": "^8.50.0", diff --git a/package.json b/package.json index 040785e..ee9e9a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@questdb/nodejs-client", - "version": "2.0.0", + "version": "2.1.0", "description": "QuestDB Node.js Client", "main": "index.js", "types": "types/index.d.ts", diff --git a/src/sender.js b/src/sender.js index f789204..9a03382 100644 --- a/src/sender.js +++ b/src/sender.js @@ -11,17 +11,26 @@ const crypto = require('crypto'); const DEFAULT_BUFFER_SIZE = 8192; +// an arbitrary public key, not used in authentication +// only used to construct a valid JWK token which is accepted by the crypto API +const PUBLIC_KEY = { + x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', + y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg' +}; + /** @classdesc - * The QuestDB client's API provides methods to connect to the database, ingest data and close the connection. + * The QuestDB client's API provides methods to connect to the database, ingest data, and close the connection. *

        * The client supports authentication.
        - * A JsonWebKey can be passed to the Sender in its constructor, the JsonWebKey will be used for authentication.
        - * If no JsonWebKey specified the client will not attempt to authenticate itself with the server.
        - * Details on how to configure QuestDB authentication: {@link https://questdb.io/docs/reference/api/ilp/authenticate} + * Authentication details can be passed to the Sender in its configuration options.
        + * The user id and the user's private key are required for authentication.
        + * More details on configuration options can be found in the description of the constructor.
        + * Please, note that authentication is enabled by default in QuestDB Enterprise only.
        + * Details on how to configure authentication in the open source version of QuestDB: {@link https://questdb.io/docs/reference/api/ilp/authenticate} *

        *

        * The client also supports TLS encryption to provide a secure connection.
        - * However, QuestDB does not support TLS yet and requires an external reverse-proxy, such as Nginx to enable encryption. + * Please, note that the open source version of QuestDB does not support TLS, and requires an external reverse-proxy, such as Nginx to enable encryption. *

        */ class Sender { @@ -55,7 +64,10 @@ class Sender { * If the value passed is not a boolean, the setting is ignored.
      • *
      • jwk: {x: string, y: string, kid: string, kty: string, d: string, crv: string} - JsonWebKey for authentication.
        * If not provided, client is not authenticated and server might reject the connection depending on configuration.
        - * No type checks performed on the object passed.
      • + * No type checks performed on the object passed.
        + * Deprecated, please, use the auth option instead. + *
      • auth: {kid: string, d: string} - Authentication details, `kid` is the username, `d` is the user's private key
        + * If not provided, client is not authenticated and server might reject the connection depending on configuration.
      • *
      • log: (level: 'error'|'warn'|'info'|'debug', message: string) => void - logging function.
        * If not provided, default logging is used which writes to the console with logging level info.
        * If not a function passed, the setting is ignored.
      • @@ -63,9 +75,7 @@ class Sender { *

        */ constructor(options = undefined) { - if (options) { - this.jwk = options.jwk; - } + initJwk(this, options); const noCopy = options && typeof options.copyBuffer === 'boolean' && !options.copyBuffer; this.toBuffer = noCopy ? this.toBufferView : this.toBufferNew; this.doResolve = noCopy @@ -532,5 +542,37 @@ function timestampToNanos(timestamp, unit) { } } +function initJwk(sender, options) { + if (options) { + if (options.auth) { + if (!options.auth.kid) { + throw new Error('Missing username, please, specify the \'kid\' property of the \'auth\' config option. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + if (typeof options.auth.kid !== 'string') { + throw new Error('Please, specify the \'kid\' property of the \'auth\' config option as a string. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + if (!options.auth.d) { + throw new Error('Missing private key, please, specify the \'d\' property of the \'auth\' config option. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + if (typeof options.auth.d !== 'string') { + throw new Error('Please, specify the \'d\' property of the \'auth\' config option as a string. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + + sender.jwk = { + ...options.auth, + ...PUBLIC_KEY, + kty: 'EC', + crv: 'P-256' + }; + } else { + sender.jwk = options.jwk; + } + } +} + exports.Sender = Sender; exports.DEFAULT_BUFFER_SIZE = DEFAULT_BUFFER_SIZE; diff --git a/test/sender.test.js b/test/sender.test.js index a4707b0..c339e3b 100644 --- a/test/sender.test.js +++ b/test/sender.test.js @@ -25,25 +25,112 @@ const proxyOptions = { key: readFileSync('test/certs/server/server.key'), cert: readFileSync('test/certs/server/server.crt'), ca: readFileSync('test/certs/ca/ca.crt') -}; - -const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8' -const PUBLIC_KEY = { - x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', - y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg' } -const JWK = { - ...PUBLIC_KEY, - kid: 'testapp', - kty: 'EC', - d: PRIVATE_KEY, - crv: 'P-256', + +const USER_NAME = 'testapp'; +const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; +const AUTH = { + kid: USER_NAME, + d: PRIVATE_KEY } async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } +describe('Sender auth config checks suite', function () { + it('requires a username for authentication', async function () { + try { + new Sender({ + bufferSize: 512, + auth: { + d: 'privateKey' + } + }); + fail('it should not be able to create the sender'); + } catch(err) { + expect(err.message).toBe('Missing username, please, specify the \'kid\' property of the \'auth\' config option. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + }); + + it('requires a non-empty username', async function () { + try { + new Sender({ + bufferSize: 512, + auth: { + kid: '', + d: 'privateKey' + } + }); + fail('it should not be able to create the sender'); + } catch(err) { + expect(err.message).toBe('Missing username, please, specify the \'kid\' property of the \'auth\' config option. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + }); + + it('requires that the username is a string', async function () { + try { + new Sender({ + bufferSize: 512, + auth: { + kid: 23, + d: 'privateKey' + } + }); + fail('it should not be able to create the sender'); + } catch(err) { + expect(err.message).toBe('Please, specify the \'kid\' property of the \'auth\' config option as a string. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + }); + + it('requires a private key for authentication', async function () { + try { + new Sender({ + auth: { + kid: 'username' + } + }); + fail('it should not be able to create the sender'); + } catch(err) { + expect(err.message).toBe('Missing private key, please, specify the \'d\' property of the \'auth\' config option. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + }); + + it('requires a non-empty private key', async function () { + try { + new Sender({ + auth: { + kid: 'username', + d: '' + } + }); + fail('it should not be able to create the sender'); + } catch(err) { + expect(err.message).toBe('Missing private key, please, specify the \'d\' property of the \'auth\' config option. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + }); + + it('requires that the private key is a string', async function () { + try { + new Sender({ + auth: { + kid: 'username', + d: true + } + }); + fail('it should not be able to create the sender'); + } catch(err) { + expect(err.message).toBe('Please, specify the \'d\' property of the \'auth\' config option as a string. ' + + 'For example: new Sender({auth: {kid: \'username\', d: \'private key\'}})'); + } + }); +}); + describe('Sender connection suite', function () { async function createProxy(auth = false, tlsOptions = undefined) { const mockConfig = { auth: auth, assertions: true }; @@ -54,8 +141,8 @@ describe('Sender connection suite', function () { return proxy; } - async function createSender(jwk = undefined, secure = false) { - const sender = new Sender({bufferSize: 1024, jwk: jwk}); + async function createSender(auth = undefined, secure = false) { + const sender = new Sender({bufferSize: 1024, auth: auth}); const connected = await sender.connect(senderOptions, secure); expect(connected).toBe(true); return sender; @@ -86,12 +173,42 @@ describe('Sender connection suite', function () { it('can authenticate', async function () { const proxy = await createProxy(true); - const sender = await createSender(JWK); + const sender = await createSender(AUTH); await sender.close(); await assertSentData(proxy, true, 'testapp\n'); await proxy.stop(); }); + it('can authenticate with a different private key', async function () { + const proxy = await createProxy(true); + const sender = await createSender({ + kid: 'user1', + d: 'zhPiK3BkYMYJvRf5sqyrWNJwjDKHOWHnRbmQggUll6A' + }); + await sender.close(); + await assertSentData(proxy, true, 'user1\n'); + await proxy.stop(); + }); + + it('is backwards compatible and still can authenticate with full JWK', async function () { + const JWK = { + x: 'BtUXC_K3oAyGlsuPjTgkiwirMUJhuRQDfcUHeyoxFxU', + y: 'R8SOup-rrNofB7wJagy4HrJhTVfrVKmj061lNRk3bF8', + kid: 'user2', + kty: 'EC', + d: 'hsg6Zm4kSBlIEvKUWT3kif-2y2Wxw-iWaGrJxrPXQhs', + crv: 'P-256' + } + + const proxy = await createProxy(true); + const sender = new Sender({jwk: JWK}); + const connected = await sender.connect(senderOptions, false); + expect(connected).toBe(true); + await sender.close(); + await assertSentData(proxy, true, 'user2\n'); + await proxy.stop(); + }); + it('can connect unauthenticated', async function () { const proxy = await createProxy(); const sender = await createSender(); @@ -102,7 +219,7 @@ describe('Sender connection suite', function () { it('can authenticate and send data to server', async function () { const proxy = await createProxy(true); - const sender = await createSender(JWK); + const sender = await createSender(AUTH); await sendData(sender); await sender.close(); await assertSentData(proxy, true, 'testapp\ntest,location=us temperature=17.1 1658484765000000000\n'); @@ -120,7 +237,7 @@ describe('Sender connection suite', function () { it('can authenticate and send data to server via secure connection', async function () { const proxy = await createProxy(true, proxyOptions); - const sender = await createSender(JWK, true); + const sender = await createSender(AUTH, true); await sendData(sender); await sender.close(); await assertSentData(proxy, true, 'testapp\ntest,location=us temperature=17.1 1658484765000000000\n'); @@ -138,9 +255,10 @@ describe('Sender connection suite', function () { it('guards against multiple connect calls', async function () { const proxy = await createProxy(true, proxyOptions); - const sender = await createSender(JWK, true); + const sender = await createSender(AUTH, true); try { await sender.connect(senderOptions, true); + fail('it should not be able to connect again'); } catch(err) { expect(err.message).toBe('Sender connected already'); } @@ -150,9 +268,10 @@ describe('Sender connection suite', function () { it('guards against concurrent connect calls', async function () { const proxy = await createProxy(true, proxyOptions); - const sender = new Sender({bufferSize: 1024, jwk: JWK}); + const sender = new Sender({bufferSize: 1024, auth: AUTH}); try { await Promise.all([sender.connect(senderOptions, true), sender.connect(senderOptions, true)]); + fail('it should not be able to connect twice'); } catch(err) { expect(err.message).toBe('Sender connected already'); } @@ -162,7 +281,7 @@ describe('Sender connection suite', function () { it('can handle unfinished rows during flush()', async function () { const proxy = await createProxy(true, proxyOptions); - const sender = await createSender(JWK, true); + const sender = await createSender(AUTH, true); sender.table('test').symbol('location', 'us'); const sent = await sender.flush(); expect(sent).toBe(false); diff --git a/test/testapp.js b/test/testapp.js index fbeac92..980245f 100644 --- a/test/testapp.js +++ b/test/testapp.js @@ -8,17 +8,11 @@ const PROXY_PORT = 9099; const PORT = 9009; const HOST = '127.0.0.1'; +const USER_NAME = 'testapp'; const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; -const PUBLIC_KEY = { - x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', - y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg' -}; -const JWK = { - ...PUBLIC_KEY, - kid: 'testapp', - kty: 'EC', - d: PRIVATE_KEY, - crv: 'P-256', +const AUTH = { + kid: USER_NAME, + d: PRIVATE_KEY }; const senderTLS = { @@ -37,7 +31,7 @@ async function run() { const proxy = new Proxy(); await proxy.start(PROXY_PORT, PORT, HOST, proxyTLS); - const sender = new Sender({bufferSize: 1024, jwk: JWK}); //with authentication + const sender = new Sender({bufferSize: 1024, auth: AUTH}); //with authentication const connected = await sender.connect(senderTLS, true); //connection through proxy with encryption if (connected) { sender.table('test') diff --git a/test/testapp.save.js b/test/testapp.save.js new file mode 100644 index 0000000..f18404b --- /dev/null +++ b/test/testapp.save.js @@ -0,0 +1,59 @@ +'use strict'; + +const { Sender } = require('../index'); + +const PORT = 9009; +const HOST = '127.0.0.1'; + +// const USER_NAME = 'testapp'; +// const PRIVATE_KEY = '9b9x5WhJywDEuo1KGQWSPNxtX-6X6R2BRCKhYMMY6n8'; +// const PUBLIC_KEY = { +// x: 'aultdA0PjhD_cWViqKKyL5chm6H1n-BiZBo_48T-uqc', +// y: '__ptaol41JWSpTTL525yVEfzmY8A6Vi_QrW1FjKcHMg' +// }; + +const USER_NAME = 'user1'; +const PRIVATE_KEY = 'zhPiK3BkYMYJvRf5sqyrWNJwjDKHOWHnRbmQggUll6A'; +// const PUBLIC_KEY = { +// x: 'LYMCsNOm_62uKIK-6yFwWd7-cHBZhs_3_rNbZK0PNqc', +// y: 'zlGEzcfTh0uQRy59-uwnGky8fULZxCGIu1Q-gro9q6I' +// }; + +// const USER_NAME = 'user2'; +// const PRIVATE_KEY = 'hsg6Zm4kSBlIEvKUWT3kif-2y2Wxw-iWaGrJxrPXQhs'; +// const PUBLIC_KEY = { +// x: 'BtUXC_K3oAyGlsuPjTgkiwirMUJhuRQDfcUHeyoxFxU', +// y: 'R8SOup-rrNofB7wJagy4HrJhTVfrVKmj061lNRk3bF8' +// }; + +// const JWK = { +// ...PUBLIC_KEY, +// kid: USER_NAME, +// kty: 'EC', +// d: PRIVATE_KEY, +// crv: 'P-256' +// }; + +async function run() { + const sender = new Sender({bufferSize: 1024, auth: {kid: USER_NAME, d: PRIVATE_KEY}}); + const connected = await sender.connect({host: HOST, port: PORT}); + if (connected) { + sender.table('testTable') + .symbol('location', 'emea').symbol('city', 'budapest') + .stringColumn('hoppa', 'hello') + .floatColumn('temperature', 14.1).intColumn('intcol', 56) + .timestampColumn('tscol', Date.now(), 'ms') + .atNow(); + + console.log('sending:\n' + sender.toBuffer().toString()); + await sender.flush(); + } + await sleep(1000); + await sender.close(); +} + +async function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +run().catch(console.error); diff --git a/types/src/sender.d.ts b/types/src/sender.d.ts index 3797b51..fed1c1e 100644 --- a/types/src/sender.d.ts +++ b/types/src/sender.d.ts @@ -31,7 +31,10 @@ export class Sender { * If the value passed is not a boolean, the setting is ignored. *
      • jwk: {x: string, y: string, kid: string, kty: string, d: string, crv: string} - JsonWebKey for authentication.
        * If not provided, client is not authenticated and server might reject the connection depending on configuration.
        - * No type checks performed on the object passed.
      • + * No type checks performed on the object passed.
        + * Deprecated, please, use the auth option instead. + *
      • auth: {kid: string, d: string} - Authentication details, `kid` is the username, `d` is the user's private key
        + * If not provided, client is not authenticated and server might reject the connection depending on configuration.
      • *
      • log: (level: 'error'|'warn'|'info'|'debug', message: string) => void - logging function.
        * If not provided, default logging is used which writes to the console with logging level info.
        * If not a function passed, the setting is ignored.
      • diff --git a/types/src/sender.d.ts.map b/types/src/sender.d.ts.map index e2409d5..0bb6627 100644 --- a/types/src/sender.d.ts.map +++ b/types/src/sender.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"sender.d.ts","sourceRoot":"","sources":["../../src/sender.js"],"names":[],"mappings":";;;AAaA;;;;;;;;;;;;GAYG;AACH;IAeI;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,sBApBW,MAAM,EAmChB;IAnDD,eAAe,CAAC,YAAI;IACpB,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,kBAAU;IAC1B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,qBAAa;IAC7B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,YAAI;IA0CpB;;;;;;OAMG;IACH,mBAFW,MAAM,QAShB;IAED;;;;;OAKG;IACH,SAFY,MAAM,CAMjB;IAED;;;;;;;OAOG;IACH,iBALW,IAAI,cAAc,GAAG,IAAI,iBAAiB,WAC1C,OAAO,GAEN,QAAQ,OAAO,CAAC,CAoD3B;IAED;;;OAGG;IACH,uBAKC;IAED;;;;;OAKG;IACH,SAFY,QAAQ,OAAO,CAAC,CAY3B;IAED;;;;OAIG;IACH,yBAHY,MAAM,CAOjB;IAED;;;;OAIG;IACH,wBAHY,MAAM,CAWjB;IAED;;;;;OAKG;IACH,aAHW,MAAM,GACL,MAAM,CAcjB;IAED;;;;;;OAMG;IACH,aAJW,MAAM,SACN,GAAG,GACF,MAAM,CAkBjB;IAED;;;;;;OAMG;IACH,mBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAUjB;IAED;;;;;;OAMG;IACH,oBAJW,MAAM,SACN,OAAO,GACN,MAAM,CAQjB;IAED;;;;;;OAMG;IACH,kBAJW,MAAM,SACN,MAAM,GACL,MAAM,CASjB;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAajB;IAED;;;;;;;OAOG;IACH,sBALW,MAAM,SACN,MAAM,GAAG,MAAM,SACf,MAAM,GACL,MAAM,CAcjB;IAED;;;;;OAKG;IACH,cAHW,MAAM,GAAG,MAAM,SACf,MAAM,QAgBhB;IAED;;;OAGG;IACH,cAOC;CACJ;AA9XD,uCAAiC"} \ No newline at end of file +{"version":3,"file":"sender.d.ts","sourceRoot":"","sources":["../../src/sender.js"],"names":[],"mappings":";;;AAoBA;;;;;;;;;;;;GAYG;AACH;IAeI;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,sBAvBW,MAAM,EAoChB;IApDD,eAAe,CAAC,YAAI;IACpB,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,eAAO;IACvB,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,kBAAU;IAC1B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,qBAAa;IAC7B,eAAe,CAAC,iBAAS;IACzB,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,mBAAW;IAC3B,eAAe,CAAC,YAAI;IA2CpB;;;;;;OAMG;IACH,mBAFW,MAAM,QAShB;IAED;;;;;OAKG;IACH,SAFY,MAAM,CAMjB;IAED;;;;;;;OAOG;IACH,iBALW,IAAI,cAAc,GAAG,IAAI,iBAAiB,WAC1C,OAAO,GAEN,QAAQ,OAAO,CAAC,CAoD3B;IAED;;;OAGG;IACH,uBAKC;IAED;;;;;OAKG;IACH,SAFY,QAAQ,OAAO,CAAC,CAY3B;IAED;;;;OAIG;IACH,yBAHY,MAAM,CAOjB;IAED;;;;OAIG;IACH,wBAHY,MAAM,CAWjB;IAED;;;;;OAKG;IACH,aAHW,MAAM,GACL,MAAM,CAcjB;IAED;;;;;;OAMG;IACH,aAJW,MAAM,SACN,GAAG,GACF,MAAM,CAkBjB;IAED;;;;;;OAMG;IACH,mBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAUjB;IAED;;;;;;OAMG;IACH,oBAJW,MAAM,SACN,OAAO,GACN,MAAM,CAQjB;IAED;;;;;;OAMG;IACH,kBAJW,MAAM,SACN,MAAM,GACL,MAAM,CASjB;IAED;;;;;;OAMG;IACH,gBAJW,MAAM,SACN,MAAM,GACL,MAAM,CAajB;IAED;;;;;;;OAOG;IACH,sBALW,MAAM,SACN,MAAM,GAAG,MAAM,SACf,MAAM,GACL,MAAM,CAcjB;IAED;;;;;OAKG;IACH,cAHW,MAAM,GAAG,MAAM,SACf,MAAM,QAgBhB;IAED;;;OAGG;IACH,cAOC;CACJ;AAtYD,uCAAiC"} \ No newline at end of file