use anyhow::Result;
use clap::Subcommand;
use colored::Colorize;
use dialoguer::Input;
use crate::auth::{AuthManager, OAuthFlow};
use crate::config::Config;
#[derive(Subcommand)]
pub enum AuthCommands {
Login {
#[arg(long, env = "BITBUCKET_CLIENT_ID")]
client_id: Option<String>,
#[arg(long, env = "BITBUCKET_CLIENT_SECRET")]
client_secret: Option<String>,
},
Logout,
Status,
}
impl AuthCommands {
pub async fn run(self) -> Result<()> {
match self {
AuthCommands::Login {
client_id,
client_secret,
} => {
let auth_manager = AuthManager::new()?;
let stored_consumer = auth_manager
.get_credentials()
.ok()
.flatten()
.and_then(|c| {
c.oauth_consumer_credentials()
.map(|(id, secret)| (id.to_owned(), secret.to_owned()))
});
let client_id = client_id
.or_else(|| stored_consumer.as_ref().map(|(id, _)| id.clone()))
.or_else(|| {
println!();
println!("📋 OAuth Consumer Setup Required");
println!();
println!("To use OAuth authentication, create an OAuth consumer in Bitbucket:");
println!("1. Go to: https://bitbucket.org/[workspace]/workspace/settings/oauth-consumers/new");
println!("2. Set callback URL to ONE of these (pick any available port):");
println!(" • http://127.0.0.1:8080/callback");
println!(" • http://127.0.0.1:3000/callback");
println!(" • http://127.0.0.1:8888/callback");
println!(" • http://127.0.0.1:9000/callback");
println!("3. Select required permissions:");
println!(" ✓ Account (Read)");
println!(" ✓ Repositories (Read)");
println!(" ✓ Pull requests (Read, Write)");
println!(" ✓ Issues (Read, Write)");
println!(" ✓ Pipelines (Read, Write)");
println!("4. Copy the Key (Client ID) and Secret");
println!();
Input::<String>::new()
.with_prompt("OAuth Client ID (Key)")
.interact_text()
.ok()
})
.ok_or_else(|| anyhow::anyhow!("OAuth Client ID is required"))?;
let client_secret = client_secret
.or_else(|| stored_consumer.map(|(_, secret)| secret))
.or_else(|| {
Input::<String>::new()
.with_prompt("OAuth Client Secret")
.interact_text()
.ok()
})
.ok_or_else(|| anyhow::anyhow!("OAuth Client Secret is required"))?;
let oauth = OAuthFlow::new(client_id, client_secret);
oauth.authenticate(&auth_manager).await?;
Ok(())
}
AuthCommands::Logout => {
let auth_manager = AuthManager::new()?;
auth_manager.clear_credentials()?;
let mut config = Config::load()?;
config.clear_auth();
config.save()?;
println!("{} Logged out successfully", "✓".green());
Ok(())
}
AuthCommands::Status => {
let auth_manager = AuthManager::new()?;
let config = Config::load()?;
if auth_manager.is_authenticated() {
println!("{} Authenticated via OAuth 2.0", "✓".green());
if let Ok(Some(credential)) = auth_manager.get_credentials() {
if credential.needs_refresh() {
println!(
" {} {}",
"Status:".dimmed(),
"Token needs refresh (will auto-refresh on next use)".yellow()
);
}
}
if let Some(username) = config.username() {
println!(" {} {}", "Username:".dimmed(), username);
}
if let Some(workspace) = config.default_workspace() {
println!(" {} {}", "Workspace:".dimmed(), workspace);
}
match crate::api::BitbucketClient::from_stored().await {
Ok(client) => match client.get::<serde_json::Value>("/user").await {
Ok(user) => {
if let Some(display_name) = user.get("display_name") {
println!(
" {} {}",
"Display name:".dimmed(),
display_name.as_str().unwrap_or("Unknown")
);
}
}
Err(e) => {
println!("{} Credentials may be invalid: {}", "âš ".yellow(), e);
}
},
Err(e) => {
println!("{} Failed to create client: {}", "✗".red(), e);
}
}
} else {
println!("{} Not authenticated", "✗".red());
println!();
println!("Run {} to authenticate", "bitbucket auth login".cyan());
}
Ok(())
}
}
}
}