Skip to content

Persist Snapmaker cloud login across restarts (secure token store + startup race fix) - #479

Open
PGMacDesign wants to merge 2 commits into
Snapmaker:mainfrom
PGMacDesign:fix/persist-snapmaker-login
Open

Persist Snapmaker cloud login across restarts (secure token store + startup race fix)#479
PGMacDesign wants to merge 2 commits into
Snapmaker:mainfrom
PGMacDesign:fix/persist-snapmaker-login

Conversation

@PGMacDesign

@PGMacDesign PGMacDesign commented Jun 14, 2026

Copy link
Copy Markdown

Description

Snapmaker cloud login was not persisted across restarts on Linux (AppImage/Flatpak) and elsewhere — every launch forced a fresh OAuth login. This PR persists the session securely and restores it on startup, with a fix for a startup race that could otherwise leave the UI showing "logged out" despite a valid restored token.

Fixes #116
Fixes #226
Builds on the approach proposed in #266, hardened to keep the token out of plaintext on disk.

1. Persist login across restarts via the OS secret store (GUI_App.cpp/.hpp, WebSMUserLoginDialog.cpp)

  • Store only the bearer token, and only in the OS secret store via wxSecretStore (libsecret / Windows Credential Manager / macOS Keychain) — never in plaintext config.
  • If no secret service is available, the token is not persisted (falls back to the prior re-login behaviour) rather than written insecurely.
  • Guarded with wxUSE_SECRETSTORE so toolchains without it (e.g. MinGW) still compile.
  • On startup, revalidate the stored token against the accounts/current endpoint and re-fetch the profile from the server, restoring the session. The stored token is cleared on validation failure or explicit logout. No user data is written to disk.

2. Push restored login to the web UI on subscribe (SSWCP.cpp)

  • The persisted session is revalidated asynchronously at startup. That can complete before the Flutter home page subscribes to login-state updates, so the one-shot user_login_notify() push reaches an empty subscriber list and is lost — leaving the UI "logged out" despite a valid token.
  • sw_SubscribeUserLoginState now pushes the current login state immediately when the user is already logged in at subscribe time. This closes the race in both orderings (restore-before-subscribe via the immediate push here; restore-after-subscribe via the existing notify path).

Security notes

  • Only the bearer token is stored, and only in the platform secret store — nothing sensitive is written to OrcaSlicer.conf or any plaintext file.
  • No persistence when no secret service is present (graceful fallback to re-login).

Breaking changes / dependencies: none. Uses wxSecretStore (already part of wxWidgets), guarded by wxUSE_SECRETSTORE.

Screenshots/Recordings/Graphs

🎉 Can confirm Login works + Persists across app close / open 🎉

Linux (Pop! OS / Ubuntu)

bugix_linux_ubuntu_popos

Mac

bugfix_mac_screenshot

Tests

  • Log in, restart the app → session restored without re-authenticating (Linux AppImage).
  • Verify the web UI reflects the logged-in state immediately on startup (race fix).
  • Explicit logout clears the stored token.
  • Builds with and without wxUSE_SECRETSTORE.
  • Verified on Linux (Pop!_OS) and macOS (see screenshots).

Snapmaker cloud login was not persisted on Linux/AppImage (and elsewhere):
every restart forced a fresh OAuth login. Persist the session so it survives
restarts, revalidating the stored token on startup.

- Store only the bearer token, and only in the OS secret store via wxSecretStore
  (libsecret / Credential Manager / Keychain) -- never in plaintext config. If no
  secret service is available the token is not persisted (falls back to the prior
  re-login behaviour) rather than written insecurely. Guarded with
  wxUSE_SECRETSTORE so toolchains without it (e.g. MinGW) still compile.
- On startup, revalidate the token against the accounts/current endpoint and
  re-fetch the profile from the server, restoring the session; clear the stored
  token on failure or explicit logout. No user data is written to disk.

Addresses Snapmaker#116, Snapmaker#226. Builds on the approach proposed in Snapmaker#266, hardened to keep
the token out of plaintext on disk.
The persisted session is revalidated asynchronously at startup. That can
complete before the Flutter home page subscribes to login-state updates, in
which case the one-shot user_login_notify() push reaches an empty subscriber
list and is lost -- leaving the UI showing 'logged out' despite a valid,
restored token.

Make sw_SubscribeUserLoginState push the current login state immediately when
the user is already logged in at subscribe time. This closes the race in both
orderings: restore-before-subscribe is covered by the immediate push here, and
restore-after-subscribe by the existing notify() path.
@PGMacDesign

Copy link
Copy Markdown
Author

@coderabbitai review

@i-am-ez76

Copy link
Copy Markdown

Hi.
Thank you for working on this but even the latest 2.4.3 version still doens't remember the login.
Screenshot_20260615_113602

Once opened, the login just fine.
closed it and re-opened it and was prompt to login again.

The flatpak version crashed when trying to access the preference menu so i am working on the AppImage only.
Attached is the log.

2026-06-15-11-36-24.log.0.zip

@tina-junold

Copy link
Copy Markdown

And still not added to the latest release -.-

@jinlaohu

Copy link
Copy Markdown

Independent confirmation of the second half of your root cause, from a third install type, plus one adjacent case this PR may not cover.

Environment

  • Snapmaker Orca 2.3.5, Flathub Flatpak (io.github.Snapmaker.Snapmaker_Orca, user install)
  • Ubuntu 24.04
  • Same behaviour previously on the 2.3.4 AppImage on this machine

With #116 at 2.2.1 and #226 at 2.4.3, the symptom spans at least 2.2.1 → 2.4.3 across AppImage and Flatpak.

The token is on disk and valid at launch — something deletes it

The session token does get written to the embedded webview's localStorage and it survives a clean quit. Read back before launching:

data/snapmaker-orca/localstorage/http_127.0.0.1_13619.localstorage
  table ItemTable, key flutter.lava_user_info
  -> access_token is a JWT, iat 10:50:37, exp = iat + 24h  (unexpired)

So there is a valid token present at the next start, and login is still demanded. That matches your "even once restored, the UI shows logged-out despite a valid token" description.

The log sequence, before any UI appears

[warning] [WCP] [warning][home][[OrcaAuthBridge] 用户状态为 offline,清除用户信息]
[warning] [WCP] [warning][home][[LavaDeviceVM] [] disconnectAllDevices start,
          authorizationStatusNotifier.value set to AuthorizationStatus.authorizing]
[warning] [WCP] [warning][home][[LavaDeviceVM] web logout event received, ...]
[warning] [WCP] [warning][deviceControl][_getDeviceInfo: loginUser: {nickname: , email: ,
          account: , status: offline, icon: , avatar: , token: , token_type: Bearer,
          userid: , uid: , id: , cellphone: }]

用户状态为 offline,清除用户信息 = "user status is offline, clear user info". The native side pushes an empty loginUser with status: offline, and OrcaAuthBridge treats that as authoritative and purges the user info the web layer holds — including the valid token above. The sequence then repeats for both the home and deviceControl webviews.

Worth checking against your race fix: if the empty-state push can still reach OrcaAuthBridge before the restored session lands, the purge branch will wipe a token your secure-store revalidation just recovered. Treating an absent native session as unknown rather than offline → clear would make that ordering-independent, if it isn't already.

Possibly out of scope for this PR: the localhost port rotates, splitting localStorage

The local server port is not fixed. When 13619 is occupied (second instance, or a stale process), startup logs:

[error] use new port for start server:13620

Webview localStorage is keyed by origin, so that session gets an entirely different store. Both exist here, each with its own flutter.lava_user_info / flutter.orca_user_info at different issue times:

data/snapmaker-orca/localstorage/http_127.0.0.1_13619.localstorage
data/snapmaker-orca/localstorage/http_127.0.0.1_13620.localstorage

If the OS secret store is now the source of truth this may be moot, but any session state still living in a port-keyed origin will be invisible to a session that lands on a fallback port. Either pin the port or keep session state out of the origin.

Reproducing the inspection

Those stores are plain SQLite:

import sqlite3
c = sqlite3.connect('file:http_127.0.0.1_13619.localstorage?mode=ro', uri=True)
for k, v in c.execute("select key, length(value) from ItemTable"):
    print(k, v)

Keys of interest: flutter.lava_user_info (holds access_token, token, token_type), flutter.orca_user_info (profile only, no token), flutter.app_environment (prod).

Happy to test a build of this branch on Flatpak/2.3.5 if that helps get it reviewed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

4 participants