From 2808452ea6264300b5353f8f92eced431b9c5df3 Mon Sep 17 00:00:00 2001 From: Derek Collison Date: Sat, 8 Dec 2018 08:52:34 -0800 Subject: [PATCH 1/2] Added support for nkeys, user jwts, and signing. Also allow client to auto-upgrade to tls. Added simplified connect with second parameter option creators. We use the nats.cred(filepath) here. e.g. nats.connect(url, opts) Signed-off-by: Derek Collison --- Makefile | 2 +- README.md | 2 +- examples/node-pub | 26 +-- examples/node-req | 36 ++++ examples/node-sub | 37 +++-- lib/nats.js | 248 +++++++++++++++++++++++++--- package.json | 7 +- test/auth.js | 4 +- test/certs/key_noip.pem | 27 +++ test/certs/server_noip.pem | 27 +++ test/configs/nkey.conf | 6 + test/configs/nkeys/op.jwt | 3 + test/configs/nkeys/test.creds | 11 ++ test/configs/nkeys/test.seed | 7 + test/configs/operator.conf | 21 +++ test/connect.js | 1 - test/nkeys.js | 69 ++++++++ test/subevents.js | 21 ++- test/support/nats_server_control.js | 11 +- test/tls.js | 18 +- test/tls_noip.js | 74 +++++++++ test/userjwt.js | 160 ++++++++++++++++++ 22 files changed, 741 insertions(+), 77 deletions(-) create mode 100755 examples/node-req create mode 100644 test/certs/key_noip.pem create mode 100644 test/certs/server_noip.pem create mode 100644 test/configs/nkey.conf create mode 100644 test/configs/nkeys/op.jwt create mode 100644 test/configs/nkeys/test.creds create mode 100644 test/configs/nkeys/test.seed create mode 100644 test/configs/operator.conf create mode 100644 test/nkeys.js create mode 100644 test/tls_noip.js create mode 100644 test/userjwt.js diff --git a/Makefile b/Makefile index 76579c88..22115d5e 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ lint: - ./node_modules/.bin/eslint ./test ./lib/nats.js ./examples ./benchmark + ./node_modules/.bin/eslint ./test/*.js ./lib/nats.js ./benchmark ./examples/* test: @NODE_ENV=test ./node_modules/.bin/mocha -c\ diff --git a/README.md b/README.md index c5f15552..2ed70edc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A [Node.js](http://nodejs.org/) client for the [NATS messaging system](https://n [![license](https://img.shields.io/github/license/nats-io/node-nats.svg)](https://www.apache.org/licenses/LICENSE-2.0) [![Build Status](https://travis-ci.org/nats-io/node-nats.svg?branch=master)](https://travis-ci.org/nats-io/node-nats) -[![Coveralls](https://img.shields.io/coveralls/github/nats-io/node-nats/master.svg)]() +[![Coveralls](https://img.shields.io/coveralls/github/nats-io/node-nats/master.svg)](https://coveralls.io/r/nats-io/node-nats?branch=master) [![npm](https://img.shields.io/npm/v/nats.svg)](https://www.npmjs.com/package/nats) [![npm](https://img.shields.io/npm/dm/nats.svg)](https://www.npmjs.com/package/nats) diff --git a/examples/node-pub b/examples/node-pub index 65313c81..6c69c172 100755 --- a/examples/node-pub +++ b/examples/node-pub @@ -3,22 +3,28 @@ /* jslint node: true */ 'use strict'; -var nats = require('nats').connect(); +var nats = require('nats'); +var argv = require('minimist')(process.argv.slice(2)); -nats.on('error', function(e) { - console.log('Error [' + nats.options.url + ']: ' + e); - process.exit(); -}); - -var subject = process.argv[2]; -var msg = process.argv[3] || ''; +var url = argv.s || nats.DEFAULT_URI; +var creds = argv.creds; +var subject = argv._[0]; +var msg = argv._[1] || ''; if (!subject) { - console.log('Usage: node-pub [msg]'); + console.log('Usage: node-pub [-s server] [--creds file] [msg]'); process.exit(); } -nats.publish(subject, msg, function() { +// Connect to NATS server. +var nc = nats.connect(url, nats.creds(creds)); + +nc.publish(subject, msg, function() { console.log('Published [' + subject + '] : "' + msg + '"'); process.exit(); }); + +nc.on('error', function(e) { + console.log('Error [' + nats.options.url + ']: ' + e); + process.exit(); +}); diff --git a/examples/node-req b/examples/node-req new file mode 100755 index 00000000..74c9d5e8 --- /dev/null +++ b/examples/node-req @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +/* jslint node: true */ +'use strict'; + +var nats = require('nats'); +var argv = require('minimist')(process.argv.slice(2)); + +var url = argv.s || nats.DEFAULT_URI; +var creds = argv.creds; +var max = argv.n || 1; +var subject = argv._[0]; +var msg = argv._[1] || ''; + +if (!subject) { + console.log('Usage: node-req [-s server] [--creds file] [-n max_responses] [msg]'); + process.exit(); +} + +console.log("url is " + url); + +// Connect to NATS server. +var nc = nats.connect(url, nats.creds(creds)); + +nc.request(subject, msg, {max:max}, function(response) { + console.log('Received: ' + response); +}); + +nc.on('unsubscribe', function(e) { + process.exit(); +}); + +nc.on('error', function(e) { + console.log('Error [' + nc.options.url + ']: ' + e); + process.exit(); +}); diff --git a/examples/node-sub b/examples/node-sub index 8fd99606..1f17b5b1 100755 --- a/examples/node-sub +++ b/examples/node-sub @@ -3,27 +3,34 @@ /* jslint node: true */ 'use strict'; -var nats = require('nats').connect(); +var nats = require('nats'); +var argv = require('minimist')(process.argv.slice(2)); -nats.on('error', function(e) { - console.log('Error [' + nats.options.url + ']: ' + e); +var url = argv.s || nats.DEFAULT_URI; +var creds = argv.creds; +var subject = argv._[0]; + +if (!subject) { + console.log('Usage: node-sub [-s server] [--creds file] '); process.exit(); +} + +// Connect to NATS server. +var nc = nats.connect(url, nats.creds(creds)); + +nc.on('connect', function() { + nc.subscribe(subject, function(msg) { + console.log('Received "' + msg + '"'); + }); + console.log('Listening on [' + subject + ']'); }); -nats.on('close', function() { - console.log('CLOSED'); +nc.on('error', function(e) { + console.log('Error [' + nats.options.url + ']: ' + e); process.exit(); }); -var subject = process.argv[2]; - -if (!subject) { - console.log('Usage: node-sub '); +nc.on('close', function() { + console.log('CLOSED'); process.exit(); -} - -console.log('Listening on [' + subject + ']'); - -nats.subscribe(subject, function(msg) { - console.log('Received "' + msg + '"'); }); diff --git a/lib/nats.js b/lib/nats.js index 7f235bdd..5bb3cfed 100644 --- a/lib/nats.js +++ b/lib/nats.js @@ -24,12 +24,14 @@ var net = require('net'), url = require('url'), util = require('util'), events = require('events'), - nuid = require('nuid'); + nuid = require('nuid'), + nkeys = require('ts-nkeys'), + fs = require('fs'); /** * Constants */ -var VERSION = '1.0.1', +var VERSION = '1.2.0', DEFAULT_PORT = 4222, DEFAULT_PRE = 'nats://localhost:', @@ -50,8 +52,6 @@ var VERSION = '1.0.1', DEFAULT_MAX_PING_OUT = 2, // Protocol - //CONTROL_LINE = /^(.*)\r\n/, // TODO: remove / never used - MSG = /^MSG\s+([^\s\r\n]+)\s+([^\s\r\n]+)\s+(([^\s\r\n]+)[^\S\r\n]+)?(\d+)\r\n/i, OK = /^\+OK\s*\r\n/i, ERR = /^-ERR\s+('.+')?\r\n/i, @@ -59,6 +59,7 @@ var VERSION = '1.0.1', PONG = /^PONG\r\n/i, INFO = /^INFO\s+([^\r\n]+)\r\n/i, SUBRE = /^SUB\s+([^\r\n]+)\r\n/i, + CREDS = /\s*(?:(?:[-]{3,}[^\n]*[-]{3,}\n)(.+)(?:\n\s*[-]{3,}[^\n]*[-]{3,}\n))/i, CR_LF = '\r\n', CR_LF_LEN = CR_LF.length, @@ -66,7 +67,6 @@ var VERSION = '1.0.1', SPC = ' ', // Protocol - //PUB = 'PUB', // TODO: remove / never used SUB = 'SUB', UNSUB = 'UNSUB', CONNECT = 'CONNECT', @@ -102,11 +102,21 @@ var VERSION = '1.0.1', REQ_TIMEOUT_MSG_PREFIX = 'The request timed out for subscription id: ', SECURE_CONN_REQ = 'SECURE_CONN_REQ', SECURE_CONN_REQ_MSG = 'Server requires a secure connection.', - STALE_CONNECTION_ERR = "stale connection", - - // Pedantic Mode support - //Q_SUB = /^([^\.\*>\s]+|>$|\*)(\.([^\.\*>\s]+|>$|\*))*$/, // TODO: remove / never used - //Q_SUB_NO_WC = /^([^\.\*>\s]+)(\.([^\.\*>\s]+))*$/, // TODO: remove / never used + STALE_CONNECTION_ERR = 'stale connection', + SIGNATURE_REQUIRED = 'SIG_REQ', + SIGNATURE_REQUIRED_MSG = 'Server requires an Nkey signature.', + SIGCB_NOTFUNC = 'SIG_NOT_FUNC', + SIGCB_NOTFUNC_MSG = 'Signature callback is not a function.', + NKEY_OR_JWT_REQ = 'NKEY_OR_JWT_REQ', + NKEY_OR_JWT_REQ_MSG = 'An Nkey or User JWT callback needs to be defined.', + BAD_CREDS = 'BAD_CREDENTIALS', + BAD_CREDS_MSG = 'Bad user credentials', + NO_SEED_IN_CREDS = 'NO_SEED_IN_CREDS', + NO_SEED_IN_CREDS_MSG = 'Can not locate signing key in credentials', + NO_USER_JWT_IN_CREDS = 'NO_USER_JWT_IN_CREDS', + NO_USER_JWT_IN_CREDS_MSG = 'Can not locate user jwt in credentials.', + BAD_OPTIONS = 'BAD_OPTIONS', + BAD_OPTIONS_MSG = 'Options should be an object as second paramater.', FLUSH_THRESHOLD = 65536; @@ -178,11 +188,24 @@ function Client(opts) { * Argument can be a url, or an object with a 'url' * property and additional options. * - * @params {Mixed} [opts] - * + * @params {Mixed} [url] - Url, port, or options object + * @params {Object} [opts] - Options * @api public */ -exports.connect = function(opts) { +exports.connect = function(url, opts) { + // If we receive one parameter, parser will + // figure out intent. If we provided opts, then + // first parameter should be url, and second an + // options object. + if (opts !== undefined) { + if ('object' !== typeof opts) { + this.emit('error', new NatsError(BAD_OPTIONS_MSG, BAD_OPTIONS)); + return; + } + opts.url = sanitizeUrl(url); + } else { + opts = url; + } return new Client(opts); }; @@ -238,12 +261,13 @@ Client.prototype.parseOptions = function(opts) { useOldRequestStyle: false }; + if (undefined === opts) { options.url = DEFAULT_URI; } else if ('number' === typeof opts) { options.url = DEFAULT_PRE + opts; } else if ('string' === typeof opts) { - options.url = opts; + options.url = sanitizeUrl(opts); } else if ('object' === typeof opts) { if (opts.port !== undefined) { options.url = DEFAULT_PRE + opts.port; @@ -277,6 +301,18 @@ Client.prototype.parseOptions = function(opts) { this.assignOption(opts, 'pingInterval'); this.assignOption(opts, 'maxPingOut'); this.assignOption(opts, 'useOldRequestStyle'); + this.assignOption(opts, 'sigCB', 'sig'); + this.assignOption(opts, 'sigcb', 'sig'); + this.assignOption(opts, 'sigCallback', 'sig'); + this.assignOption(opts, 'nkey', 'nkey'); + this.assignOption(opts, 'nkeys', 'nkey'); + this.assignOption(opts, 'userjwt', 'userjwt'); + this.assignOption(opts, 'jwt', 'userjwt'); + this.assignOption(opts, 'JWT', 'userjwt'); + this.assignOption(opts, 'userJWT', 'userjwt'); + this.assignOption(opts, 'usercreds', 'usercreds'); + this.assignOption(opts, 'userCreds', 'usercreds'); + this.assignOption(opts, 'credentials', 'usercreds'); } var client = this; @@ -313,7 +349,7 @@ Client.prototype.parseOptions = function(opts) { // if they gave an URL we should add it if different if (options.url !== undefined && client.servers.indexOf(options.url) === -1) { - //make url first element so it is attempted first + // Make url first element so it is attempted first client.servers.unshift(new Server(url.parse(options.url))); } } else { @@ -322,8 +358,29 @@ Client.prototype.parseOptions = function(opts) { } client.servers.push(new Server(url.parse(options.url))); } + // If we are not setup for tls, but were handed a url with a tls:// prefix + // then upgrade to tls. + if (options.tls === false) { + client.servers.forEach(function(server) { + if (server.url.protocol === 'tls' || server.url.protocol === 'tls:') { + options.tls = true; + } + }); + } }; +function sanitizeUrl(url) { + if ((/^.*:\/\/.*/).exec(url) === null) { + // Does not have a scheme. + url = 'nats://' + url; + } + var u = new URL(url); + if (u.port === '') { + u.port = DEFAULT_PORT; + } + return u.toString(); +} + /** * Create a new server. * @@ -381,6 +438,14 @@ Client.prototype.selectServer = function() { * @api private */ Client.prototype.checkTLSMismatch = function() { + // Switch over to TLS as needed on the fly. + if (this.info.tls_required === true || + (this.options.tls !== false && this.stream.encrypted !== true)) { + if (undefined === this.options.tls) { + this.options.tls = true; + } + } + if (this.info.tls_required === true && this.options.tls === false) { this.emit('error', new NatsError(SECURE_CONN_REQ_MSG, SECURE_CONN_REQ)); @@ -403,6 +468,110 @@ Client.prototype.checkTLSMismatch = function() { return false; }; +/** + * Load a user jwt from a chained credential file. + * + * @api private + */ +Client.prototype.loadUserJWT = function() { + var contents = fs.readFileSync(this.options.usercreds); + var m = CREDS.exec(contents); // jwt + if (m === null) { + this.emit('error', new NatsError(NO_USER_JWT_IN_CREDS_MSG, NO_USER_JWT_IN_CREDS)); + this.closeStream(); + return; + } + return m[1]; +}; + +/** + * Load a user nkey seed from a chained credential file + * and sign nonce. + * + * @api private + */ +Client.prototype.loadKeyAndSignNonce = function(nonce) { + var contents = fs.readFileSync(this.options.usercreds); + var re = new RegExp(CREDS, 'g'); + re.exec(contents); // jwt + var m = re.exec(contents); // seed + if (m === null) { + this.emit('error', new NatsError(NO_SEED_IN_CREDS_MSG, NO_SEED_IN_CREDS)); + this.closeStream(); + return; + } + var sk = nkeys.fromSeed(Buffer.from(m[1])); + return sk.sign(nonce); +}; + +/** + * Helper that takes a user credentials file and + * generates the proper opts object with proper handlers + * filled in. e.g nats.connect(url, nats.creds("my_creds.ttx") + * + * @params {String} [filepath] + * + * @api public + */ +exports.creds = function(filepath) { + if (undefined === filepath) { + return undefined; + } + return { + usercreds: filepath + }; +}; + +/** + * Check for Nkey mismatch. + * + * @api private + */ +Client.prototype.checkNkeyMismatch = function() { + if (undefined === this.info.nonce) { + return false; + } + + // If this has been specified make sure we can open the file and parse it. + if (this.options.usercreds !== undefined) { + // Treat this as a filename. + // For now we will not capture an error on file not found etc. + var contents = fs.readFileSync(this.options.usercreds); + if (CREDS.exec(contents) === null) { + this.emit('error', new NatsError(BAD_CREDS_MSG, BAD_CREDS)); + this.closeStream(); + return true; + } + // We have a valid file, set up callback handlers. + var client = this; + this.options.sig = function(nonce) { + return client.loadKeyAndSignNonce(nonce); + }; + this.options.userjwt = function() { + return client.loadUserJWT(); + }; + return false; + } + + if (undefined === this.options.sig) { + this.emit('error', new NatsError(SIGNATURE_REQUIRED_MSG, SIGNATURE_REQUIRED)); + this.closeStream(); + return true; + } + if (typeof (this.options.sig) !== 'function') { + this.emit('error', new NatsError(SIGCB_NOTFUNC_MSG, SIGCB_NOTFUNC)); + this.closeStream(); + return true; + } + if (undefined === this.options.nkey && undefined === this.options.userjwt) { + this.emit('error', new NatsError(NKEY_OR_JWT_REQ_MSG, NKEY_OR_JWT_REQ)); + this.closeStream(); + return true; + } + return false; +}; + + /** * Callback for first flush/connect. * @@ -544,6 +713,20 @@ Client.prototype.sendConnect = function() { 'pedantic': this.options.pedantic, 'protocol': 1 }; + if (this.info.nonce !== undefined && this.options.sig !== undefined) { + var sig = this.options.sig(Buffer.from(this.info.nonce)); + cs.sig = sig.toString('base64'); + } + if (this.options.userjwt !== undefined) { + if (typeof (this.options.userjwt) === 'function') { + cs.jwt = this.options.userjwt(); + } else { + cs.jwt = this.options.userjwt; + } + } + if (this.options.nkey !== undefined) { + cs.nkey = this.options.nkey; + } if (this.user !== undefined) { cs.user = this.user; cs.pass = this.pass; @@ -554,6 +737,10 @@ Client.prototype.sendConnect = function() { if (this.options.name !== undefined) { cs.name = this.options.name; } + if (this.options.nkey !== undefined) { + cs.nkey = this.options.nkey; + } + // If we enqueued requests before we received INFO from the server, or we // reconnected, there be other data pending, write this immediately instead // of adding it to the queue. @@ -610,8 +797,9 @@ Client.prototype.createConnection = function() { // Select a server to connect to. this.selectServer(); - // See #45 if we have a stream release the listeners - // otherwise in addition to the leak events will fire fire + + // See #45 if we have a stream release the listeners otherwise in addition + // to the leaking of events, the old events will still fire. if (this.stream) { this.stream.removeAllListeners(); this.stream.destroy(); @@ -867,19 +1055,21 @@ Client.prototype.processInbound = function() { client.sendCommand(PONG_RESPONSE); } else if ((m = INFO.exec(buf)) !== null) { client.info = JSON.parse(m[1]); - // Check on TLS mismatch. - if (client.checkTLSMismatch() === true) { - return; - } - // Always try to read the connect_urls from info client.processServerUpdate(); // Process first INFO if (client.infoReceived === false) { + // Check on TLS mismatch. + if (client.checkTLSMismatch() === true) { + return; + } + if (client.checkNkeyMismatch() === true) { + return; + } + // Switch over to TLS as needed. - if (client.options.tls !== false && - client.stream.encrypted !== true) { + if (client.info.tls_required === true) { var tlsOpts = { socket: client.stream }; @@ -1046,6 +1236,8 @@ Client.prototype.processServerUpdate = function() { if (newURLs.length) { // new reported servers useful for tests client.emit('serversDiscovered', newURLs); + // simpler version + client.emit('servers', newURLs); } } }; @@ -1066,6 +1258,7 @@ Client.prototype.processMsg = function() { sub.timeout = null; } } + // Check for auto-unsubscribe if (sub.max !== undefined) { if (sub.received === sub.max) { @@ -1517,15 +1710,16 @@ Client.prototype.createResponseMux = function() { var token = client.extractToken(subject); var conf = client.getMuxRequestConfig(token); if (conf) { + if (conf.callback) { + conf.callback(msg, reply); + } if (conf.hasOwnProperty('expected')) { conf.received++; if (conf.received >= conf.expected) { client.cancelMuxRequest(token); + client.emit('unsubscribe', sid, subject); } } - if (conf.callback) { - conf.callback(msg, reply); - } } }); this.respmux = {}; diff --git a/package.json b/package.json index 640df1dd..11a14992 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nats", - "version": "1.0.1", + "version": "1.2.0", "description": "Node.js client for NATS, a lightweight, high-performance cloud native messaging system", "keywords": [ "nats", @@ -41,13 +41,14 @@ "node": ">= 4.5.0" }, "dependencies": { + "minimist": "^1.2.0", "nuid": "^1.0.0", - "why-is-node-running": "^2.0.3" + "ts-nkeys": "^1.0.8" }, "devDependencies": { "coveralls": "^3.0.2", "dependency-check": "^3.2.1", - "eslint": "^5.9.0", + "eslint": "^5.10.0", "if-node-version": "^1.1.1", "istanbul": "^0.4.5", "js-beautify": "^1.6.12", diff --git a/test/auth.js b/test/auth.js index 433afbb3..077ed430 100644 --- a/test/auth.js +++ b/test/auth.js @@ -43,7 +43,7 @@ describe('Authorization', function() { var nc = NATS.connect(PORT); nc.on('error', function(err) { should.exist(err); - should.exist(/Authorization/.exec(err)); + should.exist((/Authorization/).exec(err)); nc.close(); done(); }); @@ -107,7 +107,7 @@ describe('Token Authorization', function() { var nc = NATS.connect(PORT); nc.on('error', function(err) { should.exist(err); - should.exist(/Authorization/.exec(err)); + should.exist((/Authorization/).exec(err)); nc.close(); done(); }); diff --git a/test/certs/key_noip.pem b/test/certs/key_noip.pem new file mode 100644 index 00000000..1cd7baca --- /dev/null +++ b/test/certs/key_noip.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEArFsnvZER/Hy0ZVfBInmgn8A6CFM+TRx3dcaqw5qB7qo6yGQI +fCtGSkF5bxlevf820QHA3rRO8jboSOi4CdQ0Bis9c8XleBQFuy/EMJMcZwWpW5jO +XuMOyKbJZPtucceJdl5jbYJum3whPklFa4iIifQWth9BIPQRqw06BXU7aVgKEfJi +3BFIihamQFj1YUcDAvg5zPn2rXo3m3RRVtMmqWDldxPnRFQcvmoon723J5yn5wcR +JO21sH4vgHm8tKuxFpVH5go0tmrJUPRkG6E6zURY8+qBhwXeQPzH1kUzP2w6z21k +NFYrfxhdviSJyVzqMPkqAKcTRNb3o1Po9o/r4wIDAQABAoIBABmg4FLmzGbf4C8l +klle/oxil/5+6kerEPRZWzEEu4dSlrUPcteL08Lc44KqohTcazk+6n/zDb4kffvB +ZwRHIok8SsH5TjrBt2xzGNgkN9f11o4BNcaUOrGJY275mB2hDwaj2GMDPxEtrv/F +A2aPAcaJngrssNGGmU33BIalPPVX3HHXy/DgyJGJtMOqjba3AclOjL3dNETExIG1 +LEZVu29xzUo1+g4iyM4Rtwred/rvprw7l+k7tC0EqJqu8PEMJanBwEt1Vc66Its3 +02b3l+X8E2tU43arSP2mOMmsgnyCVchIiU/UXbRyto9ngXl2tUyapLvzwPGGfuX6 +GXuA/9kCgYEA3T6OiMioN34jGkBjIH56D1p7iLfoxaNsb2CjHyFrgOcbGgDRJL6Z +fB7JNK3bZDMzOSBO6Fz9GogsXh0bX+6SDb+xTjqdDxv4Icz12e2lFUUKLzaLZtgX ++2yay89geKmj78gqsF+WBy8lB36D5zBXXD57udVRmKCH4oQlAHC9TCUCgYEAx26I +m+4JHgP7/3I51ZaSnpaJMeYf0gWWRfHmgF/bWJl+3CSbYy+e3Jn+SXAfqw2j/zYK +4R8PmAMtp3qM8uFW9aU+IdSuBsl/qzyTKJDxlvNHE8R0zvwyELmOPN0d1dpMhyuO +ohAhAtlvpVFboJoex6Cv5Mq6CvzM9OcLZ9ZGVWcCgYEAgIveSP89LbDuOEx8y95w +kQcji5Y/esWvNS9S172VNroQdxVObykHCKTihucU96z/8AsjjcKplIbWMIB87Uoy +NibQoAFSS/sWOp1Zoxt+tVL9zdzFNiYMGRtK/WwqQzFdfO4yT3PaOaZcv3P0s5jU +yVXMkXauCB4NlcDttsnKUrkCgYBGpbFfC4PPTjoG08AJGb32z/zp4EuFatVBEeBF +ua9KQ5XdBlrFWCk+nI6oFUAiqEJgNhTEiuxLPVT+7zrfD1Ru0IqiEWVUoizHzD2V +MUNj5epX5lA6wrw5kIICrZHUH/DcMWHlpzEKgVw7hAahbfdZYGLu+aHOIp+4YeB4 +eD+SIQKBgBc7SodbrugKLrLCaH3pzl4jahbbVvQ6G6T2RwDTeFG8UGI2yLvyfZ3Y +NTYq/UaNOrKpNL4ThB2FZmfpRiWuJ9FvWXmuMWmHzKIcT142tN+EBye7y8V6mURE +SU49GLqU+6fNvu2KdFZAUQnJIzc+WXrT8Qsr5ka/ENHLp8I3o03e +-----END RSA PRIVATE KEY----- diff --git a/test/certs/server_noip.pem b/test/certs/server_noip.pem new file mode 100644 index 00000000..a7317be2 --- /dev/null +++ b/test/certs/server_noip.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEjzCCAncCCQDvpOBu2zU8sDANBgkqhkiG9w0BAQsFADCBizELMAkGA1UEBhMC +VVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQK +EwpBcGNlcmEgSW5jMRAwDgYDVQQLEwduYXRzLmlvMRIwEAYDVQQDEwlsb2NhbGhv +c3QxHDAaBgkqhkiG9w0BCQEWDWRlcmVrQG5hdHMuaW8wHhcNMTgxMTI0MjA1MTQ1 +WhcNMjgxMTIxMjA1MTQ1WjCBhjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRQw +EgYDVQQHDAtMb3MgQW5nZWxlczEQMA4GA1UECgwHU3luYWRpYTEQMA4GA1UECwwH +TkFUUy5pbzESMBAGA1UEAwwJbG9jYWxob3N0MRwwGgYJKoZIhvcNAQkBFg1kZXJl +a0BuYXRzLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArFsnvZER +/Hy0ZVfBInmgn8A6CFM+TRx3dcaqw5qB7qo6yGQIfCtGSkF5bxlevf820QHA3rRO +8jboSOi4CdQ0Bis9c8XleBQFuy/EMJMcZwWpW5jOXuMOyKbJZPtucceJdl5jbYJu +m3whPklFa4iIifQWth9BIPQRqw06BXU7aVgKEfJi3BFIihamQFj1YUcDAvg5zPn2 +rXo3m3RRVtMmqWDldxPnRFQcvmoon723J5yn5wcRJO21sH4vgHm8tKuxFpVH5go0 +tmrJUPRkG6E6zURY8+qBhwXeQPzH1kUzP2w6z21kNFYrfxhdviSJyVzqMPkqAKcT +RNb3o1Po9o/r4wIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQBaw+X7f5kzrvhDFAc9 +ToSRfKW4P7gcnSTODMq3HqgI3mpjmgJKx7WJMYb7YTk4xrbREPQzr29rRCdCncOs +sM8hZZ/4CgVg+l7CgZFFviJ6tv0EWgmKj2H7V0nsC9qsEoCVNWQngTvs8vahLY9R +LBV1cGN33t2FA/y8Y2+ioSKxf2iyq7uGx9uVT/upaOtLm12Qex8Q1eb9xAE5Zs4m +wbZqIDocR5FCzWXSKOkpXmk1ii9oMars++dGq6XWoFhU2Yr3Yh443aLJCSVAcayW +QHlWAl73SKAorhvDl+7EWAK6dzSVMv52xCsyAzDCx1ek119e09NzxAjth9mcI7PC +a9sH2WJuUuQUu9lBAa13QvIepZLBDw6AaFnsh4ve7xFPPiRr9JRLUxRUxiUvLMDO +8UDLRcYxqbMMSECbkmiZeylROTCXj3WLXtqWMoeDmiRX5m46E9FEAkPCkz1XFeeW +5tuRhk4gdyvWWWYNIQBWwcjnK0M0BxFfFOMNc+BDiq8wydFBk79b8oyk7ayYSuzk +MEC5R/YwL+pEYdK1QqaGoNySXPnserNOw9KFxXsEINTJGSl0WJqW0qPEAEfgEBRY +E2s//VCHsqY9/yTo3DMNy00DkFTtGqOCIA21NN/xPtPcxwSAvizgRbeNMysXiW/q +b7bzpfVnBWoHkt/QMi+jI1A3iQ== +-----END CERTIFICATE----- diff --git a/test/configs/nkey.conf b/test/configs/nkey.conf new file mode 100644 index 00000000..42f37837 --- /dev/null +++ b/test/configs/nkey.conf @@ -0,0 +1,6 @@ + +listen: 127.0.0.1:22233 + +authorization { + users = [ {nkey: "UAH42UG6PV552P5SWLWTBP3H3S5BHAVCO2IEKEXUANJXR75J63RQ5WM6"}] +} diff --git a/test/configs/nkeys/op.jwt b/test/configs/nkeys/op.jwt new file mode 100644 index 00000000..93356896 --- /dev/null +++ b/test/configs/nkeys/op.jwt @@ -0,0 +1,3 @@ +-----BEGIN TEST OPERATOR JWT----- +eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJhdWQiOiJURVNUUyIsImV4cCI6MTg1OTEyMTI3NSwianRpIjoiWE5MWjZYWVBIVE1ESlFSTlFPSFVPSlFHV0NVN01JNVc1SlhDWk5YQllVS0VRVzY3STI1USIsImlhdCI6MTU0Mzc2MTI3NSwiaXNzIjoiT0NBVDMzTVRWVTJWVU9JTUdOR1VOWEo2NkFIMlJMU0RBRjNNVUJDWUFZNVFNSUw2NU5RTTZYUUciLCJuYW1lIjoiU3luYWRpYSBDb21tdW5pY2F0aW9ucyBJbmMuIiwibmJmIjoxNTQzNzYxMjc1LCJzdWIiOiJPQ0FUMzNNVFZVMlZVT0lNR05HVU5YSjY2QUgyUkxTREFGM01VQkNZQVk1UU1JTDY1TlFNNlhRRyIsInR5cGUiOiJvcGVyYXRvciIsIm5hdHMiOnsic2lnbmluZ19rZXlzIjpbIk9EU0tSN01ZRlFaNU1NQUo2RlBNRUVUQ1RFM1JJSE9GTFRZUEpSTUFWVk40T0xWMllZQU1IQ0FDIiwiT0RTS0FDU1JCV1A1MzdEWkRSVko2NTdKT0lHT1BPUTZLRzdUNEhONk9LNEY2SUVDR1hEQUhOUDIiLCJPRFNLSTM2TFpCNDRPWTVJVkNSNlA1MkZaSlpZTVlXWlZXTlVEVExFWjVUSzJQTjNPRU1SVEFCUiJdfX0.hyfz6E39BMUh0GLzovFfk3wT4OfualftjdJ_eYkLfPvu5tZubYQ_Pn9oFYGCV_6yKy3KMGhWGUCyCdHaPhalBw +------END TEST OPERATOR JWT------ \ No newline at end of file diff --git a/test/configs/nkeys/test.creds b/test/configs/nkeys/test.creds new file mode 100644 index 00000000..c39831e3 --- /dev/null +++ b/test/configs/nkeys/test.creds @@ -0,0 +1,11 @@ +-----BEGIN NATS USER JWT----- +eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJFU1VQS1NSNFhGR0pLN0FHUk5ZRjc0STVQNTZHMkFGWERYQ01CUUdHSklKUEVNUVhMSDJBIiwiaWF0IjoxNTQ0MjE3NzU3LCJpc3MiOiJBQ1pTV0JKNFNZSUxLN1FWREVMTzY0VlgzRUZXQjZDWENQTUVCVUtBMzZNSkpRUlBYR0VFUTJXSiIsInN1YiI6IlVBSDQyVUc2UFY1NTJQNVNXTFdUQlAzSDNTNUJIQVZDTzJJRUtFWFVBTkpYUjc1SjYzUlE1V002IiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ.kCR9Erm9zzux4G6M-V2bp7wKMKgnSNqMBACX05nwePRWQa37aO_yObbhcJWFGYjo1Ix-oepOkoyVLxOJeuD8Bw +------END NATS USER JWT------ + +************************* IMPORTANT ************************* +NKEY Seed printed below can be used sign and prove identity. +NKEYs are sensitive and should be treated as secrets. + +-----BEGIN USER NKEY SEED----- +SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ2KFE4PEJUA44CNHTC4 +------END USER NKEY SEED------ diff --git a/test/configs/nkeys/test.seed b/test/configs/nkeys/test.seed new file mode 100644 index 00000000..8a60e9c8 --- /dev/null +++ b/test/configs/nkeys/test.seed @@ -0,0 +1,7 @@ +######################################################## +# TESTS ONLY # +######################################################## + +-----BEGIN TEST OPERATOR SEED----- +SOAFYNORQLQFJYBYNUGC5D7SH2MXMUX5BFEWWGHN3EK4VGG5TPT5DZP7QU +------END TEST OPERATOR SEED------ diff --git a/test/configs/operator.conf b/test/configs/operator.conf new file mode 100644 index 00000000..119fee51 --- /dev/null +++ b/test/configs/operator.conf @@ -0,0 +1,21 @@ +# Server that loads an operator JWT + +listen: 127.0.0.1:22222 + +# Can be an array of filenames as well. +# Key can be operator, operators, roots, root, root_operators, root_operator + +operator = "./test/configs/nkeys/op.jwt" + +# This is for account resolution. +# Can be MEMORY (Testing) or can be URL(url). +# The resolver will append the account name to url for retrieval. +# E.g. +# resolver = URL("https://api.synadia.com/ngs/v1/accounts/jwt") +# +resolver = MEMORY + +# This is a map that can preload keys:jwts into a memory resolver. +resolver_preload = { + ACZSWBJ4SYILK7QVDELO64VX3EFWB6CXCPMEBUKA36MJJQRPXGEEQ2WJ : "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJXVFdYVDNCT1JWSFNLQkc2T0pIVVdFQ01QRVdBNldZVEhNRzVEWkJBUUo1TUtGU1dHM1FRIiwiaWF0IjoxNTQ0MjE3NzU3LCJpc3MiOiJPQ0FUMzNNVFZVMlZVT0lNR05HVU5YSjY2QUgyUkxTREFGM01VQkNZQVk1UU1JTDY1TlFNNlhRRyIsInN1YiI6IkFDWlNXQko0U1lJTEs3UVZERUxPNjRWWDNFRldCNkNYQ1BNRUJVS0EzNk1KSlFSUFhHRUVRMldKIiwidHlwZSI6ImFjY291bnQiLCJuYXRzIjp7ImxpbWl0cyI6eyJzdWJzIjotMSwiY29ubiI6LTEsImltcG9ydHMiOi0xLCJleHBvcnRzIjotMSwiZGF0YSI6LTEsInBheWxvYWQiOi0xLCJ3aWxkY2FyZHMiOnRydWV9fX0.q-E7bBGTU0uoTmM9Vn7WaEHDzCUrqvPDb9mPMQbry_PNzVAjf0RG9vd15lGxW5lu7CuGVqpj4CYKhNDHluIJAg" +} diff --git a/test/connect.js b/test/connect.js index 1d58ea1b..e45e7e69 100644 --- a/test/connect.js +++ b/test/connect.js @@ -37,7 +37,6 @@ describe('Basic Connectivity', function() { nsc.stop_server(server, done); }); - it('should perform basic connect with port', function() { var nc = NATS.connect(PORT); should.exist(nc); diff --git a/test/nkeys.js b/test/nkeys.js new file mode 100644 index 00000000..9ac9dfc2 --- /dev/null +++ b/test/nkeys.js @@ -0,0 +1,69 @@ +/* + * Copyright 2018 The NATS Authors + * 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. + */ + +/* jslint node: true */ +/* global describe: false, before: false, after: false, it: false */ +'use strict'; + +var NATS = require('../'), + nkeys = require('ts-nkeys'), + nsc = require('./support/nats_server_control'), + should = require('should'), + fs = require('fs'); + +describe('Direct NKeys and Signatures', function() { + this.timeout(5000); + + var PORT = 22233; + var server; + + // Start up our own nats-server + before(function(done) { + // We need v2 or above for these tests. + var version = nsc.server_version(); + if ((/\s+1\./).exec(version) !== null) { + this.skip(); + } + var flags = ['-c', './test/configs/nkey.conf']; + server = nsc.start_server(PORT, flags, done); + }); + + var nkey_seed = 'SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ2KFE4PEJUA44CNHTC4'; + + // Shutdown our server after we are done + after(function(done) { + nsc.stop_server(server, done); + }); + + it('should connect with direct nkey and sig', function(done) { + var nc = NATS.connect({ + port: PORT, + nkey: 'UAH42UG6PV552P5SWLWTBP3H3S5BHAVCO2IEKEXUANJXR75J63RQ5WM6', + sigCB: function(nonce) { + var sk = nkeys.fromSeed(Buffer.from(nkey_seed)); + return sk.sign(nonce); + } + }); + nc.on('connect', function(client) { + client.should.equal(nc); + nc.close(); + done(); + }); + nc.on('error', function(err) { + nc.close(); + done(err); + }); + }); +}); diff --git a/test/subevents.js b/test/subevents.js index fa001b9b..e069032e 100644 --- a/test/subevents.js +++ b/test/subevents.js @@ -99,7 +99,7 @@ describe('Subscription Events', function() { nc.publish(subj); }); - it('should generate only unsubscribe events on auto-unsub', function(done) { + it('should generate only one unsubscribe events on auto-unsub', function(done) { var nc = NATS.connect(PORT); var subj = 'autounsub.event'; var eventsReceived = 0; @@ -120,4 +120,23 @@ describe('Subscription Events', function() { }); }); + it('should generate unsubscribe events on request max', function(done) { + var nc = NATS.connect(PORT); + var subj = 'request.autounsub.event'; + + nc.subscribe(subj, function(subject, reply) { + nc.publish(reply, "OK"); + }); + nc.request(subj, null, { + max: 1 + }); + + nc.on('unsubscribe', function(sid, subject) { + should.exist(sid); + should.exist(subject); + nc.close(); + done(); + }); + }); + }); diff --git a/test/support/nats_server_control.js b/test/support/nats_server_control.js index dcdb525a..fc93752d 100644 --- a/test/support/nats_server_control.js +++ b/test/support/nats_server_control.js @@ -18,10 +18,17 @@ var spawn = require('child_process').spawn; var net = require('net'); +var sync = require('child_process').execSync; var SERVER = (process.env.TRAVIS) ? 'gnatsd/gnatsd' : 'gnatsd'; var DEFAULT_PORT = 4222; +function server_version() { + return sync(SERVER + ' -v', { + timeout: 1000 + }).toString(); +} + function start_server(port, opt_flags, done) { if (!port) { port = DEFAULT_PORT; @@ -36,9 +43,8 @@ function start_server(port, opt_flags, done) { flags = flags.concat(opt_flags); } - if (process.env.PRINT_LAUNCH_CMD) { - console.log(flags.join(" ")); + console.log(flags.join(' ')); } var server = spawn(SERVER, flags); @@ -117,6 +123,7 @@ function start_server(port, opt_flags, done) { } exports.start_server = start_server; +exports.server_version = server_version; function wait_stop(server, done) { if (server.killed) { diff --git a/test/tls.js b/test/tls.js index 60283f3e..707727cd 100644 --- a/test/tls.js +++ b/test/tls.js @@ -63,17 +63,7 @@ describe('TLS', function() { }); nc.on('error', function(err) { should.exist(err); - should.exist(/Server does not support a secure/.exec(err)); - nc.close(); - done(); - }); - }); - - it('should error if server requires TLS', function(done) { - var nc = NATS.connect(TLSPORT); - nc.on('error', function(err) { - should.exist(err); - should.exist(/Server requires a secure/.exec(err)); + should.exist((/Server does not support a secure/).exec(err)); nc.close(); done(); }); @@ -86,7 +76,7 @@ describe('TLS', function() { }); nc.on('error', function(err) { should.exist(err); - should.exist(/unable to verify the first certificate/.exec(err)); + should.exist((/unable to verify the first certificate/).exec(err)); nc.close(); done(); }); @@ -133,14 +123,14 @@ describe('TLS', function() { }); nc.on('error', function(err) { should.exist(err); - should.exist(/Server requires a client certificate/.exec(err)); + should.exist((/Server requires a client certificate/).exec(err)); nc.close(); done(); }); }); - it('should be authrorized with proper cert', function(done) { + it('should be authorized with proper cert', function(done) { var tlsOptions = { key: fs.readFileSync('./test/certs/client-key.pem'), cert: fs.readFileSync('./test/certs/client-cert.pem'), diff --git a/test/tls_noip.js b/test/tls_noip.js new file mode 100644 index 00000000..7a9a890d --- /dev/null +++ b/test/tls_noip.js @@ -0,0 +1,74 @@ +/* + * Copyright 2018 The NATS Authors + * 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. + */ + +/* jslint node: true */ +/* global describe: false, before: false, after: false, it: false */ +'use strict'; + +var NATS = require('../'), + nsc = require('./support/nats_server_control'), + should = require('should'), + fs = require('fs'); + +describe('TLS No IPs', function() { + this.timeout(5000); + + // this to enable per test cleanup + var servers; + + // Shutdown our servers + afterEach(function(done) { + nsc.stop_cluster(servers, function() { + servers = []; + done(); + }); + }); + + it('should reconnect via tls with discovered server ip only', function(done) { + var route_port = 55220; + var port = 54222; + var ports = [port, port + 1, port + 2]; + var flags = ['--tls', '--tlscert', './test/certs/server_noip.pem', + '--tlskey', './test/certs/key_noip.pem' + ]; + servers = nsc.start_cluster(ports, route_port, flags, function() { + should(servers.length).be.equal(3); + var nc = NATS.connect({ + url: "tls://localhost:" + port, + noRandomize: true, + reconnectTimeWait: 100, + tls: { + ca: [fs.readFileSync('./test/certs/ca.pem')] + } + }); + + nc.on('connect', function() { + var s = nsc.find_server(port, servers); + nsc.stop_server(s); + }); + nc.on('reconnect', function() { + nc.close(); + done(); + }); + nc.on('close', function() { + done("Did not reconnect"); + }); + nc.on('error', function(err) { + nc.close(); + done(); + }); + }); + }); +}); diff --git a/test/userjwt.js b/test/userjwt.js new file mode 100644 index 00000000..20d2ffef --- /dev/null +++ b/test/userjwt.js @@ -0,0 +1,160 @@ +/* + * Copyright 2018 The NATS Authors + * 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. + */ + +/* jslint node: true */ +/* global describe: false, before: false, after: false, it: false */ +'use strict'; + +var NATS = require('../'), + nkeys = require('ts-nkeys'), + nsc = require('./support/nats_server_control'), + should = require('should'), + fs = require('fs'); + +describe('NKeys, Signatures and User JWTs', function() { + this.timeout(5000); + + var PORT = 22222; + var server; + + var uSeed = "SUAIBDPBAUTWCWBKIO6XHQNINK5FWJW4OHLXC3HQ2KFE4PEJUA44CNHTC4"; + var uJWT = "eyJ0eXAiOiJqd3QiLCJhbGciOiJlZDI1NTE5In0.eyJqdGkiOiJFU1VQS1NSNFhGR0pLN0FHUk5ZRjc0STVQNTZHMkFGWERYQ01CUUdHSklKUEVNUVhMSDJBIiwiaWF0IjoxNTQ0MjE3NzU3LCJpc3MiOiJBQ1pTV0JKNFNZSUxLN1FWREVMTzY0VlgzRUZXQjZDWENQTUVCVUtBMzZNSkpRUlBYR0VFUTJXSiIsInN1YiI6IlVBSDQyVUc2UFY1NTJQNVNXTFdUQlAzSDNTNUJIQVZDTzJJRUtFWFVBTkpYUjc1SjYzUlE1V002IiwidHlwZSI6InVzZXIiLCJuYXRzIjp7InB1YiI6e30sInN1YiI6e319fQ.kCR9Erm9zzux4G6M-V2bp7wKMKgnSNqMBACX05nwePRWQa37aO_yObbhcJWFGYjo1Ix-oepOkoyVLxOJeuD8Bw"; + + // Start up our own nats-server + before(function(done) { + // We need v2 or above for these tests. + var version = nsc.server_version(); + if ((/\s+1\./).exec(version) !== null) { + this.skip(); + } + var flags = ['-c', './test/configs/operator.conf']; + server = nsc.start_server(PORT, flags, done); + }); + + var uri = 'nats://localhost:' + PORT; + + // Shutdown our server after we are done + after(function(done) { + nsc.stop_server(server, done); + }); + + it('should error when no signature callback provided', function(done) { + var nc = NATS.connect(PORT); + nc.on('error', function(err) { + should.exist(err); + should.exist((/requires an nkey signature/i).exec(err)); + nc.close(); + done(); + }); + }); + + it('should error when sigcb not a function', function(done) { + var nc = NATS.connect({ + port: PORT, + sigCB: "BAD" + }); + nc.on('error', function(err) { + should.exist(err); + should.exist((/not a function/).exec(err)); + nc.close(); + done(); + }); + }); + + it('should error when no nkey or userJWT callback defined', function(done) { + var nc = NATS.connect({ + port: PORT, + sigCB: function(nonce) {}, + }); + nc.on('error', function(err) { + should.exist(err); + should.exist((/Nkey or User JWT/).exec(err)); + nc.close(); + done(); + }); + }); + + it('should connect when userJWT and sig provided', function(done) { + var nc = NATS.connect({ + port: PORT, + sigCB: function(nonce) { + var sk = nkeys.fromSeed(Buffer.from(uSeed)); + return sk.sign(nonce); + }, + userJWT: uJWT, + }); + nc.on('connect', function(client) { + client.should.equal(nc); + nc.close(); + done(); + }); + nc.on('error', function(err) { + nc.close(); + done(err); + }); + }); + + it('should connect when userJWT is a callback function', function(done) { + var nc = NATS.connect({ + port: PORT, + sigCB: function(nonce) { + var sk = nkeys.fromSeed(Buffer.from(uSeed)); + return sk.sign(nonce); + }, + userJWT: function() { + return uJWT; + }, + }); + nc.on('connect', function(client) { + client.should.equal(nc); + nc.close(); + done(); + }); + nc.on('error', function(err) { + nc.close(); + done(err); + }); + }); + + it('should connect with a user credentials file', function(done) { + var nc = NATS.connect({ + port: PORT, + userCreds: './test/configs/nkeys/test.creds', + }); + nc.on('connect', function(client) { + client.should.equal(nc); + nc.close(); + done(); + }); + nc.on('error', function(err) { + nc.close(); + done(err); + }); + }); + + it('should connect with new style of connect with url and a user credentials file', function(done) { + var nc = NATS.connect(uri, NATS.creds('./test/configs/nkeys/test.creds')); + nc.on('connect', function(client) { + client.should.equal(nc); + nc.close(); + done(); + }); + nc.on('error', function(err) { + nc.close(); + done(err); + }); + }); + +}); From 5bd57485c24e8bc13fd8b8e75825b4e943b9cabb Mon Sep 17 00:00:00 2001 From: Derek Collison Date: Sat, 8 Dec 2018 09:53:52 -0800 Subject: [PATCH 2/2] Update for new connection/authentication Signed-off-by: Derek Collison --- README.md | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ed70edc..0fecdcb6 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,44 @@ var tlsOptions = { var nc = nats.connect({port: TLSPORT, tls: tlsOptions}); ``` -## Authentication + +## New Authentication (Nkeys and User Credentials) +See examples for more usage. +```javascript +// Simple connect using credentials file. This loads JWT and signing key +// each time that NATS connects. +var nc = NATS.connect('connect.ngs.global', NATS.creds("./myid.creds"); + +// Setting nkey and signing callback directly. +var nc = NATS.connect(url, { + nkey: 'UAH42UG6PV552P5SWLWTBP3H3S5BHAVCO2IEKEXUANJXR75J63RQ5WM6', + sigCB: function(nonce) { + return sk.sign(nonce); + } +}); + +// Setting user JWT statically. +var nc = NATS.connect(url, { + userJWT: myJWT, + sigCB: function(nonce) { + return sk.sign(nonce); + } +}); + +// Having user JWT be a function that returns the JWT. Can be useful for +// loading a new JWT. +var nc = NATS.connect(url, { + userJWT: function() { + return myJWT; + }, + sigCB: function(nonce) { + return sk.sign(nonce); + } +}); + +``` + +## Basic Authentication ```javascript // Connect with username and password in the url