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
···
"libc",
]
+
[[package]]
+
name = "core-foundation"
+
version = "0.10.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
+
dependencies = [
+
"core-foundation-sys",
+
"libc",
+
]
+
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
···
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c"
dependencies = [
+
"byteorder",
"dbus-secret-service",
"log",
"openssl",
+
"security-framework 2.11.1",
+
"security-framework 3.5.1",
+
"windows-sys 0.60.2",
"zeroize",
···
"openssl-probe",
"openssl-sys",
"schannel",
-
"security-framework",
+
"security-framework 2.11.1",
"security-framework-sys",
"tempfile",
···
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags",
-
"core-foundation",
+
"core-foundation 0.9.4",
+
"core-foundation-sys",
+
"libc",
+
"security-framework-sys",
+
]
+
+
[[package]]
+
name = "security-framework"
+
version = "3.5.1"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef"
+
dependencies = [
+
"bitflags",
+
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
···
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
dependencies = [
"bitflags",
-
"core-foundation",
+
"core-foundation 0.9.4",
"system-configuration-sys",
+1 -1
Cargo.toml
···
# Storage
dirs = "5.0"
-
keyring = { version = "3.6", features = ["sync-secret-service", "vendored"] }
+
keyring = "3.6"
# Error Handling
anyhow = "1.0"
+9 -1
crates/tangled-config/Cargo.toml
···
[dependencies]
anyhow = { workspace = true }
dirs = { workspace = true }
-
keyring = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
toml = { workspace = true }
chrono = { workspace = true }
+
[target.'cfg(target_os = "macos")'.dependencies]
+
keyring = { workspace = true, features = ["apple-native"] }
+
+
[target.'cfg(target_os = "linux")'.dependencies]
+
keyring = { workspace = true, features = ["sync-secret-service", "vendored"] }
+
+
[target.'cfg(target_os = "windows")'.dependencies]
+
keyring = { workspace = true, features = ["windows-native"] }
+
+8 -3
crates/tangled-config/src/config.rs
···
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
-
use dirs::config_dir;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
···
}
pub fn default_config_path() -> Result<PathBuf> {
-
let base = config_dir().context("Could not determine platform config directory")?;
-
Ok(base.join("tangled").join("config.toml"))
+
// Use ~/.config/tangled on all platforms for consistency
+
let home = std::env::var("HOME")
+
.or_else(|_| std::env::var("USERPROFILE"))
+
.context("Could not determine home directory")?;
+
Ok(PathBuf::from(home)
+
.join(".config")
+
.join("tangled")
+
.join("config.toml"))
}
pub fn load_config(path: Option<&Path>) -> Result<Option<RootConfig>> {
+2 -2
crates/tangled-config/src/keychain.rs
···
pub fn set_password(&self, secret: &str) -> Result<()> {
self.entry()?
.set_password(secret)
-
.map_err(|e| anyhow!("keyring error: {e}"))
+
.map_err(|e| anyhow!("Failed to save credentials to keychain: {e}"))
}
pub fn get_password(&self) -> Result<String> {
self.entry()?
.get_password()
-
.map_err(|e| anyhow!("keyring error: {e}"))
+
.map_err(|e| anyhow!("Failed to load credentials from keychain: {e}"))
}
pub fn delete_password(&self) -> Result<()> {