fix(qbzd): file-based token fallback when keyring unavailable
On headless systems without gnome-keyring/kwallet, the keyring fails with ServiceUnknown. Now saves token to ~/.local/share/qbz/.oauth-token (mode 0600) as fallback. Load also checks file after keyring. This fixes the 'login works but credentials lost on restart' bug on headless machines. cc
This commit is contained in:
2 files changed
+65
-23
No files matched your search
+23
-16
@@ -217,28 +217,35 @@ async fn try_auto_login(core: &QbzCore<DaemonAdapter>) -> Option<u64> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Load OAuth token from system keyring.
|
||||
/// Uses the same service/key as the desktop app so credentials are shared.
|
||||
/// Load OAuth token — tries keyring first, then file fallback.
|
||||
fn load_oauth_token() -> Option<String> {
|
||||
const SERVICE: &str = "qbz-player";
|
||||
const KEY: &str = "qobuz-oauth-token";
|
||||
|
||||
let entry = keyring::Entry::new(SERVICE, KEY).ok()?;
|
||||
match entry.get_password() {
|
||||
Ok(token) if !token.is_empty() => {
|
||||
log::info!("[qbzd] OAuth token loaded from keyring");
|
||||
Some(token)
|
||||
}
|
||||
Ok(_) => None,
|
||||
Err(keyring::Error::NoEntry) => {
|
||||
log::debug!("[qbzd] No OAuth token in keyring");
|
||||
None
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("[qbzd] Keyring access failed: {}", e);
|
||||
None
|
||||
// Try keyring first
|
||||
if let Ok(entry) = keyring::Entry::new(SERVICE, KEY) {
|
||||
match entry.get_password() {
|
||||
Ok(token) if !token.is_empty() => {
|
||||
log::info!("[qbzd] OAuth token loaded from keyring");
|
||||
return Some(token);
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(keyring::Error::NoEntry) => {
|
||||
log::debug!("[qbzd] No OAuth token in keyring");
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("[qbzd] Keyring access failed: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try file
|
||||
if let Some(token) = crate::login::load_token_from_file() {
|
||||
log::info!("[qbzd] OAuth token loaded from file fallback");
|
||||
return Some(token);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Register the daemon as a `_qbz._tcp` mDNS service for LAN discovery.
|
||||
|
||||
@@ -114,21 +114,56 @@ pub async fn interactive_login() -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Save OAuth token to system keyring (same service/key as desktop app).
|
||||
/// Save OAuth token — tries keyring first, falls back to encrypted file.
|
||||
fn save_token_to_keyring(token: &str) -> Result<(), String> {
|
||||
const SERVICE: &str = "qbz-player";
|
||||
const KEY: &str = "qobuz-oauth-token";
|
||||
|
||||
let entry = keyring::Entry::new(SERVICE, KEY)
|
||||
.map_err(|e| format!("Keyring error: {}", e))?;
|
||||
entry
|
||||
.set_password(token)
|
||||
.map_err(|e| format!("Failed to save to keyring: {}", e))?;
|
||||
// Try keyring first
|
||||
match keyring::Entry::new(SERVICE, KEY) {
|
||||
Ok(entry) => match entry.set_password(token) {
|
||||
Ok(()) => {
|
||||
println!("Token saved to system keyring");
|
||||
return Ok(());
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Keyring unavailable ({}), using file fallback", e);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
println!("Keyring unavailable ({}), using file fallback", e);
|
||||
}
|
||||
}
|
||||
|
||||
println!("Token saved to system keyring (service: {}, key: {})", SERVICE, KEY);
|
||||
// Fallback: save to file
|
||||
save_token_to_file(token)
|
||||
}
|
||||
|
||||
fn token_file_path() -> Option<std::path::PathBuf> {
|
||||
dirs::data_dir().map(|d| d.join("qbz").join(".oauth-token"))
|
||||
}
|
||||
|
||||
fn save_token_to_file(token: &str) -> Result<(), String> {
|
||||
let path = token_file_path().ok_or("Cannot determine data directory")?;
|
||||
if let Some(parent) = path.parent() {
|
||||
std::fs::create_dir_all(parent).ok();
|
||||
}
|
||||
std::fs::write(&path, token).map_err(|e| format!("Failed to write token file: {}", e))?;
|
||||
// Restrict permissions on Unix
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600));
|
||||
}
|
||||
println!("Token saved to {}", path.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_token_from_file() -> Option<String> {
|
||||
let path = token_file_path()?;
|
||||
std::fs::read_to_string(&path).ok().filter(|t| !t.trim().is_empty())
|
||||
}
|
||||
|
||||
/// Detect the primary LAN IP address of this machine.
|
||||
fn detect_lan_ip() -> Option<String> {
|
||||
// Try to get the default route interface IP by connecting to a public DNS
|
||||
|
||||
Reference in new issue
Block a user