bitbucket_cli/cli/
auth.rs1use anyhow::Result;
2use clap::Subcommand;
3use colored::Colorize;
4use dialoguer::{Confirm, Input};
5
6use crate::auth::{ApiKeyAuth, AuthManager, OAuthFlow};
7use crate::config::Config;
8
9#[derive(Subcommand)]
10pub enum AuthCommands {
11 Login {
13 #[arg(long)]
15 oauth: bool,
16
17 #[arg(long)]
19 api_key: bool,
20
21 #[arg(long, env = "BITBUCKET_CLIENT_ID")]
23 client_id: Option<String>,
24
25 #[arg(long, env = "BITBUCKET_CLIENT_SECRET")]
27 client_secret: Option<String>,
28 },
29
30 Logout,
32
33 Status,
35}
36
37impl AuthCommands {
38 pub async fn run(self) -> Result<()> {
39 match self {
40 AuthCommands::Login {
41 oauth,
42 api_key,
43 client_id,
44 client_secret,
45 } => {
46 let auth_manager = AuthManager::new()?;
47
48 let use_oauth = if oauth || api_key {
50 oauth
52 } else {
53 println!("\nš Bitbucket CLI Authentication");
55 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
56 println!();
57 println!("Choose authentication method:");
58 println!();
59 println!(" 1. {} (Recommended)", "OAuth 2.0".green().bold());
60 println!(" ⢠More secure with token refresh");
61 println!(" ⢠Better user experience");
62 println!(" ⢠Requires OAuth app setup");
63 println!();
64 println!(" 2. {} (Fallback)", "API Key".yellow());
65 println!(" ⢠For automation/CI pipelines");
66 println!(" ⢠Requires HTTP access token");
67 println!(" ⢠No automatic refresh");
68 println!();
69
70 Confirm::new()
71 .with_prompt("Use OAuth 2.0?")
72 .default(true)
73 .interact()?
74 };
75
76 let credential = if use_oauth {
77 let client_id = client_id
79 .or_else(|| std::env::var("BITBUCKET_CLIENT_ID").ok())
80 .or_else(|| {
81 println!();
82 println!("š OAuth App Setup Required");
83 println!();
84 println!("To use OAuth authentication, you need to create an OAuth consumer:");
85 println!("1. Go to: https://bitbucket.org/[workspace]/workspace/settings/oauth-consumers/new");
86 println!("2. Set callback URL to: http://127.0.0.1:*/callback");
87 println!("3. Select required permissions (repository, pullrequest, issue, pipeline, account)");
88 println!("4. Copy the Client ID and Secret");
89 println!();
90
91 Input::<String>::new()
92 .with_prompt("OAuth Client ID")
93 .interact_text()
94 .ok()
95 })
96 .ok_or_else(|| anyhow::anyhow!("OAuth Client ID is required"))?;
97
98 let client_secret = client_secret
99 .or_else(|| std::env::var("BITBUCKET_CLIENT_SECRET").ok())
100 .or_else(|| {
101 Input::<String>::new()
102 .with_prompt("OAuth Client Secret")
103 .interact_text()
104 .ok()
105 })
106 .ok_or_else(|| anyhow::anyhow!("OAuth Client Secret is required"))?;
107
108 let oauth = OAuthFlow::new(client_id, client_secret);
109 oauth.authenticate(&auth_manager).await?
110 } else {
111 ApiKeyAuth::authenticate(&auth_manager).await?
113 };
114
115 if let Some(username) = credential.username() {
117 let mut config = Config::load()?;
118 config.set_username(username);
119 config.save()?;
120 }
121
122 Ok(())
123 }
124
125 AuthCommands::Logout => {
126 let auth_manager = AuthManager::new()?;
127 auth_manager.clear_credentials()?;
128
129 let mut config = Config::load()?;
130 config.clear_auth();
131 config.save()?;
132
133 println!("{} Logged out successfully", "ā".green());
134 Ok(())
135 }
136
137 AuthCommands::Status => {
138 let auth_manager = AuthManager::new()?;
139 let config = Config::load()?;
140
141 if auth_manager.is_authenticated() {
142 println!("{} Authenticated", "ā".green());
143
144 if let Ok(Some(credential)) = auth_manager.get_credentials() {
146 println!(" {} {}", "Method:".dimmed(), credential.type_name());
147
148 if credential.is_oauth() && credential.needs_refresh() {
149 println!(" {} {}", "Status:".dimmed(), "Token needs refresh".yellow());
150 }
151 }
152
153 if let Some(username) = config.username() {
154 println!(" {} {}", "Username:".dimmed(), username);
155 }
156
157 if let Some(workspace) = config.default_workspace() {
158 println!(" {} {}", "Workspace:".dimmed(), workspace);
159 }
160
161 match crate::api::BitbucketClient::from_stored() {
163 Ok(client) => match client.get::<serde_json::Value>("/user").await {
164 Ok(user) => {
165 if let Some(display_name) = user.get("display_name") {
166 println!(
167 " {} {}",
168 "Display name:".dimmed(),
169 display_name.as_str().unwrap_or("Unknown")
170 );
171 }
172 }
173 Err(e) => {
174 println!("{} Credentials may be invalid: {}", "ā ".yellow(), e);
175 }
176 },
177 Err(e) => {
178 println!("{} Failed to create client: {}", "ā".red(), e);
179 }
180 }
181 } else {
182 println!("{} Not authenticated", "ā".red());
183 println!();
184 println!("Run {} to authenticate", "bitbucket auth login".cyan());
185 println!();
186 println!("š” Tip: Use {} for the best experience", "--oauth".green());
187 }
188
189 Ok(())
190 }
191 }
192 }
193}