1use std::path::Path;
2
3use anyhow::Result;
4use inquire::Confirm;
5
6use crate::{
7 BACKEND_URL,
8 auth::{server::run_local_auth_server, token::get_auth_user},
9};
10
11pub async fn login(auth_path: &Path) -> Result<()> {
12 if let Ok(existing) = get_auth_user(auth_path) {
13 let proceed = Confirm::new(&format!(
14 "Already logged in as @{}. Log in with a different account?",
15 existing.name
16 ))
17 .with_default(false)
18 .prompt()?;
19
20 if !proceed {
21 return Ok(());
22 }
23 }
24
25 let state = generate_state_token();
26 open::that(format!("{}/auth/cli-login?state={}", BACKEND_URL, state))?;
30 println!("Go to your browser for further authorization");
31 let user = run_local_auth_server(state).await?;
32
33 let auth_json = serde_json::to_string(&user)?;
34 write_auth_file(auth_path, &auth_json)?;
35
36 println!("✅ Authorization successful as @{}", user.name);
37
38 Ok(())
39}
40
41fn generate_state_token() -> String {
45 use std::collections::hash_map::DefaultHasher;
46 use std::hash::{Hash, Hasher};
47
48 let mut hasher = DefaultHasher::new();
49 std::time::SystemTime::now().hash(&mut hasher);
50 std::process::id().hash(&mut hasher);
51 format!("{:016x}", hasher.finish())
52}
53
54fn write_auth_file(path: &Path, content: &str) -> Result<()> {
57 #[cfg(unix)]
58 {
59 use std::io::Write;
60 use std::os::unix::fs::OpenOptionsExt;
61 let mut file = std::fs::OpenOptions::new()
62 .write(true)
63 .create(true)
64 .truncate(true)
65 .mode(0o600)
66 .open(path)?;
67 file.write_all(content.as_bytes())?;
68 }
69 #[cfg(not(unix))]
70 {
71 std::fs::write(path, content)?;
72 }
73 Ok(())
74}