use std::io::{self, BufRead, Write};
use lobe_core::error::{Result, TloxError};
use serde::Deserialize;
use crate::config::{self, DEFAULT_API_URL, DEFAULT_WEB_URL};
#[derive(Debug, Deserialize)]
struct WhoAmIResponse {
user: WhoAmIUser,
}
#[derive(Debug, Deserialize)]
struct WhoAmIUser {
email: String,
#[serde(default)]
#[allow(dead_code)]
name: Option<String>,
tier: String,
}
pub async fn run_login(
token: Option<String>,
web_url: Option<String>,
api_url: Option<String>,
) -> Result<()> {
let mut config = config::load()?;
if let Some(url) = web_url {
config.urls.web = url;
}
if let Some(url) = api_url {
config.urls.api = url;
}
let token = match token {
Some(t) => t,
None => prompt_for_token(&config.urls.web)?,
};
let token = token.trim().to_string();
if !token.starts_with("lobe_") {
return Err(TloxError::Auth(
"invalid token — expected a value starting with `lobe_`".to_string(),
));
}
let user = verify_token(&config.urls.api, &token).await?;
config.auth.token = Some(token);
config.auth.email = Some(user.email.clone());
config::save(&config)?;
println!("✓ Hippocampus authorized · {} ({} tier)", user.email, user.tier);
Ok(())
}
fn prompt_for_token(web_url: &str) -> Result<String> {
let default_url_note = if web_url == DEFAULT_WEB_URL {
String::new()
} else {
format!(" (default: {DEFAULT_WEB_URL})")
};
println!();
println!("Visit this URL in your browser to mint a token:");
println!();
println!(" {web_url}/cli-auth{default_url_note}");
println!();
print!("Paste your token here: ");
io::stdout().flush()?;
let mut line = String::new();
io::stdin().lock().read_line(&mut line)?;
Ok(line)
}
async fn verify_token(api_url: &str, token: &str) -> Result<WhoAmIUser> {
let default_url_note = if api_url == DEFAULT_API_URL {
String::new()
} else {
format!(" (using {api_url})")
};
let url = format!("{api_url}/cli/whoami");
let client = reqwest::Client::new();
let response = client
.get(&url)
.header("Authorization", format!("Bearer {token}"))
.send()
.await
.map_err(|e| TloxError::Network(format!("failed to reach Lobe cloud{default_url_note}: {e}")))?;
let status = response.status();
if status == reqwest::StatusCode::UNAUTHORIZED {
return Err(TloxError::Auth(
"token was rejected — mint a fresh one at /cli-auth".to_string(),
));
}
if !status.is_success() {
return Err(TloxError::Network(format!(
"unexpected response from /cli/whoami: {status}"
)));
}
let payload: WhoAmIResponse = response
.json()
.await
.map_err(|e| TloxError::Network(format!("could not parse /cli/whoami response: {e}")))?;
Ok(payload.user)
}