bitbucket_cli/auth/
app_password.rs

1use anyhow::{Context, Result};
2use dialoguer::{Input, Password};
3
4use super::{AuthManager, Credential};
5
6/// App password authentication flow
7pub struct AppPasswordAuth;
8
9impl AppPasswordAuth {
10    /// Run the interactive app password authentication flow
11    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        // Validate credentials by making a test API call
38        Self::validate_credentials(&credential).await?;
39
40        // Store credentials
41        auth_manager.store_credentials(&credential)?;
42
43        println!("\nāœ… Successfully authenticated as {}", username);
44
45        Ok(credential)
46    }
47
48    /// Validate credentials against the Bitbucket API
49    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}