feat: secure_session commands, login tokens in the OS keychain (cinny #105, step 1)

Commands for the web client to keep a copy of the login tokens in the OS
keychain: secure_session_supported / _set / _get / _clear.

- Windows: Credential Manager via the keyring crate (3.6, windows-native),
  entry "session" in service "Lotus Chat". Only the secrets are stored
  (userId, deviceId, accessToken, refreshToken); the serialized value is
  capped at 1200 chars (Windows' limit is 2560 bytes).
- Other platforms: supported = false and the other commands answer "not
  supported on this platform" (Linux Secret Service can prompt to unlock a
  wallet at startup; that needs its own testing). No new Linux dependency:
  without a platform feature the crate only has its mock store.
- Keychain calls run on the blocking pool, off the main thread.

Step 1 is a mirror only (the web client still reads its session from
localStorage); see the cinny PR.

Tests: round trip + clear, clearing an empty keychain, incomplete and
oversized sessions rejected with nothing written, the JSON shape the web
client sends, a realistic OIDC session fits (keyring's mock store). Linux
release build: commands answer as designed and login is unaffected.
Windows: type-checked only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
Lotus CI
2026-09-29 00:25:01 -04:00
co-authored by Claude Opus 5.5
parent 884877a55b
commit 032b6e04e7
4 changed files with 204 additions and 0 deletions
+5
View File
@@ -38,6 +38,10 @@ tauri-plugin-single-instance = "2"
tauri-plugin-autostart = "2" # P6-1 launch-on-login
# Update retry backoff (already in the tree via tauri; adds only the timer).
tokio = { version = "1", features = ["time"] }
# cinny #105: login tokens in the OS keychain. Without a platform feature
# the crate only has its in-memory mock store (used by the tests); Windows
# turns on Credential Manager below.
keyring = "3.6"
[target.'cfg(target_os = "linux")'.dependencies]
# P6-1 desktop parity: screensaver inhibit (no-sleep in calls) + Unity launcher
@@ -50,6 +54,7 @@ zbus = "5"
webkit2gtk = "2.0"
[target.'cfg(target_os = "windows")'.dependencies]
keyring = { version = "3.6", features = ["windows-native"] }
webview2-com = "0.38"
window-vibrancy = "0.6"
windows = { version = "0.61", features = [