Skip to main content

bitbucket_cli/auth/
mod.rs

1pub mod file_store;
2pub mod keyring_store;
3pub mod oauth;
4
5use anyhow::Result;
6use serde::{Deserialize, Serialize};
7
8pub use file_store::*;
9pub use keyring_store::*;
10pub use oauth::*;
11
12/// OAuth 2.0 credential
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub enum Credential {
15    OAuth {
16        access_token: String,
17        refresh_token: Option<String>,
18        expires_at: Option<i64>,
19        #[serde(default, skip_serializing_if = "Option::is_none")]
20        client_id: Option<String>,
21        #[serde(default, skip_serializing_if = "Option::is_none")]
22        client_secret: Option<String>,
23    },
24}
25
26impl Credential {
27    /// Get the authorization header value for API requests
28    #[inline]
29    pub fn auth_header(&self) -> String {
30        match self {
31            Credential::OAuth { access_token, .. } => {
32                let mut result = String::with_capacity(7 + access_token.len());
33                result.push_str("Bearer ");
34                result.push_str(access_token);
35                result
36            }
37        }
38    }
39
40    /// Get the credential type name for display
41    #[inline]
42    pub fn type_name(&self) -> &'static str {
43        "OAuth 2.0"
44    }
45
46    /// Check if the credential needs refresh
47    #[inline]
48    pub fn needs_refresh(&self) -> bool {
49        match self {
50            Credential::OAuth {
51                expires_at: Some(expires),
52                ..
53            } => {
54                // Refresh if expiring within 5 minutes (300 seconds)
55                *expires < chrono::Utc::now().timestamp() + 300
56            }
57            _ => false,
58        }
59    }
60
61    /// Get stored OAuth consumer credentials (client_id, client_secret)
62    pub fn oauth_consumer_credentials(&self) -> Option<(&str, &str)> {
63        match self {
64            Credential::OAuth {
65                client_id: Some(id),
66                client_secret: Some(secret),
67                ..
68            } => Some((id, secret)),
69            _ => None,
70        }
71    }
72}
73
74/// Authentication manager - uses file-based credential storage
75pub struct AuthManager {
76    store: FileStore,
77}
78
79impl AuthManager {
80    pub fn new() -> Result<Self> {
81        Ok(Self {
82            store: FileStore::new()?,
83        })
84    }
85
86    /// Get stored credentials
87    pub fn get_credentials(&self) -> Result<Option<Credential>> {
88        self.store.get_credential()
89    }
90
91    /// Store credentials
92    pub fn store_credentials(&self, credential: &Credential) -> Result<()> {
93        self.store.store_credential(credential)
94    }
95
96    /// Clear stored credentials
97    pub fn clear_credentials(&self) -> Result<()> {
98        self.store.delete_credential()
99    }
100
101    /// Check if authenticated
102    pub fn is_authenticated(&self) -> bool {
103        self.get_credentials().map(|c| c.is_some()).unwrap_or(false)
104    }
105}
106
107impl Default for AuthManager {
108    fn default() -> Self {
109        Self::new().expect("Failed to create auth manager")
110    }
111}