node-red-contrib-tdn-eventsub 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -39,6 +39,7 @@ Restart Node-RED. Nodes appear under **TDN REST**. Import the example via *Impor
39
39
  | Auth scheme | Authorization header only. Blank (default): `Authorization: Basic base64(":" + key)`, accepted by server v9.0+. For older servers enter the API-key scheme token from your server's REST API docs; the key is then sent as `Authorization: <scheme> <key>`. |
40
40
  | Header name | API key header only. Sent as `<header name>: <key>`; default `X-API-Key`. |
41
41
  | Cert fingerprint | Optional. SHA-256 (64 hex) or SHA-1 (40 hex) of the **server** certificate, colons optional. When set, the server's self-signed cert is accepted only if it matches; the check runs before any request is written, so the key is never sent to an impostor. A mismatch error prints the actual fingerprint. |
42
+ | Plain HTTP (insecure) | Unencrypted `http://` whatever the URL says; certificate settings are ignored and the key travels in clear text. For bench testing against a non-TLS endpoint only. A TLS error like `unexpected message` or `wrong version number` usually means the endpoint needs this; the error text says so. |
42
43
  | Verify server certificate | Normal CA check when no fingerprint is set. |
43
44
  | Force links onto this host:port | The server builds `href`s from its own machine name. With this on, followed links keep path + query but use your configured host/port. With it off, a link that would downgrade `https` to `http` is refused. |
44
45
  | Client cert / key / PFX | Only if the REST Client item on the server has a thumbprint (pinned client certificate). |
@@ -290,6 +290,7 @@
290
290
  "authScheme": "",
291
291
  "headerName": "X-API-Key",
292
292
  "fingerprint": "",
293
+ "plainHttp": false,
293
294
  "rejectUnauthorized": true,
294
295
  "rewriteHrefs": true,
295
296
  "timeout": 30000,
package/lib/client.js CHANGED
@@ -84,6 +84,18 @@ function buildAuthHeaders(cred) {
84
84
  };
85
85
  }
86
86
 
87
+ // OpenSSL errors seen when the other end isn't speaking TLS at all
88
+ const NOT_TLS_CODES = ['ERR_SSL_WRONG_VERSION_NUMBER', 'ERR_SSL_UNEXPECTED_MESSAGE', 'ERR_SSL_PACKET_LENGTH_TOO_LONG', 'ERR_SSL_HTTP_REQUEST', 'ERR_SSL_RECORD_LAYER_FAILURE'];
89
+
90
+ /** Add a hint to TLS handshake errors that usually mean the endpoint is plain HTTP. */
91
+ function notTlsHint(e) {
92
+ if (e && NOT_TLS_CODES.includes(e.code)) {
93
+ e.message = `TLS handshake failed (${e.code}): the endpoint may not be using TLS. ` +
94
+ 'For a plain-HTTP endpoint tick "Plain HTTP (insecure)" in the server config. ' + e.message;
95
+ }
96
+ return e;
97
+ }
98
+
87
99
  function abortError() {
88
100
  return new RestError('Request aborted', { code: 'ABORTED', aborted: true });
89
101
  }
@@ -101,6 +113,7 @@ class RestClient {
101
113
  * @param {boolean} [o.rejectUnauthorized] used only when no fingerprint is set
102
114
  * @param {string} [o.certPath] [o.keyPath] [o.pfxPath] [o.passphrase] client certificate
103
115
  * @param {boolean} [o.rewriteHrefs] force followed hrefs back onto the configured host:port
116
+ * @param {boolean} [o.plainHttp] use unencrypted http:// whatever the URL says (bench/test only)
104
117
  * @param {number} [o.timeoutMs] default request timeout
105
118
  */
106
119
  constructor(o) {
@@ -109,6 +122,7 @@ class RestClient {
109
122
  if (!raw) throw new RestError('No server URL configured');
110
123
  if (!/^https?:\/\//i.test(raw)) raw = 'https://' + raw;
111
124
  const u = new URL(raw);
125
+ if (o.plainHttp) u.protocol = 'http:';
112
126
  const port = parseInt(o.port, 10);
113
127
  if (port > 0) u.port = String(port);
114
128
  else if (!u.port) u.port = String(DEFAULT_PORT);
@@ -230,7 +244,7 @@ class RestClient {
230
244
  if (!net.isIP(host)) opts.servername = host;
231
245
 
232
246
  sock = tls.connect(opts);
233
- sock.once('error', (e) => finish(e));
247
+ sock.once('error', (e) => finish(notTlsHint(e)));
234
248
  sock.once('secureConnect', () => {
235
249
  if (pinning) {
236
250
  const c = sock.getPeerCertificate();
@@ -16,6 +16,7 @@
16
16
  return h.length === 40 || h.length === 64;
17
17
  }
18
18
  },
19
+ plainHttp: { value: false },
19
20
  rejectUnauthorized: { value: true },
20
21
  rewriteHrefs: { value: true },
21
22
  timeout: { value: 30000, validate: RED.validators.number() },
@@ -39,6 +40,13 @@
39
40
  if (!this.authType) $('#node-config-input-authType').val('authorization');
40
41
  $('#node-config-input-authType').on('change', sync);
41
42
  sync();
43
+ var syncTls = function () {
44
+ var plain = $('#node-config-input-plainHttp').is(':checked');
45
+ $('.tdn-rest-tls').toggle(!plain);
46
+ $('.tdn-rest-plain-warning').toggle(plain);
47
+ };
48
+ $('#node-config-input-plainHttp').on('change', syncTls);
49
+ syncTls();
42
50
  }
43
51
  });
44
52
  </script>
@@ -76,10 +84,16 @@
76
84
  <input type="text" id="node-config-input-headerName" placeholder="X-API-Key">
77
85
  </div>
78
86
  <div class="form-row">
87
+ <label>&nbsp;</label>
88
+ <input type="checkbox" id="node-config-input-plainHttp" style="display:inline-block;width:auto;vertical-align:top">
89
+ <label for="node-config-input-plainHttp" style="width:auto">Plain HTTP (insecure, no TLS)</label>
90
+ </div>
91
+ <div class="form-tips tdn-rest-plain-warning"><b>Insecure:</b> the API key and all data are sent unencrypted. Use only for bench testing against a non-TLS endpoint.</div>
92
+ <div class="form-row tdn-rest-tls">
79
93
  <label for="node-config-input-fingerprint"><i class="fa fa-certificate"></i> Cert fingerprint</label>
80
94
  <input type="text" id="node-config-input-fingerprint" placeholder="optional SHA-256 or SHA-1 of server cert">
81
95
  </div>
82
- <div class="form-row">
96
+ <div class="form-row tdn-rest-tls">
83
97
  <label>&nbsp;</label>
84
98
  <input type="checkbox" id="node-config-input-rejectUnauthorized" style="display:inline-block;width:auto;vertical-align:top">
85
99
  <label for="node-config-input-rejectUnauthorized" style="width:auto">Verify server certificate against CA (ignored when a fingerprint is set)</label>
@@ -93,6 +107,7 @@
93
107
  <label for="node-config-input-timeout"><i class="fa fa-clock-o"></i> Timeout (ms)</label>
94
108
  <input type="text" id="node-config-input-timeout" placeholder="30000" style="width:100px">
95
109
  </div>
110
+ <div class="tdn-rest-tls">
96
111
  <hr>
97
112
  <div class="form-tips">Client certificate &mdash; only if the REST Client item on the server has a thumbprint set. Use PEM cert + key, <i>or</i> a PFX.</div>
98
113
  <div class="form-row">
@@ -111,6 +126,7 @@
111
126
  <label for="node-config-input-passphrase">Passphrase</label>
112
127
  <input type="password" id="node-config-input-passphrase">
113
128
  </div>
129
+ </div>
114
130
  </script>
115
131
 
116
132
  <script type="text/html" data-help-name="tdn-rest-server">
@@ -138,6 +154,10 @@
138
154
  When set, the connection is accepted only if the presented certificate matches, even if it is self-signed.
139
155
  The check happens before any request (and therefore the API key) is sent.
140
156
  A mismatch error reports the actual fingerprint so you can copy it.</dd>
157
+ <dt>Plain HTTP (insecure)</dt>
158
+ <dd>Connect with unencrypted <code>http://</code> whatever the URL says. The API key travels in clear text and the
159
+ certificate settings are ignored, so use it only for bench testing against a non-TLS endpoint. A TLS handshake error such as
160
+ <code>unexpected message</code> or <code>wrong version number</code> usually means the endpoint needs this.</dd>
141
161
  <dt>Verify server certificate</dt>
142
162
  <dd>Normal CA verification when no fingerprint is set. Untick only for lab use.</dd>
143
163
  <dt>Force returned links back onto this host:port</dt>
@@ -16,6 +16,7 @@ module.exports = function (RED) {
16
16
  this.headerName = (config.headerName || '').trim();
17
17
  this.rejectUnauthorized = config.rejectUnauthorized !== false;
18
18
  this.rewriteHrefs = config.rewriteHrefs !== false;
19
+ this.plainHttp = config.plainHttp === true;
19
20
  this.timeout = parseInt(config.timeout, 10) || 30000;
20
21
  this.certPath = config.certPath;
21
22
  this.keyPath = config.keyPath;
@@ -36,13 +37,17 @@ module.exports = function (RED) {
36
37
  fingerprint: this.fingerprint,
37
38
  rejectUnauthorized: this.rejectUnauthorized,
38
39
  rewriteHrefs: this.rewriteHrefs,
40
+ plainHttp: this.plainHttp,
39
41
  timeoutMs: this.timeout,
40
42
  certPath: this.certPath,
41
43
  keyPath: this.keyPath,
42
44
  pfxPath: this.pfxPath,
43
45
  passphrase: creds.passphrase
44
46
  });
45
- if (this.client.base.protocol === 'http:') this.warn('REST server URL is http:// - the API key will be sent unencrypted');
47
+ if (this.client.base.protocol === 'http:') {
48
+ this.warn('REST server uses plain HTTP - the API key will be sent unencrypted' +
49
+ (this.fingerprint || this.certPath || this.pfxPath ? '; certificate settings are ignored' : ''));
50
+ }
46
51
  } catch (e) {
47
52
  this.clientError = e;
48
53
  this.error('REST server config: ' + e.message);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-tdn-eventsub",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Node-RED nodes for an access-control REST Server API: access groups, cardholders and items lists to global context, plus continuous item status subscriptions.",
5
5
  "main": "lib/client.js",
6
6
  "scripts": {