Skip to main content

oxide_cli/auth/
login.rs

1use std::path::Path;
2
3use anyhow::Result;
4use inquire::Confirm;
5
6use crate::auth::{server::run_local_auth_server, token::get_auth_user};
7
8pub async fn login(auth_path: &Path, backend_url: &str, frontend_url: &str) -> Result<()> {
9  if let Ok(existing) = get_auth_user(auth_path) {
10    let proceed = Confirm::new(&format!(
11      "Already logged in as @{}. Log in with a different account?",
12      existing.name
13    ))
14    .with_default(false)
15    .prompt()?;
16
17    if !proceed {
18      return Ok(());
19    }
20  }
21
22  let state = generate_state_token();
23  // NOTE: oxide-server must forward the `?state=` query param it receives
24  // at /auth/cli-login through to the localhost callback redirect so that
25  // the CSRF check below can validate it.
26  open::that(format!("{}/auth/cli-login?state={}", backend_url, state))?;
27  println!("Go to your browser for further authorization");
28  let user = run_local_auth_server(state, frontend_url).await?;
29
30  let auth_json = serde_json::to_string(&user)?;
31  write_auth_file(auth_path, &auth_json)?;
32
33  println!("✅ Authorization successful as @{}", user.name);
34
35  Ok(())
36}
37
38/// Generates a cryptographically random 128-bit state token for CSRF protection.
39fn generate_state_token() -> String {
40  uuid::Uuid::new_v4().simple().to_string()
41}
42
43/// Writes `content` to `path` with owner-only read/write permissions (0600)
44/// on Unix, preventing other local users from reading the auth token.
45fn write_auth_file(path: &Path, content: &str) -> Result<()> {
46  #[cfg(unix)]
47  {
48    use std::io::Write;
49    use std::os::unix::fs::OpenOptionsExt;
50    let mut file = std::fs::OpenOptions::new()
51      .write(true)
52      .create(true)
53      .truncate(true)
54      .mode(0o600)
55      .open(path)?;
56    file.write_all(content.as_bytes())?;
57  }
58  #[cfg(not(unix))]
59  {
60    std::fs::write(path, content)?;
61  }
62  Ok(())
63}