bitbucket_cli/cli/
auth.rs1use anyhow::Result;
2use clap::Subcommand;
3use colored::Colorize;
4use dialoguer::Input;
5
6use crate::auth::{AuthManager, OAuthFlow};
7use crate::config::Config;
8
9#[derive(Subcommand)]
10pub enum AuthCommands {
11 Login {
13 #[arg(long, env = "BITBUCKET_CLIENT_ID")]
15 client_id: Option<String>,
16
17 #[arg(long, env = "BITBUCKET_CLIENT_SECRET")]
19 client_secret: Option<String>,
20 },
21
22 Logout,
24
25 Status,
27}
28
29impl AuthCommands {
30 pub async fn run(self) -> Result<()> {
31 match self {
32 AuthCommands::Login {
33 client_id,
34 client_secret,
35 } => {
36 let auth_manager = AuthManager::new()?;
37
38 let stored_consumer = auth_manager
43 .get_credentials()
44 .ok()
45 .flatten()
46 .and_then(|c| {
47 c.oauth_consumer_credentials()
48 .map(|(id, secret)| (id.to_owned(), secret.to_owned()))
49 });
50
51 let client_id = client_id
52 .or_else(|| stored_consumer.as_ref().map(|(id, _)| id.clone()))
53 .or_else(|| {
54 println!();
55 println!("📋 OAuth Consumer Setup Required");
56 println!();
57 println!("To use OAuth authentication, create an OAuth consumer in Bitbucket:");
58 println!("1. Go to: https://bitbucket.org/[workspace]/workspace/settings/oauth-consumers/new");
59 println!("2. Set callback URL to ONE of these (pick any available port):");
60 println!(" • http://127.0.0.1:8080/callback");
61 println!(" • http://127.0.0.1:3000/callback");
62 println!(" • http://127.0.0.1:8888/callback");
63 println!(" • http://127.0.0.1:9000/callback");
64 println!("3. Select required permissions:");
65 println!(" ✓ Account (Read)");
66 println!(" ✓ Repositories (Read)");
67 println!(" ✓ Pull requests (Read, Write)");
68 println!(" ✓ Issues (Read, Write)");
69 println!(" ✓ Pipelines (Read, Write)");
70 println!("4. Copy the Key (Client ID) and Secret");
71 println!();
72
73 Input::<String>::new()
74 .with_prompt("OAuth Client ID (Key)")
75 .interact_text()
76 .ok()
77 })
78 .ok_or_else(|| anyhow::anyhow!("OAuth Client ID is required"))?;
79
80 let client_secret = client_secret
81 .or_else(|| stored_consumer.map(|(_, secret)| secret))
82 .or_else(|| {
83 Input::<String>::new()
84 .with_prompt("OAuth Client Secret")
85 .interact_text()
86 .ok()
87 })
88 .ok_or_else(|| anyhow::anyhow!("OAuth Client Secret is required"))?;
89
90 let oauth = OAuthFlow::new(client_id, client_secret);
91 oauth.authenticate(&auth_manager).await?;
92
93 Ok(())
94 }
95
96 AuthCommands::Logout => {
97 let auth_manager = AuthManager::new()?;
98 auth_manager.clear_credentials()?;
99
100 let mut config = Config::load()?;
101 config.clear_auth();
102 config.save()?;
103
104 println!("{} Logged out successfully", "✓".green());
105 Ok(())
106 }
107
108 AuthCommands::Status => {
109 let auth_manager = AuthManager::new()?;
110 let config = Config::load()?;
111
112 if auth_manager.is_authenticated() {
113 println!("{} Authenticated via OAuth 2.0", "✓".green());
114
115 if let Ok(Some(credential)) = auth_manager.get_credentials() {
116 if credential.needs_refresh() {
117 println!(
118 " {} {}",
119 "Status:".dimmed(),
120 "Token needs refresh (will auto-refresh on next use)".yellow()
121 );
122 }
123 }
124
125 if let Some(username) = config.username() {
126 println!(" {} {}", "Username:".dimmed(), username);
127 }
128
129 if let Some(workspace) = config.default_workspace() {
130 println!(" {} {}", "Workspace:".dimmed(), workspace);
131 }
132
133 match crate::api::BitbucketClient::from_stored().await {
134 Ok(client) => match client.get::<serde_json::Value>("/user").await {
135 Ok(user) => {
136 if let Some(display_name) = user.get("display_name") {
137 println!(
138 " {} {}",
139 "Display name:".dimmed(),
140 display_name.as_str().unwrap_or("Unknown")
141 );
142 }
143 }
144 Err(e) => {
145 println!("{} Credentials may be invalid: {}", "âš ".yellow(), e);
146 }
147 },
148 Err(e) => {
149 println!("{} Failed to create client: {}", "✗".red(), e);
150 }
151 }
152 } else {
153 println!("{} Not authenticated", "✗".red());
154 println!();
155 println!("Run {} to authenticate", "bitbucket auth login".cyan());
156 }
157
158 Ok(())
159 }
160 }
161 }
162}