Skip to content

fix: make QR-code login and endpoint switch reliable (PROD-8200) - #311

Open
deaflynx wants to merge 8 commits into
thingsboard:develop/1.9.0from
deaflynx:fix/prod-8200-qr-host-switch
Open

fix: make QR-code login and endpoint switch reliable (PROD-8200)#311
deaflynx wants to merge 8 commits into
thingsboard:develop/1.9.0from
deaflynx:fix/prod-8200-qr-host-switch

Conversation

@deaflynx

Copy link
Copy Markdown
Contributor

Merge of the CE fix branch into PE. Fixes PROD-8200; fixes #303. CE counterpart: thingsboard/flutter_thingsboard_app#247.

Summary

QR-code login and host switching were unreliable: stale-client 401 storms wiped the fresh session after a switch ('Token is outdated', fixed only by force-restarting the app), raw stacktraces and irrelevant 'You don't have permission' toasts surfaced on screen, QR links without a secret span forever, camera-scanned app links were silently dropped, and re-scanning a QR after logout put the app into a silent login/refresh loop.

See thingsboard/flutter_thingsboard_app#247 for the full root-cause analysis and change list.

PE-specific notes

  • Clean merge of ce-origin/fix/prod-8200-qr-host-switch (single conflict in generated noauth_provider.g.dart, regenerated with build_runner).
  • The restored app-link listener is mirrored into thingsboard_app_pe.dart (the PE root widget), since thingsboard_app.dart exports the PE variant.
  • PE-specific logic in login_provider.dart (custom translations, user permissions) is preserved on top of the fix.

Testing

The underlying fixes were verified end-to-end on the CE app (emulator + on-device scanner runs against a local TB instance and demo.thingsboard.io): QR switch with auto-login, same-host relogin, re-scan within TTL, re-scan after logout (clean server message + rollback), expired secret, no-secret host switch, logout/login regression — zero unhandled exceptions. PE merge: flutter analyze at the develop/1.9.0 baseline (no new findings).

deaflynx and others added 8 commits July 2, 2026 18:26
…-back-button

fix(dashboard): make system back mirror the AppBar back arrow
…gsboard#303)

- 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.
… into fix/prod-8200-qr-host-switch

# Conflicts:
#	lib/core/auth/noauth/provider/noauth_provider.g.dart
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant