fix: make QR-code login and endpoint switch reliable (PROD-8200) - #247
Open
deaflynx wants to merge 5 commits into
Open
fix: make QR-code login and endpoint switch reliable (PROD-8200)#247deaflynx wants to merge 5 commits into
deaflynx wants to merge 5 commits into
Conversation
- Read the live ThingsboardClient via getter in Login and
NotificationService: after a QR endpoint switch re-creates the client,
captured references kept hitting the old host with new tokens (401
'Token is outdated'), and the failed refresh wiped the fresh session.
- Rework NoauthProvider.switchEndpoint: stage the exchanged JWT pair in
storage before reInit so the new client can only start with exactly
these tokens (a stale session left in storage can no longer win the
race), roll back to the previous endpoint (not the compiled default)
on failure, and surface the server's error message instead of raw
Dio text.
- Handle QR links without a secret (the login-page app QR): switch the
host and land on the new host's login page instead of spinning
forever; SwitchEndpointArgs.secret is now optional.
- Never render ThingsboardError.toString() in the noauth view (it
embeds the stacktrace); navigate on isDone and add a fallback so the
user is never stranded on the spinner.
- Restore app-link handling in the v2 router (the TbContext listener is
no longer initialized): listen in the app root, consume the cold-start
link, and drop platform duplicate deliveries.
- Pop the QR scanner once per scan: repeated MLKit detections popped the
route twice ('There is nothing to pop').
- Guard fire-and-forget handleUserLoaded calls against unhandled async
errors.
…8200) The dart client is autogenerated and must stay unmodified, so: - TbClientService suppresses error toasts while client.init() runs (init and reInit): the client's internal best-effort version check hits /api/admin/updates, which answers 403 for non-SYS_ADMIN users and otherwise surfaced as an error toast on every (re)init. Real init failures still propagate and are handled by the callers. - switchEndpoint re-applies the exchanged JWT pair to the new client if it comes out of reInit unauthenticated: a failing background refresh of the previous session may clear the shared token storage after the pair was staged but before init read it.
getLoginMobileInfo already has a graceful fallback (QR-only button list), but its failure still surfaced through the interceptor's global error channel as a 'You don't have permission' toast right after a successful QR switch: some servers answer 403 for an unknown mobile package. Pass ignoreErrors/ignoreLoading via the request extras so the interceptor stays quiet; the fallback behavior is unchanged.
…-8200) Two more sources of the 'You don't have permission' toast right after a successful QR switch: - NotificationService mobile-session calls (get/save/removeMobileSession, unread count) run against servers that may not know the mobile package and answer 403. Mark them ignoreErrors/ignoreLoading and guard the session sync so notification setup failures never surface to the user or abort init. - The generated client delivers error callbacks via Future(() => cb()), so the internal version-check 403 raised during client.init() reaches onClientError one event-loop turn AFTER init() returns - just outside the suppression window. Release the suppression flag after a short grace period instead of synchronously.
The JWT pair returned for a QR secret is bound to the secret, so re-scanning the same code after a logout hands the app tokens issued before the logout watermark: the exchange succeeds but every authenticated call answers 401 'Token is outdated', which sent the switch into a silent ~10s login/refresh loop that ended on the login page with no explanation. Verify the pair with GET /api/auth/user on the target host before committing the switch: a rejected pair now shows the server's message within seconds and rolls back, same as an expired QR code.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the QR-code login / endpoint switch flow. Related: PROD-8200, thingsboard/flutter_thingsboard_pe_app#303.
Root causes
LoginandNotificationServicecaptured theThingsboardClientonce at construction. After a QR switch re-creates the client (ITbClientService.reInit), they kept calling the old host with the new host's tokens → 401Token is outdated, and the failed refresh wiped the fresh tokens from shared storage, undoing the switch. A full app restart "fixed" it because the client was re-captured.switchEndpointloaded the new tokens into the old client before switching and rolled back to the compiled default endpoint (not the previous one) on failure, losing the user's session.onAuthErrorre-threw from inside the error callback → unhandled zone errors, and the noauth view renderedThingsboardError.toString(), which embeds the stacktrace shown on screen.secret(the mobile-app QR on the web login page) rendered a spinner forever.TbContext.initis no longer called), so camera-scanned links were silently dropped.getLoginMobileInfo, notification mobile-session sync, the client's internal version check) surfaced their expected 401/403 answers as "You don't have permission" toasts.Changes
LoginandNotificationService.NoauthProvider.switchEndpoint: stage the exchanged pair in storage beforereInit(the new client can only start with exactly these tokens), verify the pair withGET /api/auth/useron the target host before committing (a revoked pair now shows the server's message within seconds and rolls back), roll back to the previous endpoint on failure keeping the previous session, surface server error messages instead of raw Dio text.SwitchEndpointArgs.secretis now optional.GoError: There is nothing to pop).getLoginMobileInfo, mobile-session calls, unread count) withignoreErrors/ignoreLoadingextras and suppress client-internal init errors so expected 401/403 answers no longer toast; guard fire-and-forgethandleUserLoadedagainst unhandled async errors.Testing
Verified on emulator (Android 16) against a local TB instance and demo.thingsboard.io, driving the same
navigateByAppLinkpath the in-app scanner uses, plus on-device scanner runs:flutter analyzeat the develop/1.9.0 baseline