Context
Raised multiple times in review on PR #52 (Bugbot: 3104888918, 3104928927, 3104931735, 3106546873, 3106571423). Deferred for a follow-up to avoid stretching #52 further.
Problem
cloudIdToken (a Google OAuth ID token, effectively a Bearer credential for the Cloud API) is persisted in plaintext via saveSettings() → localStorage (apps/desktop/src/utils/settings.ts). Any JS running in the WebView (including injected proxy scripts in app windows loaded from HTTPS origins) can read it. On disk, Tauri's localStorage is unencrypted JSON.
Existing facility
The app already has OS-keychain storage wired up: secure_store_token / secure_get_token Tauri commands backed by the keyring crate. Used for the local node's access/refresh tokens. Cloud token just needs to be routed through the same path.
Scope
- Remove
cloudIdToken from Settings type and from getSettings / saveSettings in apps/desktop/src/utils/settings.ts.
- Add a
cloud_id_token key (or similar) to the keyring service used by secure_store_token.
- Rewrite
apps/desktop/src/utils/cloudAuth.ts:
setCloudCredentials / equivalent → call the secure store Tauri command (async).
getCloudIdToken → read from secure store.
disconnectCloud → clear the secure store entry.
- Every caller of
getCloudIdToken becomes async. Audit: apps/desktop/src/pages/Settings.tsx, Namespaces.tsx, Onboarding.tsx (and anywhere else).
- Keep the expiry check (
isTokenExpired) — don't remove that logic, just relocate the storage.
Migration
Read localStorage once on startup; if cloudIdToken is present, move it to the keyring and delete the localStorage entry. One-shot migration keeps existing users signed in across the upgrade.
Also worth checking
The decoded user profile (cloudUserEmail, cloudUserName, cloudUserPicture) currently lives next to the token in Settings. Those are less sensitive but derivable from the token — consider whether they need to move too, or stay in localStorage. My lean: move cloudIdToken only; keep the profile fields in localStorage so UI can hydrate without an async keychain call on every render.
Related
Blocks anything that'd touch token handling on desktop (e.g., planned multi-account or tauri-session refactors). Not urgent per se but it's the last security-debt item from PR #52 review.
Context
Raised multiple times in review on PR #52 (Bugbot: 3104888918, 3104928927, 3104931735, 3106546873, 3106571423). Deferred for a follow-up to avoid stretching #52 further.
Problem
cloudIdToken(a Google OAuth ID token, effectively a Bearer credential for the Cloud API) is persisted in plaintext viasaveSettings()→localStorage(apps/desktop/src/utils/settings.ts). Any JS running in the WebView (including injected proxy scripts in app windows loaded from HTTPS origins) can read it. On disk, Tauri's localStorage is unencrypted JSON.Existing facility
The app already has OS-keychain storage wired up:
secure_store_token/secure_get_tokenTauri commands backed by thekeyringcrate. Used for the local node's access/refresh tokens. Cloud token just needs to be routed through the same path.Scope
cloudIdTokenfromSettingstype and fromgetSettings/saveSettingsinapps/desktop/src/utils/settings.ts.cloud_id_tokenkey (or similar) to the keyring service used bysecure_store_token.apps/desktop/src/utils/cloudAuth.ts:setCloudCredentials/ equivalent → call the secure store Tauri command (async).getCloudIdToken→ read from secure store.disconnectCloud→ clear the secure store entry.getCloudIdTokenbecomes async. Audit:apps/desktop/src/pages/Settings.tsx,Namespaces.tsx,Onboarding.tsx(and anywhere else).isTokenExpired) — don't remove that logic, just relocate the storage.Migration
Read localStorage once on startup; if
cloudIdTokenis present, move it to the keyring and delete the localStorage entry. One-shot migration keeps existing users signed in across the upgrade.Also worth checking
The decoded user profile (
cloudUserEmail,cloudUserName,cloudUserPicture) currently lives next to the token in Settings. Those are less sensitive but derivable from the token — consider whether they need to move too, or stay inlocalStorage. My lean: movecloudIdTokenonly; keep the profile fields inlocalStorageso UI can hydrate without an async keychain call on every render.Related
Blocks anything that'd touch token handling on desktop (e.g., planned multi-account or tauri-session refactors). Not urgent per se but it's the last security-debt item from PR #52 review.