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 #[serde(default, skip_serializing_if = "Option::is_none")]
25 client_id: Option<String>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
27 client_secret: Option<String>,
28 },
29 ApiKey { username: String, api_key: String },
32}
33
34impl Credential {
35 #[inline]
37 pub fn auth_header(&self) -> String {
38 match self {
39 Credential::OAuth { access_token, .. } => {
40 let mut result = String::with_capacity(7 + access_token.len());
42 result.push_str("Bearer ");
43 result.push_str(access_token);
44 result
45 }
46 Credential::ApiKey { username, api_key } => {
47 use base64::Engine;
48 let input_len = username.len() + 1 + api_key.len();
51 let base64_len = input_len.div_ceil(3) * 4;
52 let mut result = String::with_capacity(6 + base64_len);
53 result.push_str("Basic ");
54
55 let mut credentials = Vec::with_capacity(input_len);
57 credentials.extend_from_slice(username.as_bytes());
58 credentials.push(b':');
59 credentials.extend_from_slice(api_key.as_bytes());
60
61 base64::engine::general_purpose::STANDARD.encode_string(&credentials, &mut result);
62 result
63 }
64 }
65 }
66
67 #[inline]
69 pub fn type_name(&self) -> &'static str {
70 match self {
71 Credential::OAuth { .. } => "OAuth 2.0",
72 Credential::ApiKey { .. } => "API Key",
73 }
74 }
75
76 #[inline]
78 pub fn needs_refresh(&self) -> bool {
79 match self {
80 Credential::OAuth {
81 expires_at: Some(expires),
82 ..
83 } => {
84 *expires < chrono::Utc::now().timestamp() + 300
86 }
87 _ => false,
88 }
89 }
90
91 #[inline]
93 pub fn username(&self) -> Option<&str> {
94 match self {
95 Credential::ApiKey { username, .. } => Some(username),
96 Credential::OAuth { .. } => None,
97 }
98 }
99
100 #[inline]
102 pub fn is_oauth(&self) -> bool {
103 matches!(self, Credential::OAuth { .. })
104 }
105
106 #[inline]
108 pub fn is_api_key(&self) -> bool {
109 matches!(self, Credential::ApiKey { .. })
110 }
111
112 pub fn oauth_consumer_credentials(&self) -> Option<(&str, &str)> {
114 match self {
115 Credential::OAuth {
116 client_id: Some(id),
117 client_secret: Some(secret),
118 ..
119 } => Some((id, secret)),
120 _ => None,
121 }
122 }
123}
124
125pub struct AuthManager {
127 store: FileStore,
128}
129
130impl AuthManager {
131 pub fn new() -> Result<Self> {
132 Ok(Self {
133 store: FileStore::new()?,
134 })
135 }
136
137 pub fn get_credentials(&self) -> Result<Option<Credential>> {
139 self.store.get_credential()
140 }
141
142 pub fn store_credentials(&self, credential: &Credential) -> Result<()> {
144 self.store.store_credential(credential)
145 }
146
147 pub fn clear_credentials(&self) -> Result<()> {
149 self.store.delete_credential()
150 }
151
152 pub fn is_authenticated(&self) -> bool {
154 self.get_credentials().map(|c| c.is_some()).unwrap_or(false)
155 }
156}
157
158impl Default for AuthManager {
159 fn default() -> Self {
160 Self::new().expect("Failed to create auth manager")
161 }
162}