-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat(mqtt5): implement re-authentication and handle auth success #2048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
c305b81
689abba
682e53f
0ae96d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -433,6 +433,7 @@ export interface MqttClientEventCallbacks { | |
| reconnect: VoidCallback | ||
| offline: VoidCallback | ||
| outgoingEmpty: VoidCallback | ||
| reauth: OnPacketCallback | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -521,6 +522,8 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |
|
|
||
| private connackPacket: IConnackPacket | ||
|
|
||
| private _reauthCallback: PacketCallback | ||
|
robertsLando marked this conversation as resolved.
Outdated
|
||
|
|
||
| public static defaultId() { | ||
| return `mqttjs_${Math.random().toString(16).substr(2, 8)}` | ||
| } | ||
|
|
@@ -1678,6 +1681,105 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |
| return this | ||
| } | ||
|
|
||
| /** | ||
| * reauthenticate - MQTT 5.0 Re-authentication | ||
| * * @param {Object} reauthOptions - Re-authentication properties | ||
|
robertsLando marked this conversation as resolved.
Outdated
|
||
| * @param {Buffer} [reauthOptions.authenticationData] - Binary data for auth exchange | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Doc] JSDoc says * @param {Buffer} [reauthOptions.authenticationData] - Binary data for auth exchangeThe square brackets in JSDoc denote an optional parameter, but |
||
| * @param {string} [reauthOptions.reasonString] - Human-readable reason for re-auth | ||
| * @param {Object} [reauthOptions.userProperties] - Custom user properties | ||
| * @param {PacketCallback} [callback] - Fired when the AUTH exchange completes or fails | ||
| * @returns {MqttClient} - Returns the client instance | ||
| */ | ||
| public reauthenticate( | ||
| reauthOptions: Pick< | ||
| NonNullable<IAuthPacket['properties']>, | ||
| 'authenticationData' | 'reasonString' | 'userProperties' | ||
| >, | ||
| callback?: PacketCallback, | ||
| ): MqttClient { | ||
| let error: Error | null = null | ||
|
|
||
| if (this._reauthCallback) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [High] The interrupt fires before the new request is validated. if (this._reauthCallback) {
this._handleReauth(new Error('reauthenticate: interrupted by new reauthentication request'))
}
if (this.options.protocolVersion !== 5) return fail(...)
else if (!this.connected) return fail(...)
else if (!reauthOptions.authenticationData) return fail(...)A second call with invalid arguments (wrong protocol version, missing data, etc.) will still cancel a perfectly valid in-flight re-auth before its own validation runs. The first caller is rejected with Move the validation above the interrupt branch so that interruption only happens when the new request will actually go on the wire. |
||
| this._handleReauthCompleted( | ||
| new Error( | ||
| 'reauthenticate: interrupted by new reauthentication request', | ||
| ), | ||
| ) | ||
| } | ||
|
|
||
| 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) | ||
| } else { | ||
| this.emit('error', error) | ||
| } | ||
| return this | ||
| } | ||
|
robertsLando marked this conversation as resolved.
Outdated
|
||
|
|
||
| if ( | ||
| reauthOptions.authenticationData && | ||
| Buffer.isBuffer(this.options.properties.authenticationData) && | ||
| reauthOptions.authenticationData.equals( | ||
| this.options.properties.authenticationData, | ||
| ) | ||
| ) { | ||
| this.log( | ||
| 'reauthenticate: sending same authenticationData as initial connection', | ||
| ) | ||
| } | ||
|
robertsLando marked this conversation as resolved.
Outdated
|
||
|
|
||
| const authPacket: IAuthPacket = { | ||
| cmd: 'auth', | ||
| reasonCode: 0x19, // Re-authentication (MQTT 5.0 Spec) | ||
| properties: { | ||
| ...reauthOptions, | ||
| authenticationMethod: method, | ||
| }, | ||
| } | ||
|
|
||
| this._reauthCallback = callback | ||
| this._sendPacket(authPacket) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Medium] Two related issues here:
|
||
| return this | ||
| } | ||
|
|
||
| /** | ||
| * _handleReauthCompleted | ||
| * Internal method to finalize the re-authentication process. | ||
| * Clears the pending reauth callback and signals completion via callback or event. | ||
| * @param {Error | null} err - The error if the re-authentication failed | ||
| * @param {IAuthPacket} [packet] - The AUTH packet received from the broker | ||
| * @api private | ||
| */ | ||
| public _handleReauthCompleted(err: Error | null, packet?: IAuthPacket) { | ||
|
robertsLando marked this conversation as resolved.
Outdated
|
||
| if (this._reauthCallback) { | ||
| const cb = this._reauthCallback | ||
| this._reauthCallback = null | ||
| cb(err, packet) | ||
| } | ||
|
|
||
| if (!err && packet) { | ||
| this.emit('reauth', packet) | ||
| } | ||
| } | ||
|
robertsLando marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * PRIVATE METHODS | ||
| * ===================== | ||
|
|
@@ -1864,6 +1966,10 @@ export default class MqttClient extends TypedEventEmitter<MqttClientEventCallbac | |
| }) | ||
| } | ||
|
|
||
| if (this._reauthCallback) { | ||
| this._handleReauthCompleted(new Error('client disconnected')) | ||
| } | ||
|
|
||
| if (!this.disconnecting && !this.reconnecting) { | ||
| this.log( | ||
| '_cleanUp :: client not disconnecting/reconnecting. Clearing and resetting reconnect.', | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.