1178 lines
27 KiB
JavaScript
1178 lines
27 KiB
JavaScript
'use strict'
|
|
|
|
const { test } = require('tap')
|
|
const eos = require('end-of-stream')
|
|
const Faketimers = require('@sinonjs/fake-timers')
|
|
const Client = require('../lib/client')
|
|
const { setup, connect, noError, subscribe, subscribeMultiple } = require('./helper')
|
|
const aedes = require('../')
|
|
|
|
test('authenticate successfully a client with username and password', function (t) {
|
|
t.plan(4)
|
|
|
|
const s = noError(setup())
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
cb(null, true)
|
|
}
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 0,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'successful connack')
|
|
})
|
|
})
|
|
|
|
test('authenticate unsuccessfully a client with username and password', function (t) {
|
|
t.plan(6)
|
|
|
|
const s = setup()
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
cb(null, false)
|
|
}
|
|
|
|
s.broker.on('clientError', function (client, err) {
|
|
t.equal(err.errorCode, 5)
|
|
})
|
|
|
|
s.broker.on('clientReady', function (client) {
|
|
t.fail('client should not ready')
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 5,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack, unauthorized')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
test('authenticate errors', function (t) {
|
|
t.plan(7)
|
|
|
|
const s = setup()
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
cb(new Error('this should happen!'))
|
|
}
|
|
|
|
s.broker.on('clientError', function (client, err) {
|
|
t.equal(err.message, 'this should happen!')
|
|
t.equal(err.errorCode, 5)
|
|
})
|
|
|
|
s.broker.on('clientReady', function (client) {
|
|
t.fail('client should not ready')
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 5,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack, unauthorized')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
test('authentication error when return code 1 (unacceptable protocol version) is passed', function (t) {
|
|
t.plan(7)
|
|
|
|
const s = setup()
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
const error = new Error('Auth error')
|
|
error.returnCode = 1
|
|
cb(error, null)
|
|
}
|
|
|
|
s.broker.on('clientError', function (client, err) {
|
|
t.equal(err.message, 'Auth error')
|
|
t.equal(err.errorCode, 5)
|
|
})
|
|
|
|
s.broker.on('clientReady', function (client) {
|
|
t.fail('client should not ready')
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 5,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack, unauthorized')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
test('authentication error when return code 2 (identifier rejected) is passed', function (t) {
|
|
t.plan(7)
|
|
|
|
const s = setup()
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
const error = new Error('Auth error')
|
|
error.returnCode = 2
|
|
cb(error, null)
|
|
}
|
|
|
|
s.broker.on('clientError', function (client, err) {
|
|
t.equal(err.message, 'Auth error')
|
|
t.equal(err.errorCode, 2)
|
|
})
|
|
|
|
s.broker.on('clientReady', function (client) {
|
|
t.fail('client should not ready')
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 2,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack, identifier rejected')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
test('authentication error when return code 3 (Server unavailable) is passed', function (t) {
|
|
t.plan(7)
|
|
|
|
const s = setup()
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
const error = new Error('Auth error')
|
|
error.returnCode = 3
|
|
cb(error, null)
|
|
}
|
|
|
|
s.broker.on('clientError', function (client, err) {
|
|
t.equal(err.message, 'Auth error')
|
|
t.equal(err.errorCode, 3)
|
|
})
|
|
|
|
s.broker.on('clientReady', function (client) {
|
|
t.fail('client should not ready')
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 3,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack, Server unavailable')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
test('authentication error when return code 4 (bad user or password) is passed', function (t) {
|
|
t.plan(7)
|
|
|
|
const s = setup()
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
const error = new Error('Auth error')
|
|
error.returnCode = 4
|
|
cb(error, null)
|
|
}
|
|
|
|
s.broker.on('clientError', function (client, err) {
|
|
t.equal(err.message, 'Auth error')
|
|
t.equal(err.errorCode, 4)
|
|
})
|
|
|
|
s.broker.on('clientReady', function (client) {
|
|
t.fail('client should not ready')
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 4,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack, bad username or password')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
test('authentication error when non numeric return code is passed', function (t) {
|
|
t.plan(7)
|
|
|
|
const s = setup()
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
const error = new Error('Non numeric error codes')
|
|
error.returnCode = 'return Code'
|
|
cb(error, null)
|
|
}
|
|
|
|
s.broker.on('clientError', function (client, err) {
|
|
t.equal(err.message, 'Non numeric error codes')
|
|
t.equal(err.errorCode, 5)
|
|
})
|
|
|
|
s.broker.on('clientReady', function (client) {
|
|
t.fail('client should not ready')
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 5,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack, unauthorized')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
test('authorize publish', function (t) {
|
|
t.plan(4)
|
|
|
|
const s = connect(setup(), { clientId: 'my-client-xyz' })
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
retain: false,
|
|
length: 12,
|
|
dup: false
|
|
}
|
|
|
|
s.broker.authorizePublish = function (client, packet, cb) {
|
|
t.ok(client, 'client exists')
|
|
t.same(packet, expected, 'packet matches')
|
|
cb()
|
|
}
|
|
|
|
s.broker.mq.on('hello', function (packet, cb) {
|
|
t.notOk(Object.prototype.hasOwnProperty.call(packet, 'messageId'), 'should not contain messageId in QoS 0')
|
|
expected.brokerId = s.broker.id
|
|
expected.brokerCounter = s.broker.counter
|
|
expected.clientId = 'my-client-xyz'
|
|
delete expected.length
|
|
t.same(packet, expected, 'packet matches')
|
|
cb()
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world'
|
|
})
|
|
})
|
|
|
|
test('authorize waits for authenticate', function (t) {
|
|
t.plan(6)
|
|
|
|
const s = setup(aedes({ clientId: 'my-client-xyz-2' }))
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authenticate = function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
process.nextTick(function () {
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
client.authenticated = true
|
|
cb(null, true)
|
|
})
|
|
}
|
|
|
|
s.broker.authorizePublish = function (client, packet, cb) {
|
|
t.ok(client.authenticated, 'client authenticated')
|
|
cb()
|
|
}
|
|
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
retain: false,
|
|
length: 12,
|
|
dup: false,
|
|
clientId: 'my-client'
|
|
}
|
|
|
|
s.broker.mq.on('hello', function (packet, cb) {
|
|
t.notOk(Object.prototype.hasOwnProperty.call(packet, 'messageId'), 'should not contain messageId in QoS 0')
|
|
expected.brokerId = s.broker.id
|
|
expected.brokerCounter = s.broker.counter
|
|
delete expected.length
|
|
t.same(packet, expected, 'packet matches')
|
|
cb()
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world'
|
|
})
|
|
})
|
|
|
|
test('authorize publish from configOptions', function (t) {
|
|
t.plan(4)
|
|
|
|
const s = connect(setup(aedes({
|
|
clientId: 'my-client-xyz-3',
|
|
authorizePublish: function (client, packet, cb) {
|
|
t.ok(client, 'client exists')
|
|
t.same(packet, expected, 'packet matches')
|
|
cb()
|
|
}
|
|
})), { clientId: 'my-client-xyz-3' })
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
retain: false,
|
|
length: 12,
|
|
dup: false
|
|
}
|
|
|
|
s.broker.mq.on('hello', function (packet, cb) {
|
|
t.notOk(Object.prototype.hasOwnProperty.call(packet, 'messageId'), 'should not contain messageId in QoS 0')
|
|
expected.brokerId = s.broker.id
|
|
expected.brokerCounter = s.broker.counter
|
|
expected.clientId = 'my-client-xyz-3'
|
|
delete expected.length
|
|
t.same(packet, expected, 'packet matches')
|
|
cb()
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world'
|
|
})
|
|
})
|
|
|
|
test('do not authorize publish', function (t) {
|
|
t.plan(3)
|
|
|
|
const s = connect(setup())
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0,
|
|
retain: false,
|
|
length: 12,
|
|
dup: false
|
|
}
|
|
|
|
s.broker.authorizePublish = function (client, packet, cb) {
|
|
t.ok(client, 'client exists')
|
|
t.same(packet, expected, 'packet matches')
|
|
cb(new Error('auth negated'))
|
|
}
|
|
|
|
eos(s.conn, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world'
|
|
})
|
|
})
|
|
|
|
test('modify qos out of range in authorize publish ', function (t) {
|
|
t.plan(2)
|
|
|
|
const s = connect(setup(), { clientId: 'my-client-xyz-4' })
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'foo',
|
|
payload: Buffer.from('bar'),
|
|
qos: 0,
|
|
retain: false,
|
|
length: 12,
|
|
dup: false,
|
|
clientId: 'my-client-xyz-4'
|
|
}
|
|
|
|
s.broker.authorizePublish = function (client, packet, cb) {
|
|
if (packet.topic === 'hello') { packet.qos = 10 }
|
|
cb()
|
|
}
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.fail('should no data sent')
|
|
})
|
|
s.broker.mq.on('hello', function (packet, cb) {
|
|
t.fail('should not publish')
|
|
})
|
|
s.broker.mq.on('foo', function (packet, cb) {
|
|
t.notOk(Object.prototype.hasOwnProperty.call(packet, 'messageId'), 'should not contain messageId in QoS 0')
|
|
expected.brokerId = s.broker.id
|
|
expected.brokerCounter = s.broker.counter
|
|
delete expected.length
|
|
t.same(packet, expected, 'packet matches')
|
|
cb()
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: 'world'
|
|
})
|
|
s.inStream.write({
|
|
cmd: 'publish',
|
|
topic: 'foo',
|
|
payload: 'bar'
|
|
})
|
|
})
|
|
|
|
test('authorize subscribe', function (t) {
|
|
t.plan(5)
|
|
|
|
const s = connect(setup())
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authorizeSubscribe = function (client, sub, cb) {
|
|
t.ok(client, 'client exists')
|
|
t.same(sub, {
|
|
topic: 'hello',
|
|
qos: 0
|
|
}, 'topic matches')
|
|
cb(null, sub)
|
|
}
|
|
|
|
subscribe(t, s, 'hello', 0)
|
|
})
|
|
|
|
test('authorize subscribe multiple same topics with same qos', function (t) {
|
|
t.plan(4)
|
|
|
|
const s = connect(setup())
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authorizeSubscribe = function (client, sub, cb) {
|
|
t.same(sub, {
|
|
topic: 'hello',
|
|
qos: 0
|
|
}, 'topic matches')
|
|
cb(null, sub)
|
|
}
|
|
|
|
subscribeMultiple(t, s, [{ topic: 'hello', qos: 0 }, { topic: 'hello', qos: 0 }], [0])
|
|
})
|
|
|
|
test('authorize subscribe multiple same topics with different qos', function (t) {
|
|
t.plan(4)
|
|
|
|
const s = connect(setup())
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authorizeSubscribe = function (client, sub, cb) {
|
|
t.same(sub, {
|
|
topic: 'hello',
|
|
qos: 1
|
|
}, 'topic matches')
|
|
cb(null, sub)
|
|
}
|
|
|
|
subscribeMultiple(t, s, [{ topic: 'hello', qos: 0 }, { topic: 'hello', qos: 1 }], [1])
|
|
})
|
|
|
|
test('authorize subscribe multiple different topics', function (t) {
|
|
t.plan(7)
|
|
|
|
const s = connect(setup())
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authorizeSubscribe = function (client, sub, cb) {
|
|
t.ok(client, 'client exists')
|
|
if (sub.topic === 'hello') {
|
|
t.same(sub, {
|
|
topic: 'hello',
|
|
qos: 0
|
|
}, 'topic matches')
|
|
} else if (sub.topic === 'foo') {
|
|
t.same(sub, {
|
|
topic: 'foo',
|
|
qos: 0
|
|
}, 'topic matches')
|
|
}
|
|
cb(null, sub)
|
|
}
|
|
|
|
subscribeMultiple(t, s, [{ topic: 'hello', qos: 0 }, { topic: 'foo', qos: 0 }], [0, 0])
|
|
})
|
|
|
|
test('authorize subscribe from config options', function (t) {
|
|
t.plan(5)
|
|
|
|
const s = connect(setup(aedes({
|
|
authorizeSubscribe: function (client, sub, cb) {
|
|
t.ok(client, 'client exists')
|
|
t.same(sub, {
|
|
topic: 'hello',
|
|
qos: 0
|
|
}, 'topic matches')
|
|
cb(null, sub)
|
|
}
|
|
})))
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
subscribe(t, s, 'hello', 0)
|
|
})
|
|
|
|
test('negate subscription', function (t) {
|
|
t.plan(5)
|
|
|
|
const s = connect(setup())
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authorizeSubscribe = function (client, sub, cb) {
|
|
t.ok(client, 'client exists')
|
|
t.same(sub, {
|
|
topic: 'hello',
|
|
qos: 0
|
|
}, 'topic matches')
|
|
cb(null, null)
|
|
}
|
|
|
|
s.inStream.write({
|
|
cmd: 'subscribe',
|
|
messageId: 24,
|
|
subscriptions: [{
|
|
topic: 'hello',
|
|
qos: 0
|
|
}]
|
|
})
|
|
|
|
s.outStream.once('data', function (packet) {
|
|
t.equal(packet.cmd, 'suback')
|
|
t.same(packet.granted, [128])
|
|
t.equal(packet.messageId, 24)
|
|
})
|
|
})
|
|
|
|
test('negate multiple subscriptions', function (t) {
|
|
t.plan(6)
|
|
|
|
const s = connect(setup())
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.broker.authorizeSubscribe = function (client, sub, cb) {
|
|
t.ok(client, 'client exists')
|
|
cb(null, null)
|
|
}
|
|
|
|
const expectedSubs = [{
|
|
topic: 'hello',
|
|
qos: 128
|
|
}, {
|
|
topic: 'world',
|
|
qos: 128
|
|
}]
|
|
|
|
s.broker.once('subscribe', function (subs, client) {
|
|
t.same(subs, expectedSubs)
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'subscribe',
|
|
messageId: 24,
|
|
subscriptions: [{
|
|
topic: 'hello',
|
|
qos: 0
|
|
}, {
|
|
topic: 'world',
|
|
qos: 0
|
|
}]
|
|
})
|
|
|
|
s.outStream.once('data', function (packet) {
|
|
t.equal(packet.cmd, 'suback')
|
|
t.same(packet.granted, [128, 128])
|
|
t.equal(packet.messageId, 24)
|
|
})
|
|
})
|
|
|
|
test('negate subscription with correct persistence', function (t) {
|
|
t.plan(6)
|
|
|
|
// rh, rap, nl are undefined because mqtt.parser is set to MQTT 3.1.1 and will thus erase these props from s.inStream.write
|
|
const expected = [{
|
|
topic: 'hello',
|
|
qos: 0,
|
|
rh: undefined,
|
|
rap: undefined,
|
|
nl: undefined
|
|
}, {
|
|
topic: 'world',
|
|
qos: 0,
|
|
rh: undefined,
|
|
rap: undefined,
|
|
nl: undefined
|
|
}]
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
broker.authorizeSubscribe = function (client, sub, cb) {
|
|
t.ok(client, 'client exists')
|
|
if (sub.topic === 'hello') {
|
|
sub = null
|
|
}
|
|
cb(null, sub)
|
|
}
|
|
|
|
const s = connect(setup(broker), { clean: false, clientId: 'abcde' })
|
|
s.outStream.once('data', function (packet) {
|
|
t.equal(packet.cmd, 'suback')
|
|
t.same(packet.granted, [128, 0])
|
|
broker.persistence.subscriptionsByClient(broker.clients.abcde, function (_, subs, client) {
|
|
t.same(subs, expected)
|
|
})
|
|
t.equal(packet.messageId, 24)
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'subscribe',
|
|
messageId: 24,
|
|
subscriptions: [{
|
|
topic: 'hello',
|
|
qos: 0,
|
|
rh: 0,
|
|
rap: true,
|
|
nl: false
|
|
}, {
|
|
topic: 'world',
|
|
qos: 0,
|
|
rh: 0,
|
|
rap: true,
|
|
nl: false
|
|
}]
|
|
})
|
|
})
|
|
|
|
test('negate multiple subscriptions random times', function (t) {
|
|
t.plan(5)
|
|
|
|
const clock = Faketimers.createClock()
|
|
const s = connect(setup())
|
|
t.teardown(function () {
|
|
clock.reset()
|
|
s.broker.close()
|
|
})
|
|
|
|
s.broker.authorizeSubscribe = function (client, sub, cb) {
|
|
t.ok(client, 'client exists')
|
|
if (sub.topic === 'hello') {
|
|
clock.setTimeout(function () {
|
|
cb(null, sub)
|
|
}, 100)
|
|
} else {
|
|
cb(null, null)
|
|
clock.tick(100)
|
|
}
|
|
}
|
|
|
|
s.inStream.write({
|
|
cmd: 'subscribe',
|
|
messageId: 24,
|
|
subscriptions: [{
|
|
topic: 'hello',
|
|
qos: 0
|
|
}, {
|
|
topic: 'world',
|
|
qos: 0
|
|
}]
|
|
})
|
|
|
|
s.outStream.once('data', function (packet) {
|
|
t.equal(packet.cmd, 'suback')
|
|
t.same(packet.granted, [0, 128])
|
|
t.equal(packet.messageId, 24)
|
|
})
|
|
})
|
|
|
|
test('failed authentication does not disconnect other client with same clientId', function (t) {
|
|
t.plan(3)
|
|
|
|
const broker = aedes()
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const s = setup(broker)
|
|
const s0 = setup(broker)
|
|
|
|
broker.authenticate = function (client, username, password, cb) {
|
|
cb(null, password.toString() === 'right')
|
|
}
|
|
|
|
s0.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'right',
|
|
keepalive: 0
|
|
})
|
|
|
|
s0.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 0,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'successful connack')
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'wrong',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
const removeEos = eos(s0.outStream, function () {
|
|
t.fail('ended before time')
|
|
})
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 5,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.pass('ended')
|
|
removeEos()
|
|
})
|
|
})
|
|
|
|
test('unauthorized connection should not unregister the correct one with same clientId', function (t) {
|
|
t.plan(4)
|
|
|
|
const broker = aedes({
|
|
authenticate: function (client, username, password, callback) {
|
|
if (username === 'correct') {
|
|
callback(null, true)
|
|
} else {
|
|
const error = new Error()
|
|
error.returnCode = 4
|
|
callback(error, false)
|
|
}
|
|
}
|
|
})
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
broker.on('clientError', function (client, err) {
|
|
t.equal(err.message, 'bad user name or password')
|
|
t.equal(err.errorCode, 4)
|
|
t.equal(broker.connectedClients, 1, 'my-client still connected')
|
|
})
|
|
|
|
connect(setup(broker), {
|
|
clientId: 'my-client',
|
|
username: 'correct'
|
|
}, function () {
|
|
t.equal(broker.connectedClients, 1, 'my-client connected')
|
|
connect(setup(broker), {
|
|
clientId: 'my-client',
|
|
username: 'unauthorized'
|
|
}, function () {
|
|
// other unauthorized connection with the same clientId should not unregister the correct one.
|
|
t.fail('unauthorized should not connect')
|
|
})
|
|
})
|
|
})
|
|
|
|
test('set authentication method in config options', function (t) {
|
|
t.plan(5)
|
|
|
|
const s = setup(aedes({
|
|
authenticate: function (client, username, password, cb) {
|
|
t.type(client, Client, 'client is there')
|
|
t.equal(username, 'my username', 'username is there')
|
|
t.same(password, Buffer.from('my pass'), 'password is there')
|
|
cb(null, false)
|
|
}
|
|
}))
|
|
t.teardown(s.broker.close.bind(s.broker))
|
|
|
|
s.outStream.on('data', function (packet) {
|
|
t.same(packet, {
|
|
cmd: 'connack',
|
|
returnCode: 5,
|
|
length: 2,
|
|
qos: 0,
|
|
retain: false,
|
|
dup: false,
|
|
topic: null,
|
|
payload: null,
|
|
sessionPresent: false
|
|
}, 'unsuccessful connack')
|
|
})
|
|
|
|
eos(s.outStream, function () {
|
|
t.equal(s.broker.connectedClients, 0, 'no connected clients')
|
|
})
|
|
|
|
s.inStream.write({
|
|
cmd: 'connect',
|
|
protocolId: 'MQTT',
|
|
protocolVersion: 4,
|
|
clean: true,
|
|
clientId: 'my-client',
|
|
username: 'my username',
|
|
password: 'my pass',
|
|
keepalive: 0
|
|
})
|
|
})
|
|
|
|
test('change a topic name inside authorizeForward method in QoS 1 mode', function (t) {
|
|
t.plan(3)
|
|
|
|
const broker = aedes({
|
|
authorizeForward: function (client, packet) {
|
|
packet.payload = Buffer.from('another-world')
|
|
packet.messageId = 2
|
|
return packet
|
|
}
|
|
})
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
const expected = {
|
|
cmd: 'publish',
|
|
topic: 'hello',
|
|
payload: Buffer.from('another-world'),
|
|
dup: false,
|
|
length: 22,
|
|
qos: 1,
|
|
retain: false,
|
|
messageId: 2
|
|
}
|
|
|
|
broker.on('client', function (client) {
|
|
client.subscribe({
|
|
topic: 'hello',
|
|
qos: 1
|
|
}, function (err) {
|
|
t.error(err, 'no error')
|
|
|
|
broker.publish({
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 1
|
|
}, function (err) {
|
|
t.error(err, 'no error')
|
|
})
|
|
})
|
|
})
|
|
|
|
const s = connect(setup(broker))
|
|
|
|
s.outStream.once('data', function (packet) {
|
|
t.same(packet, expected, 'packet matches')
|
|
})
|
|
})
|
|
|
|
;[true, false].forEach(function (cleanSession) {
|
|
test(`unauthorized forward publish in QoS 1 mode [clean=${cleanSession}]`, function (t) {
|
|
t.plan(2)
|
|
|
|
const broker = aedes({
|
|
authorizeForward: function (client, packet) {
|
|
return null
|
|
}
|
|
})
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
broker.on('client', function (client) {
|
|
client.subscribe({
|
|
topic: 'hello',
|
|
qos: 1
|
|
}, function (err) {
|
|
t.error(err, 'no error')
|
|
|
|
broker.publish({
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 1
|
|
}, function (err) {
|
|
t.error(err, 'no error')
|
|
})
|
|
})
|
|
})
|
|
|
|
const s = connect(setup(broker), { clean: cleanSession })
|
|
|
|
s.outStream.once('data', function (packet) {
|
|
t.fail('Should have not recieved this packet')
|
|
})
|
|
})
|
|
})
|
|
|
|
test('prevent publish in QoS 0 mode', function (t) {
|
|
t.plan(2)
|
|
|
|
const broker = aedes({
|
|
authorizeForward: function (client, packet) {
|
|
return null
|
|
}
|
|
})
|
|
t.teardown(broker.close.bind(broker))
|
|
|
|
broker.on('client', function (client) {
|
|
client.subscribe({
|
|
topic: 'hello',
|
|
qos: 0
|
|
}, function (err) {
|
|
t.error(err, 'no error')
|
|
|
|
broker.publish({
|
|
topic: 'hello',
|
|
payload: Buffer.from('world'),
|
|
qos: 0
|
|
}, function (err) {
|
|
t.error(err, 'no error')
|
|
})
|
|
})
|
|
})
|
|
|
|
const s = connect(setup(broker))
|
|
|
|
s.outStream.once('data', function (packet) {
|
|
t.fail('Should have not recieved this packet')
|
|
})
|
|
})
|