bitbucket_cli/auth/
file_store.rs

1use anyhow::{Context, Result};
2use std::fs;
3use std::path::PathBuf;
4
5use super::Credential;
6
7/// File-based credential storage (fallback when keyring is unavailable)
8pub struct FileStore {
9    path: PathBuf,
10}
11
12impl FileStore {
13    pub fn new() -> Result<Self> {
14        let config_dir = dirs::config_dir()
15            .context("Could not determine config directory")?
16            .join("bitbucket");
17
18        // Create config directory if it doesn't exist
19        fs::create_dir_all(&config_dir)
20            .context("Failed to create config directory")?;
21
22        let path = config_dir.join("credentials.json");
23
24        Ok(Self { path })
25    }
26
27    /// Store credentials in a file
28    pub fn store_credential(&self, credential: &Credential) -> Result<()> {
29        let json = serde_json::to_string_pretty(credential)
30            .context("Failed to serialize credential")?;
31
32        // Write with restrictive permissions (0600 = read/write for owner only)
33        #[cfg(unix)]
34        {
35            use std::fs::OpenOptions;
36            use std::os::unix::fs::OpenOptionsExt;
37
38            OpenOptions::new()
39                .write(true)
40                .create(true)
41                .truncate(true)
42                .mode(0o600)
43                .open(&self.path)
44                .and_then(|mut file| {
45                    use std::io::Write;
46                    file.write_all(json.as_bytes())
47                })
48                .context("Failed to write credential file")?;
49        }
50
51        #[cfg(not(unix))]
52        {
53            fs::write(&self.path, json).context("Failed to write credential file")?;
54        }
55
56        Ok(())
57    }
58
59    /// Get credentials from the file
60    pub fn get_credential(&self) -> Result<Option<Credential>> {
61        if !self.path.exists() {
62            return Ok(None);
63        }
64
65        let json = fs::read_to_string(&self.path)
66            .context("Failed to read credential file")?;
67
68        let credential: Credential =
69            serde_json::from_str(&json).context("Failed to parse stored credential")?;
70
71        Ok(Some(credential))
72    }
73
74    /// Delete credentials from the file
75    pub fn delete_credential(&self) -> Result<()> {
76        if self.path.exists() {
77            fs::remove_file(&self.path).context("Failed to delete credential file")?;
78        }
79        Ok(())
80    }
81}