Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions src/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ export interface MqttClientEventCallbacks {
reconnect: VoidCallback
offline: VoidCallback
outgoingEmpty: VoidCallback
reauth: OnPacketCallback
}

/**
Expand Down Expand Up @@ -1678,6 +1679,66 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac
return this
}

/**
* reauthenticate - MQTT 5.0 Re-authentication
*/
public reauthenticate(
reAuthOptions: Pick<
NonNullable<IAuthPacket['properties']>,
'authenticationData' | 'reasonString' | 'userProperties'
>,
callback?: PacketCallback,
): MqttClient {
let error: Error | null = null

if (this.options.protocolVersion !== 5) {
error = new Error(
'reauthenticate: this feature works only with mqtt-v5',
)
} else if (!this.connected) {
error = new Error('reauthenticate: client is not connected')
} else if (!reAuthOptions.authenticationData) {
error = new Error('reauthenticate: authenticationData is required')
}

const method = this.options.properties?.authenticationMethod

if (!error && !method) {
error = new Error(
'reauthenticate: authenticationMethod is required from initial CONNECT',
)
}

if (error) {
if (callback) {
callback(error)
}
this.emit('error', error)
return this
}

if (
reAuthOptions.authenticationData ===
this.options.properties.authenticationData
) {
this.log(
'reauthenticate: sending same authenticationData as initial connection',
)
}

const authPacket: IAuthPacket = {
cmd: 'auth',
reasonCode: 0x19,
properties: {
authenticationMethod: method,
...reAuthOptions,
},
}

this._sendPacket(authPacket, callback)
return this
}

/**
* PRIVATE METHODS
* =====================
Expand Down
4 changes: 4 additions & 0 deletions src/lib/handlers/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ const handleAuth: PacketHandler = (
if (rc === 24) {
client.reconnecting = false
client['_sendPacket'](packet2)
} else if (rc === 0) {
if (client.connected) {
client.emit('reauth', packet)
}
} else {
const error = new ErrorWithReasonCode(
`Connection refused: ${ReasonCodes[rc]}`,
Expand Down
111 changes: 111 additions & 0 deletions test/node/client_mqtt5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1390,6 +1390,117 @@ describe('MQTT 5.0', () => {
},
)

it(
'should successfully reauthenticate with the broker',
{
timeout: 15000,
},
function (t, done) {
const port = ports.PORTAND103 + 92
const authMethod = 'GS-AUTH'

const testServer = serverBuilder('mqtt', (serverClient) => {
serverClient.on('connect', () => {
serverClient.connack({ reasonCode: 0 })
})

serverClient.on('auth', (packet) => {
if (packet.properties.authenticationMethod === authMethod) {
serverClient.auth({ reasonCode: 0 })
}
})
}).listen(port)

const client = mqtt.connect({
port: port,
protocolVersion: 5,
properties: {
authenticationMethod: authMethod,
},
})

client.on('connect', () => {
client.reauthenticate({
authenticationData: Buffer.from('initial-token'),
})
})

client.on('reauth', (packet: any) => {
assert.strictEqual(packet.reasonCode, 0)
client.end(true, () => testServer.close(done))
})
},
)

it(
'should emit an error if reauthenticate is called on a non-v5 connection',
{
timeout: 15000,
},
function (t, done) {
const port = ports.PORTAND103 + 90
const testServer = serverBuilder('mqtt', (serverClient) => {
serverClient.on('connect', () =>
serverClient.connack({ returnCode: 0 }),
)
}).listen(port)

const client = mqtt.connect({
port: port,
protocolVersion: 4,
})

client.on('error', (err) => {
try {
assert.ok(err.message.includes('works only with mqtt-v5'))
client.end(true, () => testServer.close(done))
} catch (assertErr) {
done(assertErr)
}
})

client.on('connect', () => {
client.reauthenticate({
authenticationData: Buffer.from('test'),
})
})
},
)

it(
'should emit an error if reauthenticate is called without an initial authenticationMethod',
{
timeout: 15000,
},
function (t, done) {
const port = ports.PORTAND103 + 91
const testServer = serverBuilder('mqtt', (serverClient) => {
serverClient.on('connect', () =>
serverClient.connack({ reasonCode: 0 }),
)
}).listen(port)

const client = mqtt.connect({
port: port,
protocolVersion: 5,
// Missing authenticationMethod
})

client.on('error', (err) => {
assert.ok(
err.message.includes('authenticationMethod is required'),
)
client.end(true, () => testServer.close(done))
})

client.on('connect', () => {
client.reauthenticate({
authenticationData: Buffer.from('test'),
})
})
},
)

it(
'pubrec handling custom invalid reason code',
{
Expand Down