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).context("Failed to create config directory")?;
20
21        let path = config_dir.join("credentials.json");
22
23        Ok(Self { path })
24    }
25
26    /// Store credentials in a file
27    pub fn store_credential(&self, credential: &Credential) -> Result<()> {
28        let json =
29            serde_json::to_string_pretty(credential).context("Failed to serialize credential")?;
30
31        // Write with restrictive permissions (0600 = read/write for owner only)
32        #[cfg(unix)]
33        {
34            use std::fs::OpenOptions;
35            use std::os::unix::fs::OpenOptionsExt;
36
37            OpenOptions::new()
38                .write(true)
39                .create(true)
40                .truncate(true)
41                .mode(0o600)
42                .open(&self.path)
43                .and_then(|mut file| {
44                    use std::io::Write;
45                    file.write_all(json.as_bytes())
46                })
47                .context("Failed to write credential file")?;
48        }
49
50        #[cfg(not(unix))]
51        {
52            fs::write(&self.path, json).context("Failed to write credential file")?;
53        }
54
55        Ok(())
56    }
57
58    /// Get credentials from the file
59    pub fn get_credential(&self) -> Result<Option<Credential>> {
60        if !self.path.exists() {
61            return Ok(None);
62        }
63
64        let json = fs::read_to_string(&self.path).context("Failed to read credential file")?;
65
66        let credential: Credential =
67            serde_json::from_str(&json).context("Failed to parse stored credential")?;
68
69        Ok(Some(credential))
70    }
71
72    /// Delete credentials from the file
73    pub fn delete_credential(&self) -> Result<()> {
74        if self.path.exists() {
75            fs::remove_file(&self.path).context("Failed to delete credential file")?;
76        }
77        Ok(())
78    }
79}