bitbucket_cli/auth/
keyring_store.rs

1use anyhow::{Context, Result};
2use keyring::Entry;
3
4use super::Credential;
5
6const SERVICE_NAME: &str = "bitbucket-cli";
7const CREDENTIAL_KEY: &str = "credentials";
8
9/// Secure credential storage using system keyring
10pub struct KeyringStore {
11    entry: Entry,
12}
13
14impl KeyringStore {
15    pub fn new() -> Result<Self> {
16        let entry =
17            Entry::new(SERVICE_NAME, CREDENTIAL_KEY).context("Failed to create keyring entry")?;
18        Ok(Self { entry })
19    }
20
21    /// Store credentials in the keyring
22    pub fn store_credential(&self, credential: &Credential) -> Result<()> {
23        let json = serde_json::to_string(credential).context("Failed to serialize credential")?;
24
25        self.entry
26            .set_password(&json)
27            .context("Failed to store credential in keyring")?;
28
29        Ok(())
30    }
31
32    /// Get credentials from the keyring
33    pub fn get_credential(&self) -> Result<Option<Credential>> {
34        match self.entry.get_password() {
35            Ok(json) => {
36                let credential: Credential =
37                    serde_json::from_str(&json).context("Failed to parse stored credential")?;
38                Ok(Some(credential))
39            }
40            Err(keyring::Error::NoEntry) => Ok(None),
41            Err(e) => Err(anyhow::anyhow!(
42                "Failed to get credential from keyring: {}",
43                e
44            )),
45        }
46    }
47
48    /// Delete credentials from the keyring
49    pub fn delete_credential(&self) -> Result<()> {
50        match self.entry.delete_credential() {
51            Ok(()) => Ok(()),
52            Err(keyring::Error::NoEntry) => Ok(()), // Already deleted
53            Err(e) => Err(anyhow::anyhow!(
54                "Failed to delete credential from keyring: {}",
55                e
56            )),
57        }
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    // Note: Keyring tests require a system keyring to be available
64    // and may not work in all CI environments
65}