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