1use std::io::{self, Write};
8
9use async_trait::async_trait;
11use eyre::Result;
12use url::Url;
13use webbrowser;
14
15use crate::storage::JwtToken;
17use crate::traits::ClientAuthenticator;
18
19pub struct CliAuthenticator {
24 output: Box<dyn OutputHandler + Send + Sync>,
26}
27
28impl std::fmt::Debug for CliAuthenticator {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 f.debug_struct("CliAuthenticator")
31 .field("output", &"<dyn OutputHandler>")
32 .finish()
33 }
34}
35
36impl Clone for CliAuthenticator {
37 fn clone(&self) -> Self {
38 Self::new()
40 }
41}
42
43pub trait OutputHandler: Send + Sync {
45 fn display_message(&self, message: &str);
47
48 fn display_error(&self, error: &str);
50
51 fn display_success(&self, message: &str);
53
54 fn open_browser(&self, url: &Url) -> Result<()>;
56
57 fn wait_for_input(&self, prompt: &str) -> Result<String>;
59}
60
61#[derive(Debug, Clone, Copy)]
63pub struct ConsoleOutputHandler;
64
65impl OutputHandler for ConsoleOutputHandler {
66 fn display_message(&self, message: &str) {
67 println!("{}", message);
68 }
69
70 fn display_error(&self, error: &str) {
71 eprintln!("Error: {}", error);
72 }
73
74 fn display_success(&self, message: &str) {
75 println!("✓ {}", message);
76 }
77
78 fn open_browser(&self, url: &Url) -> Result<()> {
79 webbrowser::open(url.as_str())?;
80 Ok(())
81 }
82
83 fn wait_for_input(&self, prompt: &str) -> Result<String> {
84 print!("{}", prompt);
85 io::stdout().flush()?;
86
87 let mut input = String::new();
88 let _ = io::stdin().read_line(&mut input)?;
89
90 Ok(input.trim().to_owned())
91 }
92}
93
94impl CliAuthenticator {
95 pub fn new() -> Self {
97 Self {
98 output: Box::new(ConsoleOutputHandler),
99 }
100 }
101
102 pub fn with_output(output: Box<dyn OutputHandler + Send + Sync>) -> Self {
104 Self { output }
105 }
106
107 pub fn output(&self) -> &dyn OutputHandler {
109 self.output.as_ref()
110 }
111}
112
113#[async_trait]
114impl ClientAuthenticator for CliAuthenticator {
115 async fn authenticate(&self, api_url: &Url) -> Result<JwtToken> {
116 self.output.display_message("Starting authentication...");
117
118 self.output
126 .display_message(&format!("Please authenticate at: {}", api_url));
127
128 let access_token = self.output.wait_for_input("Enter access token: ")?;
130
131 if access_token.is_empty() {
132 return Err(eyre::eyre!("Access token cannot be empty"));
133 }
134
135 let refresh_token = self
136 .output
137 .wait_for_input("Enter refresh token (optional): ")?;
138
139 let token = if refresh_token.is_empty() {
140 JwtToken::new(access_token)
141 } else {
142 JwtToken::with_refresh(access_token, refresh_token)
143 };
144
145 self.output.display_success("Authentication successful!");
146 Ok(token)
147 }
148
149 async fn refresh_tokens(&self, _refresh_token: &str) -> Result<JwtToken> {
150 self.output
151 .display_message("Refreshing authentication tokens...");
152
153 self.output
160 .display_message("Please provide new access token:");
161 let access_token = self.output.wait_for_input("Enter new access token: ")?;
162
163 if access_token.is_empty() {
164 return Err(eyre::eyre!("Access token cannot be empty"));
165 }
166
167 let token = JwtToken::new(access_token);
168 self.output.display_success("Token refresh successful!");
169 Ok(token)
170 }
171
172 async fn handle_auth_failure(&self, api_url: &Url) -> Result<JwtToken> {
173 self.output
174 .display_error("Authentication failed. Please try again.");
175
176 if let Err(e) = self.output.open_browser(api_url) {
178 self.output
179 .display_error(&format!("Failed to open browser: {}", e));
180 self.output
181 .display_message(&format!("Please manually visit: {}", api_url));
182 }
183
184 self.output
186 .display_message("Please complete authentication in your browser, then press Enter.");
187 drop(self.output.wait_for_input("Press Enter when done: ")?);
188
189 self.authenticate(api_url).await
191 }
192
193 async fn check_auth_required(&self, _api_url: &Url) -> Result<bool> {
194 Ok(true)
197 }
198
199 fn get_auth_method(&self) -> &'static str {
200 "CLI Browser-based OAuth"
201 }
202
203 fn supports_refresh(&self) -> bool {
204 true
205 }
206}
207
208#[derive(Debug, Clone)]
210pub struct HeadlessAuthenticator {
211 tokens: Option<JwtToken>,
213}
214
215impl HeadlessAuthenticator {
216 pub fn new() -> Self {
218 Self { tokens: None }
219 }
220
221 pub fn with_tokens(tokens: JwtToken) -> Self {
223 Self {
224 tokens: Some(tokens),
225 }
226 }
227
228 pub fn set_tokens(&mut self, tokens: JwtToken) {
230 self.tokens = Some(tokens);
231 }
232}
233
234#[async_trait]
235impl ClientAuthenticator for HeadlessAuthenticator {
236 async fn authenticate(&self, _api_url: &Url) -> Result<JwtToken> {
237 if let Some(tokens) = &self.tokens {
238 Ok(tokens.clone())
239 } else {
240 Err(eyre::eyre!(
241 "No tokens configured for headless authenticator"
242 ))
243 }
244 }
245
246 async fn refresh_tokens(&self, _refresh_token: &str) -> Result<JwtToken> {
247 Err(eyre::eyre!("Token refresh not supported in headless mode"))
248 }
249
250 async fn handle_auth_failure(&self, _api_url: &Url) -> Result<JwtToken> {
251 Err(eyre::eyre!(
252 "Cannot handle authentication failure in headless mode"
253 ))
254 }
255
256 async fn check_auth_required(&self, _api_url: &Url) -> Result<bool> {
257 Ok(true)
258 }
259
260 fn get_auth_method(&self) -> &'static str {
261 "Headless Pre-configured Tokens"
262 }
263
264 fn supports_refresh(&self) -> bool {
265 false
266 }
267}
268
269#[derive(Debug, Clone)]
271pub struct ApiKeyAuthenticator {
272 api_key: String,
274}
275
276impl ApiKeyAuthenticator {
277 pub fn new(api_key: String) -> Self {
279 Self { api_key }
280 }
281
282 pub fn api_key(&self) -> &str {
284 &self.api_key
285 }
286}
287
288#[async_trait]
289impl ClientAuthenticator for ApiKeyAuthenticator {
290 async fn authenticate(&self, _api_url: &Url) -> Result<JwtToken> {
291 let token = JwtToken::new(self.api_key.clone())
293 .with_metadata("auth_type".to_owned(), serde_json::json!("api_key"));
294 Ok(token)
295 }
296
297 async fn refresh_tokens(&self, _refresh_token: &str) -> Result<JwtToken> {
298 Err(eyre::eyre!("API keys do not support token refresh"))
300 }
301
302 async fn handle_auth_failure(&self, _api_url: &Url) -> Result<JwtToken> {
303 Err(eyre::eyre!("API key authentication failed"))
304 }
305
306 async fn check_auth_required(&self, _api_url: &Url) -> Result<bool> {
307 Ok(true)
308 }
309
310 fn get_auth_method(&self) -> &'static str {
311 "API Key"
312 }
313
314 fn supports_refresh(&self) -> bool {
315 false
316 }
317}
318
319pub trait MeroctlOutputHandler: Send + Sync {
321 fn display_message(&self, message: &str);
323
324 fn display_error(&self, error: &str);
326
327 fn display_success(&self, message: &str);
329
330 fn open_browser(&self, url: &Url) -> Result<()>;
332
333 fn wait_for_input(&self, prompt: &str) -> Result<String>;
335}