Persist Snapmaker cloud login across restarts (supersedes #479) - #644
Draft
respinoza wants to merge 4 commits into
Draft
Persist Snapmaker cloud login across restarts (supersedes #479)#644respinoza wants to merge 4 commits into
respinoza wants to merge 4 commits into
Conversation
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.
|
I hope this works. It's the reason why I rarely print with my U1, choosing my X1C instead because the login every single time drives me crazy. I have to verify with my phone as well. |
respinoza
force-pushed
the
pr/login-persistence
branch
from
August 2, 2026 08:39
412fe53 to
9f99ab1
Compare
…ggle Building on Snapmaker#479, this makes the persisted session survive the ways it previously did not: * accounts/current reports auth failures as HTTP 200 with a non-200 body code (110002 authorization_missing, 110003 token_expired, 110004 authentication_failed, per the bundled web UI's error table), so a 2xx status alone proves nothing. Slic3r::sm_parse_account_response reports what a reply contains - success envelope, token refusal, profile fields - and each caller applies its own policy: a restore needs a success envelope with a usable account id, while a fresh login proceeds unless the token was refused outright. Previously an invalid token produced a "signed in" state with an empty profile while the UI still showed Login/Register. The parser and the region-to-host mapping live in slic3r/Utils/SnapmakerAccount, free of any GUI dependency, so they can be covered by tests/slic3rutils. * The stored token is cleared only on those auth codes or HTTP 401/403. Transient failures keep it for the next launch, and an unrecognized response is logged rather than passing silently. * A login epoch guards every asynchronous completion, so a slow restore cannot clobber a manual login or logout that happened meanwhile, and a keyring write that lands late cannot resurrect a session the user ended. Failing to store degrades to "nothing persisted" rather than leaving a previous account's token behind. * Exactly one keyring item exists per platform: Windows and macOS delete before saving under a fixed attribute set. * [sm_login]/has_session records that a token really reached the store, so startup skips the keyring - and any unlock prompt - for users who never signed in. It is a hint only: a reader that finds it disagreeing with the store corrects it, and a marker that outlives its item merely costs the keyring probe that having no marker would cost anyway. * The revalidation request has a 30s timeout and a 64 KiB response cap, and its callbacks are disarmed on exit through a liveness token. * Persistence is opt-out through a "Stay signed in" preference (default on); turning it off also drops whatever is already stored. * The login-state payload sent to the web UI is deduplicated into GUI_App::sm_login_state_json, and the subscribe-time push added by Snapmaker#479 is gated on m_event_id so plain subscribe calls keep their shape.
Use libsecret's asynchronous password API instead of wxSecretStore on Linux. Inside a Flatpak this lets libsecret select its file backend behind the Secret portal (org.freedesktop.portal.Secret): the token lives in an encrypted per-application keyring keyed by a per-app master secret, so the sandbox needs no org.freedesktop.secrets permission at all and cannot read other applications' secrets. The manifest drops that permission accordingly. Unsandboxed builds talk to the ordinary Secret Service, and hosts with neither a portal nor a keyring degrade to signing in each launch. The asynchronous API avoids blocking the UI thread on a D-Bus round trip; Keychain and Credential Manager on the wxSecretStore path are local and stay synchronous, and both present the same callback-shaped interface so the restore path needs no platform branching. In-flight operations are cancelled on exit through a GCancellable.
respinoza
force-pushed
the
pr/login-persistence
branch
from
August 3, 2026 01:09
9f99ab1 to
c651048
Compare
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 #116, fixes #226. Supersedes #479, whose two commits are included unchanged with @PGMacDesign's authorship. Built through the flatpak CI and verified on a real U1 setup (CachyOS, KDE Plasma 6).
Scope
The desktop login flow returns exactly one credential:
id.snapmaker.com/?from=orcaends ataccount.snapmaker.com/?token=…, whose only parameter is a JWT withiat/exp24 hours apart, scopeopenid profile mqtt, nooffline_access, no refresh token. So restarts within 24h no longer cost a login (today every launch does); after that the user signs in again. Lifting that ceiling needs a refresh token or silent re-auth via the webview session — your call, not in this PR.Why #479 alone didn't work
accounts/currentreports auth failures as HTTP 200 with{"code":110002,"msg":"Missing authorization"}(verified by replaying an expired token; the web UI's table also lists 110003, 110004). Persist Snapmaker cloud login across restarts (secure token store + startup race fix) #479 accepted any 2xx, so a dead token yielded a "logged in" state with an empty profile and the UI still showed Login/Register. Restore now validates the body envelope viasm_parse_account_response, shared with the login dialog, toleratingcode/idas int or numeric string (only theid.snapmaker.comenvelope could be observed live).Http::cancel()alone cannot suppress a completion that has already begun on the detached worker thread.Linux: Secret portal instead of wxSecretStore
In the Flatpak build, libsecret selects its file backend behind
org.freedesktop.portal.Secret: the token lives in an encrypted per-app keyring and the manifest needs noorg.freedesktop.secretspermission, so the sandbox cannot read other apps' secrets. Unsandboxed builds (AppImage, distro packages) get no such isolation — same code, normal Secret Service, token in the user's login keyring. Windows/macOS keep wxSecretStore. No portal and no keyring means signing in each launch.Only the token is persisted; profile fields are re-fetched on restore. A non-secret
[sm_login]/has_sessionmarker means startup never touches the keyring (or triggers an unlock prompt) for users who never signed in.Also from #479: the subscribe-time push in
sw_SubscribeUserLoginState, so a session restored before the web UI subscribes isn't lost. Gated onm_event_id != ""to leave plain subscribe calls' response shape unchanged.Verification
Log in → quit → relaunch restores in ~2s, repeatedly, over a day of slicing until the token hit its natural expiry — at which point it was cleared and a fresh login requested, as intended. Token only in the per-app keyring (host KDE wallet has no Snapmaker entries); a simulated server error leaves it intact; device connect and sending prints work against current
mainincluding #468.Caveats, out of scope
Httpdisables TLS certificate and hostname verification globally (CURLOPT_SSL_VERIFYPEER/VERIFYHOST= 0 inHttp::priv::priv, present since the original BambuStudio import). Nothing in this PR touches it and the feature does not depend on it, but persisting a token does mean that credential is now replayed at each startup over unverified TLS rather than being sent once per interactive login. We don't know the original reason for the setting — if it's incidental rather than deliberate, it may deserve its own issue..keyring~backup, so a cleared token's ciphertext can linger inside the sandbox. Deleting another library's private on-disk format seemed worse than documenting it.