Simplified keychain management on non-Windows platforms

This commit is contained in:
2025-12-21 12:35:17 +08:00
parent 0e0cb8f33a
commit e1de05de7a

View File

@@ -187,31 +187,41 @@ pub fn save_auth_pass(auth_pass: &AuthPass) -> Result<(), OAuthError> {
let encoded = URL_SAFE_NO_PAD.encode(&compressed); let encoded = URL_SAFE_NO_PAD.encode(&compressed);
info!("Encoded length: {}", encoded.len()); info!("Encoded length: {}", encoded.len());
// Windows keyring has a 2560-byte UTF-16 limit, which means 1280 chars max #[cfg(target_os = "windows")]
// Split into chunks of 1200 chars to be safe {
const CHUNK_SIZE: usize = 1200; // Windows keyring has a 2560-byte UTF-16 limit, which means 1280 chars max
let chunks: Vec<&str> = encoded // Split into chunks of 1200 chars to be safe
.as_bytes() const CHUNK_SIZE: usize = 1200;
.chunks(CHUNK_SIZE) let chunks: Vec<&str> = encoded
.map(|chunk| std::str::from_utf8(chunk).unwrap()) .as_bytes()
.collect(); .chunks(CHUNK_SIZE)
.map(|chunk| std::str::from_utf8(chunk).unwrap())
.collect();
info!("Splitting auth pass into {} chunks", chunks.len()); info!("Splitting auth pass into {} chunks", chunks.len());
// Save chunk count // Save chunk count
let count_entry = Entry::new(SERVICE_NAME, "auth_pass_count")?; let count_entry = Entry::new(SERVICE_NAME, "auth_pass_count")?;
count_entry.set_password(&chunks.len().to_string())?; count_entry.set_password(&chunks.len().to_string())?;
// Save each chunk // Save each chunk
for (i, chunk) in chunks.iter().enumerate() { for (i, chunk) in chunks.iter().enumerate() {
let entry = Entry::new(SERVICE_NAME, &format!("auth_pass_{}", i))?; let entry = Entry::new(SERVICE_NAME, &format!("auth_pass_{}", i))?;
entry.set_password(chunk)?; entry.set_password(chunk)?;
}
info!(
"Auth pass saved to keyring successfully in {} chunks",
chunks.len()
);
} }
info!( #[cfg(not(target_os = "windows"))]
"Auth pass saved to keyring successfully in {} chunks", {
chunks.len() let entry = Entry::new(SERVICE_NAME, "auth_pass")?;
); entry.set_password(&encoded)?;
info!("Auth pass saved to keyring successfully");
}
Ok(()) Ok(())
} }
@@ -219,40 +229,60 @@ pub fn save_auth_pass(auth_pass: &AuthPass) -> Result<(), OAuthError> {
pub fn load_auth_pass() -> Result<Option<AuthPass>, OAuthError> { pub fn load_auth_pass() -> Result<Option<AuthPass>, OAuthError> {
info!("Reading credentials from keyring"); info!("Reading credentials from keyring");
// Get chunk count #[cfg(target_os = "windows")]
let count_entry = Entry::new(SERVICE_NAME, "auth_pass_count")?; let encoded = {
let chunk_count = match count_entry.get_password() { // Get chunk count
Ok(count_str) => match count_str.parse::<usize>() { let count_entry = Entry::new(SERVICE_NAME, "auth_pass_count")?;
Ok(count) => count, let chunk_count = match count_entry.get_password() {
Err(_) => { Ok(count_str) => match count_str.parse::<usize>() {
error!("Invalid chunk count in keyring"); Ok(count) => count,
Err(_) => {
error!("Invalid chunk count in keyring");
return Ok(None);
}
},
Err(keyring::Error::NoEntry) => {
info!("No auth pass found in keyring");
return Ok(None); return Ok(None);
} }
}, Err(e) => {
Err(keyring::Error::NoEntry) => { error!("Failed to load chunk count from keyring");
info!("No auth pass found in keyring"); return Err(OAuthError::KeyringError(e));
return Ok(None); }
} };
Err(e) => {
error!("Failed to load chunk count from keyring"); info!("Loading {} auth pass chunks from keyring", chunk_count);
return Err(OAuthError::KeyringError(e));
// Reassemble chunks
let mut encoded = String::new();
for i in 0..chunk_count {
let entry = Entry::new(SERVICE_NAME, &format!("auth_pass_{}", i))?;
match entry.get_password() {
Ok(chunk) => encoded.push_str(&chunk),
Err(e) => {
error!("Failed to load chunk {} from keyring", i);
return Err(OAuthError::KeyringError(e));
}
}
} }
encoded
}; };
info!("Loading {} auth pass chunks from keyring", chunk_count); #[cfg(not(target_os = "windows"))]
let encoded = {
// Reassemble chunks let entry = Entry::new(SERVICE_NAME, "auth_pass")?;
let mut encoded = String::new();
for i in 0..chunk_count {
let entry = Entry::new(SERVICE_NAME, &format!("auth_pass_{}", i))?;
match entry.get_password() { match entry.get_password() {
Ok(chunk) => encoded.push_str(&chunk), Ok(pass) => pass,
Err(keyring::Error::NoEntry) => {
info!("No auth pass found in keyring");
return Ok(None);
}
Err(e) => { Err(e) => {
error!("Failed to load chunk {} from keyring", i); error!("Failed to load auth pass from keyring");
return Err(OAuthError::KeyringError(e)); return Err(OAuthError::KeyringError(e));
} }
} }
} };
info!("Reassembled encoded length: {}", encoded.len()); info!("Reassembled encoded length: {}", encoded.len());
@@ -288,21 +318,30 @@ pub fn load_auth_pass() -> Result<Option<AuthPass>, OAuthError> {
/// Clear auth_pass from secure storage and app state. /// Clear auth_pass from secure storage and app state.
pub fn clear_auth_pass() -> Result<(), OAuthError> { pub fn clear_auth_pass() -> Result<(), OAuthError> {
// Try to get chunk count #[cfg(target_os = "windows")]
let count_entry = Entry::new(SERVICE_NAME, "auth_pass_count")?; {
let chunk_count = match count_entry.get_password() { // Try to get chunk count
Ok(count_str) => count_str.parse::<usize>().unwrap_or(0), let count_entry = Entry::new(SERVICE_NAME, "auth_pass_count")?;
Err(_) => 0, let chunk_count = match count_entry.get_password() {
}; Ok(count_str) => count_str.parse::<usize>().unwrap_or(0),
Err(_) => 0,
};
// Delete all chunks // Delete all chunks
for i in 0..chunk_count { for i in 0..chunk_count {
let entry = Entry::new(SERVICE_NAME, &format!("auth_pass_{}", i))?; let entry = Entry::new(SERVICE_NAME, &format!("auth_pass_{}", i))?;
let _ = entry.delete_credential(); let _ = entry.delete_credential();
}
// Delete chunk count
let _ = count_entry.delete_credential();
} }
// Delete chunk count #[cfg(not(target_os = "windows"))]
let _ = count_entry.delete_credential(); {
let entry = Entry::new(SERVICE_NAME, "auth_pass")?;
let _ = entry.delete_credential();
}
info!("Auth pass cleared from keyring successfully"); info!("Auth pass cleared from keyring successfully");
Ok(()) Ok(())