aedes 0.45.1 → 0.46.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.
@@ -20,10 +20,10 @@ jobs:
20
20
  os: [ubuntu-latest, windows-latest, macOS-latest]
21
21
 
22
22
  steps:
23
- - uses: actions/checkout@v2
23
+ - uses: actions/checkout@v2.3.5
24
24
 
25
25
  - name: Use Node.js
26
- uses: actions/setup-node@v2.1.5
26
+ uses: actions/setup-node@v2.3.2
27
27
  with:
28
28
  node-version: ${{ matrix.node-version }}
29
29
 
package/.taprc CHANGED
@@ -2,4 +2,5 @@ ts: false
2
2
  jsx: false
3
3
  flow: false
4
4
  coverage: true
5
- strict: true
5
+ strict: true
6
+ check-coverage: false
package/aedes.d.ts CHANGED
@@ -4,5 +4,6 @@ export declare function aedes (options?: AedesOptions): Aedes
4
4
  export declare function Server (options?: AedesOptions): Aedes
5
5
 
6
6
  export * from './types/instance'
7
+ export * from './types/packet'
7
8
  export * from './types/client'
8
9
  export default aedes
package/aedes.js CHANGED
@@ -12,6 +12,7 @@ const Packet = require('aedes-packet')
12
12
  const memory = require('aedes-persistence')
13
13
  const mqemitter = require('mqemitter')
14
14
  const Client = require('./lib/client')
15
+ const { $SYS_PREFIX } = require('./lib/utils')
15
16
 
16
17
  module.exports = Aedes.Server = Aedes
17
18
 
@@ -78,7 +79,7 @@ function Aedes (opts) {
78
79
  this.clients = {}
79
80
  this.brokers = {}
80
81
 
81
- const heartbeatTopic = '$SYS/' + that.id + '/heartbeat'
82
+ const heartbeatTopic = $SYS_PREFIX + that.id + '/heartbeat'
82
83
  this._heartbeatInterval = setInterval(heartbeat, opts.heartbeatInterval)
83
84
 
84
85
  const bufId = Buffer.from(that.id, 'utf8')
@@ -138,12 +139,12 @@ function Aedes (opts) {
138
139
  }
139
140
  }
140
141
 
141
- this.mq.on('$SYS/+/heartbeat', function storeBroker (packet, done) {
142
+ this.mq.on($SYS_PREFIX + '+/heartbeat', function storeBroker (packet, done) {
142
143
  that.brokers[packet.payload.toString()] = Date.now()
143
144
  done()
144
145
  })
145
146
 
146
- this.mq.on('$SYS/+/new/clients', function closeSameClients (packet, done) {
147
+ this.mq.on($SYS_PREFIX + '+/new/clients', function closeSameClients (packet, done) {
147
148
  const serverId = packet.topic.split('/')[1]
148
149
  const clientId = packet.payload.toString()
149
150
 
@@ -206,7 +207,7 @@ function DoEnqueues () {
206
207
  return
207
208
  }
208
209
 
209
- if (that.topic.indexOf('$SYS') === 0) {
210
+ if (that.topic.indexOf($SYS_PREFIX) === 0) {
210
211
  subs = subs.filter(removeSharp)
211
212
  }
212
213
 
@@ -281,7 +282,7 @@ Aedes.prototype._finishRegisterClient = function (client) {
281
282
  this.clients[client.id] = client
282
283
  this.emit('client', client)
283
284
  this.publish({
284
- topic: '$SYS/' + this.id + '/new/clients',
285
+ topic: $SYS_PREFIX + this.id + '/new/clients',
285
286
  payload: Buffer.from(client.id, 'utf8')
286
287
  }, noop)
287
288
  }
@@ -291,7 +292,7 @@ Aedes.prototype.unregisterClient = function (client) {
291
292
  delete this.clients[client.id]
292
293
  this.emit('clientDisconnect', client)
293
294
  this.publish({
294
- topic: '$SYS/' + this.id + '/disconnect/clients',
295
+ topic: $SYS_PREFIX + this.id + '/disconnect/clients',
295
296
  payload: Buffer.from(client.id, 'utf8')
296
297
  }, noop)
297
298
  }
@@ -326,6 +327,9 @@ function defaultAuthenticate (client, username, password, callback) {
326
327
  }
327
328
 
328
329
  function defaultAuthorizePublish (client, packet, callback) {
330
+ if (packet.topic.startsWith($SYS_PREFIX)) {
331
+ return callback(new Error($SYS_PREFIX + ' topic is reserved'))
332
+ }
329
333
  callback(null)
330
334
  }
331
335
 
package/docs/Aedes.md CHANGED
@@ -113,7 +113,7 @@ Emitted when timeout happes in the `client` keepalive.
113
113
  - `packet` `<aedes-packet>` & [`PUBLISH`][PUBLISH]
114
114
  - `client` [`<Client>`](./Client.md) | `null`
115
115
 
116
- Emitted when servers delivers the `packet` to subscribed `client`. If there are no clients subscribed to the `packet` topic, server still publish the `packet` and emit thie event. `client` is `null` when `packet` is an internal message like aedes heartbeat message and LWT.
116
+ Emitted when servers delivers the `packet` to subscribed `client`. If there are no clients subscribed to the `packet` topic, server still publish the `packet` and emit the event. `client` is `null` when `packet` is an internal message like aedes heartbeat message and LWT.
117
117
 
118
118
  > _Note! `packet` belongs `aedes-packet` type. Some properties belongs to aedes internal, any changes on them will break aedes internal flow._
119
119
 
@@ -315,6 +315,17 @@ aedes.authorizePublish = function (client, packet, callback) {
315
315
  }
316
316
  ```
317
317
 
318
+ By default `authorizePublish` throws error in case a client publish to topics with `$SYS/` prefix to prevent possible DoS (see [#597](https://github.com/moscajs/aedes/issues/597)). If you write your own implementation of `authorizePublish` we suggest you to add a check for this. Default implementation:
319
+
320
+ ```js
321
+ function defaultAuthorizePublish (client, packet, callback) {
322
+ if (packet.topic.startsWith($SYS_PREFIX)) {
323
+ return callback(new Error($SYS_PREFIX + ' topic is reserved'))
324
+ }
325
+ callback(null)
326
+ }
327
+ ```
328
+
318
329
  ## Handler: authorizeSubscribe (client, subscription, callback)
319
330
 
320
331
  - client: [`<Client>`](./Client.md)
@@ -392,7 +403,7 @@ aedes.authorizeForward = function (client, packet) {
392
403
  - client: [`<Client>`](./Client.md)
393
404
  - callback: `<Function>`
394
405
 
395
- same as [`Event: publish`](#event-publish), but provides a backpressure functionality.
406
+ same as [`Event: publish`](#event-publish), but provides a backpressure functionality. TLDR; If you are doing operations on packets that MUST require finishing operations on a packet before handling the next one use this otherwise, expecially for long running operations, you should use [`Event: publish`](#event-publish) instead.
396
407
 
397
408
  [CONNECT]: https://github.com/mqttjs/mqtt-packet#connect
398
409
  [CONNACK]: https://github.com/mqttjs/mqtt-packet#connack
@@ -3,7 +3,7 @@
3
3
  const fastfall = require('fastfall')
4
4
  const Packet = require('aedes-packet')
5
5
  const { through } = require('../utils')
6
- const { validateTopic } = require('../utils')
6
+ const { validateTopic, $SYS_PREFIX } = require('../utils')
7
7
  const write = require('../write')
8
8
 
9
9
  const subscribeTopicActions = fastfall([
@@ -184,7 +184,7 @@ function completeSubscribe (err) {
184
184
  const packet = this.packet
185
185
  const client = this.client
186
186
 
187
- if (packet.messageId) {
187
+ if (packet.messageId !== undefined) {
188
188
  // [MQTT-3.9.3-1]
189
189
  write(client,
190
190
  new SubAck(packet, this.subState.map(obj => obj.granted)),
@@ -207,7 +207,7 @@ function completeSubscribe (err) {
207
207
 
208
208
  broker.emit('subscribe', subs, client)
209
209
  broker.publish({
210
- topic: '$SYS/' + broker.id + '/new/subscribes',
210
+ topic: $SYS_PREFIX + broker.id + '/new/subscribes',
211
211
  payload: Buffer.from(JSON.stringify({
212
212
  clientId: client.id,
213
213
  subs: subs
@@ -234,6 +234,6 @@ function completeSubscribe (err) {
234
234
  }))
235
235
  }
236
236
 
237
- function noop () {}
237
+ function noop () { }
238
238
 
239
239
  module.exports = handleSubscribe
@@ -1,7 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const write = require('../write')
4
- const { validateTopic } = require('../utils')
4
+ const { validateTopic, $SYS_PREFIX } = require('../utils')
5
5
 
6
6
  function UnSubAck (packet) {
7
7
  this.cmd = 'unsuback'
@@ -26,7 +26,7 @@ function handleUnsubscribe (client, packet, done) {
26
26
  }
27
27
  }
28
28
 
29
- if (packet.messageId) {
29
+ if (packet.messageId !== undefined) {
30
30
  if (client.clean) {
31
31
  return actualUnsubscribe(client, packet, done)
32
32
  }
@@ -80,7 +80,7 @@ function completeUnsubscribe (err) {
80
80
  const packet = this.packet
81
81
  const done = this.finish
82
82
 
83
- if (packet.messageId) {
83
+ if (packet.messageId !== undefined) {
84
84
  write(client, new UnSubAck(packet),
85
85
  done)
86
86
  } else {
@@ -90,7 +90,7 @@ function completeUnsubscribe (err) {
90
90
  if ((!client.closed || client.clean === true) && packet.unsubscriptions.length > 0) {
91
91
  client.broker.emit('unsubscribe', packet.unsubscriptions, client)
92
92
  client.broker.publish({
93
- topic: '$SYS/' + client.broker.id + '/new/unsubscribes',
93
+ topic: $SYS_PREFIX + client.broker.id + '/new/unsubscribes',
94
94
  payload: Buffer.from(JSON.stringify({
95
95
  clientId: client.id,
96
96
  subs: packet.unsubscriptions
@@ -99,6 +99,6 @@ function completeUnsubscribe (err) {
99
99
  }
100
100
  }
101
101
 
102
- function noop () {}
102
+ function noop () { }
103
103
 
104
104
  module.exports = handleUnsubscribe
package/lib/utils.js CHANGED
@@ -39,5 +39,6 @@ function through (transform) {
39
39
 
40
40
  module.exports = {
41
41
  validateTopic,
42
- through
42
+ through,
43
+ $SYS_PREFIX: '$SYS/'
43
44
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aedes",
3
- "version": "0.45.1",
3
+ "version": "0.46.2",
4
4
  "description": "Stream-based MQTT broker",
5
5
  "main": "aedes.js",
6
6
  "types": "aedes.d.ts",
@@ -96,14 +96,14 @@
96
96
  ],
97
97
  "license": "MIT",
98
98
  "devDependencies": {
99
- "@sinonjs/fake-timers": "^7.0.5",
100
- "@types/node": "^14.14.41",
99
+ "@sinonjs/fake-timers": "^8.0.1",
100
+ "@types/node": "^16.0.0",
101
101
  "@typescript-eslint/eslint-plugin": "^4.22.0",
102
102
  "@typescript-eslint/parser": "^4.22.0",
103
103
  "concat-stream": "^2.0.0",
104
104
  "duplexify": "^4.1.1",
105
105
  "license-checker": "^25.0.1",
106
- "markdownlint-cli": "^0.27.1",
106
+ "markdownlint-cli": "^0.29.0",
107
107
  "mqtt": "^4.2.6",
108
108
  "mqtt-connection": "^4.1.0",
109
109
  "pre-commit": "^1.2.2",
@@ -112,7 +112,7 @@
112
112
  "snazzy": "^9.0.0",
113
113
  "standard": "^16.0.3",
114
114
  "tap": "^15.0.2",
115
- "tsd": "^0.14.0",
115
+ "tsd": "^0.18.0",
116
116
  "typescript": "^4.2.4",
117
117
  "websocket-stream": "^5.5.2"
118
118
  },
@@ -126,7 +126,7 @@
126
126
  "fastseries": "^2.0.0",
127
127
  "hyperid": "^2.1.0",
128
128
  "mqemitter": "^4.4.1",
129
- "mqtt-packet": "^6.9.1",
129
+ "mqtt-packet": "^7.0.0",
130
130
  "readable-stream": "^3.6.0",
131
131
  "retimer": "^3.0.0",
132
132
  "reusify": "^1.0.4",
package/test/basic.js CHANGED
@@ -95,6 +95,23 @@ test('publish empty topic throws error', function (t) {
95
95
  })
96
96
  })
97
97
 
98
+ test('publish to $SYS topic throws error', function (t) {
99
+ t.plan(1)
100
+
101
+ const s = connect(setup())
102
+ t.teardown(s.broker.close.bind(s.broker))
103
+
104
+ s.inStream.write({
105
+ cmd: 'publish',
106
+ topic: '$SYS/not/allowed',
107
+ payload: 'world'
108
+ })
109
+
110
+ s.broker.on('clientError', function (client, err) {
111
+ t.pass('should emit error')
112
+ })
113
+ })
114
+
98
115
  ;[{ qos: 0, clean: false }, { qos: 0, clean: true }, { qos: 1, clean: false }, { qos: 1, clean: true }].forEach(function (ele) {
99
116
  test('subscribe a single topic in QoS ' + ele.qos + ' [clean=' + ele.clean + ']', function (t) {
100
117
  t.plan(5)
@@ -243,6 +260,35 @@ test('subscribe should have messageId', function (t) {
243
260
  })
244
261
  })
245
262
 
263
+ test('subscribe with messageId 0 should return suback', function (t) {
264
+ t.plan(1)
265
+
266
+ const s = connect(setup())
267
+ t.teardown(s.broker.close.bind(s.broker))
268
+
269
+ s.inStream.write({
270
+ cmd: 'subscribe',
271
+ subscriptions: [{
272
+ topic: 'hello',
273
+ qos: 0
274
+ }],
275
+ messageId: 0
276
+ })
277
+ s.outStream.once('data', function (packet) {
278
+ t.same(packet, {
279
+ cmd: 'suback',
280
+ messageId: 0,
281
+ dup: false,
282
+ length: 3,
283
+ qos: 0,
284
+ retain: false,
285
+ granted: [
286
+ 0
287
+ ]
288
+ }, 'packet matches')
289
+ })
290
+ })
291
+
246
292
  test('unsubscribe', function (t) {
247
293
  t.plan(5)
248
294
 
package/types/client.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { IncomingMessage } from 'http'
2
2
  import { PublishPacket, SubscribePacket, Subscription, Subscriptions, UnsubscribePacket } from './packet'
3
3
  import { Connection } from './instance'
4
+ import { EventEmitter } from 'events'
4
5
 
5
- export interface Client {
6
+ export interface Client extends EventEmitter {
6
7
  id: Readonly<string>
7
8
  clean: Readonly<boolean>
8
9
  version: Readonly<number>
@@ -2,6 +2,7 @@ import { Duplex } from 'stream'
2
2
  import { Socket } from 'net'
3
3
  import { Client } from './client'
4
4
  import type { AedesPublishPacket, ConnectPacket, ConnackPacket, Subscription, PingreqPacket, PublishPacket, PubrelPacket } from './packet'
5
+ import { EventEmitter } from 'events'
5
6
 
6
7
  type LastHearthbeatTimestamp = Date;
7
8
 
@@ -56,7 +57,7 @@ export interface AedesOptions {
56
57
  published?: PublishedHandler
57
58
  }
58
59
 
59
- export interface Aedes {
60
+ export interface Aedes extends EventEmitter {
60
61
  id: Readonly<string>
61
62
  connectedClients: Readonly<number>
62
63
  closed: Readonly<boolean>
@@ -89,4 +90,11 @@ export interface Aedes {
89
90
  callback: () => void
90
91
  ): void
91
92
  close (callback?: () => void): void
93
+
94
+ preConnect: PreConnectHandler
95
+ authenticate: AuthenticateHandler
96
+ authorizePublish: AuthorizePublishHandler
97
+ authorizeSubscribe: AuthorizeSubscribeHandler
98
+ authorizeForward: AuthorizeForwardHandler
99
+ published: PublishedHandler
92
100
  }