Move to keyring based session storage to fix macOS auth issues #3

open
opened by dunkirk.sh targeting main

This fixes session persistence issues on macOS where login credentials were not being saved to the keychain. Also standardizes the config directory location across all platforms.

Changes#

Keyring Platform Support

  • Fixed macOS keychain integration by using apple-native feature instead of Linux-only sync-secret-service
  • Added platform-specific keyring features:
    • macOS: apple-native (uses macOS Keychain)
    • Linux: sync-secret-service, vendored (uses GNOME Keyring/KWallet)
    • Windows: windows-native (uses Windows Credential Manager)

Config Path Standardization

  • Moved config directory to ~/.config/tangled on all platforms for consistency
  • Previously used platform-specific paths (e.g., ~/Library/Application Support/tangled on macOS)

Error Messages

  • Improved keychain error messages to be more descriptive
Changed files
+50 -10
crates
tangled-config
+30 -3
Cargo.lock
··· 387 387 "libc", 388 388 ] 389 389 390 + [[package]] 391 + name = "core-foundation" 392 + version = "0.10.1" 393 + source = "registry+https://github.com/rust-lang/crates.io-index" 394 + checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" 395 + dependencies = [ 396 + "core-foundation-sys", 397 + "libc", 398 + ] 399 + 390 400 [[package]] 391 401 name = "core-foundation-sys" 392 402 version = "0.8.7" ··· 1117 1127 source = "registry+https://github.com/rust-lang/crates.io-index" 1118 1128 checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" 1119 1129 dependencies = [ 1130 + "byteorder", 1120 1131 "dbus-secret-service", 1121 1132 "log", 1122 1133 "openssl", 1134 + "security-framework 2.11.1", 1135 + "security-framework 3.5.1", 1136 + "windows-sys 0.60.2", 1123 1137 "zeroize", 1124 1138 ] 1125 1139 ··· 1316 1330 "openssl-probe", 1317 1331 "openssl-sys", 1318 1332 "schannel", 1319 - "security-framework", 1333 + "security-framework 2.11.1", 1320 1334 "security-framework-sys", 1321 1335 "tempfile", 1322 1336 ] ··· 1831 1845 checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1832 1846 dependencies = [ 1833 1847 "bitflags", 1834 - "core-foundation", 1848 + "core-foundation 0.9.4", 1849 + "core-foundation-sys", 1850 + "libc", 1851 + "security-framework-sys", 1852 + ] 1853 + 1854 + [[package]] 1855 + name = "security-framework" 1856 + version = "3.5.1" 1857 + source = "registry+https://github.com/rust-lang/crates.io-index" 1858 + checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" 1859 + dependencies = [ 1860 + "bitflags", 1861 + "core-foundation 0.10.1", 1835 1862 "core-foundation-sys", 1836 1863 "libc", 1837 1864 "security-framework-sys", ··· 2055 2082 checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 2056 2083 dependencies = [ 2057 2084 "bitflags", 2058 - "core-foundation", 2085 + "core-foundation 0.9.4", 2059 2086 "system-configuration-sys", 2060 2087 ] 2061 2088
+1 -1
Cargo.toml
··· 41 41 42 42 # Storage 43 43 dirs = "5.0" 44 - keyring = { version = "3.6", features = ["sync-secret-service", "vendored"] } 44 + keyring = "3.6" 45 45 46 46 # Error Handling 47 47 anyhow = "1.0"
+9 -1
crates/tangled-config/Cargo.toml
··· 8 8 [dependencies] 9 9 anyhow = { workspace = true } 10 10 dirs = { workspace = true } 11 - keyring = { workspace = true } 12 11 serde = { workspace = true, features = ["derive"] } 13 12 serde_json = { workspace = true } 14 13 toml = { workspace = true } 15 14 chrono = { workspace = true } 16 15 16 + [target.'cfg(target_os = "macos")'.dependencies] 17 + keyring = { workspace = true, features = ["apple-native"] } 18 + 19 + [target.'cfg(target_os = "linux")'.dependencies] 20 + keyring = { workspace = true, features = ["sync-secret-service", "vendored"] } 21 + 22 + [target.'cfg(target_os = "windows")'.dependencies] 23 + keyring = { workspace = true, features = ["windows-native"] } 24 +
+8 -3
crates/tangled-config/src/config.rs
··· 2 2 use std::path::{Path, PathBuf}; 3 3 4 4 use anyhow::{Context, Result}; 5 - use dirs::config_dir; 6 5 use serde::{Deserialize, Serialize}; 7 6 8 7 #[derive(Debug, Clone, Serialize, Deserialize, Default)] ··· 55 54 } 56 55 57 56 pub fn default_config_path() -> Result<PathBuf> { 58 - let base = config_dir().context("Could not determine platform config directory")?; 59 - Ok(base.join("tangled").join("config.toml")) 57 + // Use ~/.config/tangled on all platforms for consistency 58 + let home = std::env::var("HOME") 59 + .or_else(|_| std::env::var("USERPROFILE")) 60 + .context("Could not determine home directory")?; 61 + Ok(PathBuf::from(home) 62 + .join(".config") 63 + .join("tangled") 64 + .join("config.toml")) 60 65 } 61 66 62 67 pub fn load_config(path: Option<&Path>) -> Result<Option<RootConfig>> {
+2 -2
crates/tangled-config/src/keychain.rs
··· 21 21 pub fn set_password(&self, secret: &str) -> Result<()> { 22 22 self.entry()? 23 23 .set_password(secret) 24 - .map_err(|e| anyhow!("keyring error: {e}")) 24 + .map_err(|e| anyhow!("Failed to save credentials to keychain: {e}")) 25 25 } 26 26 27 27 pub fn get_password(&self) -> Result<String> { 28 28 self.entry()? 29 29 .get_password() 30 - .map_err(|e| anyhow!("keyring error: {e}")) 30 + .map_err(|e| anyhow!("Failed to load credentials from keychain: {e}")) 31 31 } 32 32 33 33 pub fn delete_password(&self) -> Result<()> {