bitbucket_cli/auth/
mod.rs1pub mod api_key;
2pub mod file_store;
3pub mod keyring_store;
4pub mod oauth;
5
6use anyhow::Result;
7use serde::{Deserialize, Serialize};
8
9pub use api_key::*;
10pub use file_store::*;
11pub use keyring_store::*;
12pub use oauth::*;
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
17pub enum Credential {
18 OAuth {
20 access_token: String,
21 refresh_token: Option<String>,
22 expires_at: Option<i64>,
23 },
24 ApiKey { username: String, api_key: String },
27}
28
29impl Credential {
30 #[inline]
32 pub fn auth_header(&self) -> String {
33 match self {
34 Credential::OAuth { access_token, .. } => {
35 let mut result = String::with_capacity(7 + access_token.len());
37 result.push_str("Bearer ");
38 result.push_str(access_token);
39 result
40 }
41 Credential::ApiKey { username, api_key } => {
42 use base64::Engine;
43 let input_len = username.len() + 1 + api_key.len();
46 let base64_len = input_len.div_ceil(3) * 4;
47 let mut result = String::with_capacity(6 + base64_len);
48 result.push_str("Basic ");
49
50 let mut credentials = Vec::with_capacity(input_len);
52 credentials.extend_from_slice(username.as_bytes());
53 credentials.push(b':');
54 credentials.extend_from_slice(api_key.as_bytes());
55
56 base64::engine::general_purpose::STANDARD.encode_string(&credentials, &mut result);
57 result
58 }
59 }
60 }
61
62 #[inline]
64 pub fn type_name(&self) -> &'static str {
65 match self {
66 Credential::OAuth { .. } => "OAuth 2.0",
67 Credential::ApiKey { .. } => "API Key",
68 }
69 }
70
71 #[inline]
73 pub fn needs_refresh(&self) -> bool {
74 match self {
75 Credential::OAuth {
76 expires_at: Some(expires),
77 ..
78 } => {
79 *expires < chrono::Utc::now().timestamp() + 300
81 }
82 _ => false,
83 }
84 }
85
86 #[inline]
88 pub fn username(&self) -> Option<&str> {
89 match self {
90 Credential::ApiKey { username, .. } => Some(username),
91 Credential::OAuth { .. } => None,
92 }
93 }
94
95 #[inline]
97 pub fn is_oauth(&self) -> bool {
98 matches!(self, Credential::OAuth { .. })
99 }
100
101 #[inline]
103 pub fn is_api_key(&self) -> bool {
104 matches!(self, Credential::ApiKey { .. })
105 }
106}
107
108pub struct AuthManager {
110 store: FileStore,
111}
112
113impl AuthManager {
114 pub fn new() -> Result<Self> {
115 Ok(Self {
116 store: FileStore::new()?,
117 })
118 }
119
120 pub fn get_credentials(&self) -> Result<Option<Credential>> {
122 self.store.get_credential()
123 }
124
125 pub fn store_credentials(&self, credential: &Credential) -> Result<()> {
127 self.store.store_credential(credential)
128 }
129
130 pub fn clear_credentials(&self) -> Result<()> {
132 self.store.delete_credential()
133 }
134
135 pub fn is_authenticated(&self) -> bool {
137 self.get_credentials().map(|c| c.is_some()).unwrap_or(false)
138 }
139}
140
141impl Default for AuthManager {
142 fn default() -> Self {
143 Self::new().expect("Failed to create auth manager")
144 }
145}