bitbucket_cli/auth/
mod.rs1pub mod app_password;
2pub mod keyring_store;
3pub mod oauth;
4
5use anyhow::Result;
6use serde::{Deserialize, Serialize};
7
8pub use app_password::*;
9pub use keyring_store::*;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub enum Credential {
14 AppPassword {
15 username: String,
16 app_password: String,
17 },
18 OAuth {
19 access_token: String,
20 refresh_token: Option<String>,
21 expires_at: Option<i64>,
22 },
23}
24
25impl Credential {
26 #[inline]
28 pub fn auth_header(&self) -> String {
29 match self {
30 Credential::AppPassword {
31 username,
32 app_password,
33 } => {
34 use base64::Engine;
35 let input_len = username.len() + 1 + app_password.len();
38 let base64_len = input_len.div_ceil(3) * 4;
39 let mut result = String::with_capacity(6 + base64_len);
40 result.push_str("Basic ");
41
42 let mut credentials = Vec::with_capacity(input_len);
44 credentials.extend_from_slice(username.as_bytes());
45 credentials.push(b':');
46 credentials.extend_from_slice(app_password.as_bytes());
47
48 base64::engine::general_purpose::STANDARD.encode_string(&credentials, &mut result);
49 result
50 }
51 Credential::OAuth { access_token, .. } => {
52 let mut result = String::with_capacity(7 + access_token.len());
54 result.push_str("Bearer ");
55 result.push_str(access_token);
56 result
57 }
58 }
59 }
60
61 #[inline]
63 pub fn needs_refresh(&self) -> bool {
64 match self {
65 Credential::OAuth {
66 expires_at: Some(expires),
67 ..
68 } => {
69 *expires < chrono::Utc::now().timestamp() + 300
71 }
72 _ => false,
73 }
74 }
75
76 #[inline]
78 pub fn username(&self) -> Option<&str> {
79 match self {
80 Credential::AppPassword { username, .. } => Some(username),
81 Credential::OAuth { .. } => None,
82 }
83 }
84}
85
86pub struct AuthManager {
88 keyring: KeyringStore,
89}
90
91impl AuthManager {
92 pub fn new() -> Result<Self> {
93 Ok(Self {
94 keyring: KeyringStore::new()?,
95 })
96 }
97
98 pub fn get_credentials(&self) -> Result<Option<Credential>> {
100 self.keyring.get_credential()
101 }
102
103 pub fn store_credentials(&self, credential: &Credential) -> Result<()> {
105 self.keyring.store_credential(credential)
106 }
107
108 pub fn clear_credentials(&self) -> Result<()> {
110 self.keyring.delete_credential()
111 }
112
113 pub fn is_authenticated(&self) -> bool {
115 self.get_credentials().map(|c| c.is_some()).unwrap_or(false)
116 }
117}
118
119impl Default for AuthManager {
120 fn default() -> Self {
121 Self::new().expect("Failed to create auth manager")
122 }
123}