bitbucket_cli/auth/
app_password.rs1use anyhow::{Context, Result};
2use dialoguer::{Input, Password};
3
4use super::{AuthManager, Credential};
5
6pub struct AppPasswordAuth;
8
9impl AppPasswordAuth {
10 pub async fn authenticate(auth_manager: &AuthManager) -> Result<Credential> {
12 println!("\nš Bitbucket App Password Authentication");
13 println!("āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā");
14 println!();
15 println!("To create an app password:");
16 println!("1. Go to Bitbucket Settings ā Personal Bitbucket settings");
17 println!("2. Click 'App passwords' under 'Access management'");
18 println!("3. Click 'Create app password'");
19 println!("4. Give it a label and select required permissions");
20 println!();
21
22 let username: String = Input::new()
23 .with_prompt("Bitbucket username")
24 .interact_text()
25 .context("Failed to read username")?;
26
27 let app_password: String = Password::new()
28 .with_prompt("App password")
29 .interact()
30 .context("Failed to read app password")?;
31
32 let credential = Credential::AppPassword {
33 username: username.clone(),
34 app_password,
35 };
36
37 Self::validate_credentials(&credential).await?;
39
40 auth_manager.store_credentials(&credential)?;
42
43 println!("\nā
Successfully authenticated as {}", username);
44
45 Ok(credential)
46 }
47
48 async fn validate_credentials(credential: &Credential) -> Result<()> {
50 let client = reqwest::Client::new();
51
52 let response = client
53 .get("https://api.bitbucket.org/2.0/user")
54 .header("Authorization", credential.auth_header())
55 .send()
56 .await
57 .context("Failed to connect to Bitbucket API")?;
58
59 if response.status().is_success() {
60 Ok(())
61 } else if response.status() == reqwest::StatusCode::UNAUTHORIZED {
62 anyhow::bail!("Invalid username or app password")
63 } else {
64 let status = response.status();
65 let body = response.text().await.unwrap_or_default();
66 anyhow::bail!("API error ({}): {}", status, body)
67 }
68 }
69}