use crate::api::ControlPlaneClient;
use crate::auth_context::{credentials_available, load_play_auth, PlayAuthKind};
use crate::config::{default_api_base, SavedLogin, SessionConfig};
#[derive(serde::Deserialize)]
struct LoginData {
session_token: String,
}
pub async fn login(email: &str, password: &str, api: &str) -> anyhow::Result<()> {
if email.trim().is_empty() || password.is_empty() {
anyhow::bail!("email and password are required");
}
let client = ControlPlaneClient::new(api);
let data: LoginData = client
.post(
"/v1/auth/login",
&serde_json::json!({ "email": email.trim(), "password": password }),
None,
)
.await?;
let last_character_id = SessionConfig::load().ok().and_then(|s| s.last_character_id);
let api_base = api.trim_end_matches('/').to_string();
let config = SessionConfig {
api_base: api_base.clone(),
session_token: data.session_token,
last_character_id,
};
config.save()?;
let saved_api_base = if flatland_client_lib::host_override_active() {
SavedLogin::load().and_then(|s| s.api_base)
} else {
Some(api_base)
};
SavedLogin {
email: email.trim().to_string(),
password: password.to_string(),
api_base: saved_api_base,
}
.save()?;
Ok(())
}
pub async fn register(
email: &str,
password: &str,
display_name: &str,
api: &str,
) -> anyhow::Result<()> {
if email.trim().is_empty() || password.is_empty() || display_name.trim().is_empty() {
anyhow::bail!("email, password, and display name are required");
}
let client = ControlPlaneClient::new(api);
let _: serde_json::Value = client
.post(
"/v1/auth/register",
&serde_json::json!({
"email": email.trim(),
"password": password,
"display_name": display_name.trim(),
}),
None,
)
.await?;
login(email, password, api).await
}
pub async fn login_default(email: &str, password: &str) -> anyhow::Result<()> {
let api = SavedLogin::load()
.and_then(|s| s.api_base)
.unwrap_or_else(default_api_base);
login(email, password, &api).await
}
pub async fn register_default(
email: &str,
password: &str,
display_name: &str,
) -> anyhow::Result<()> {
let api = SavedLogin::load()
.and_then(|s| s.api_base)
.unwrap_or_else(default_api_base);
register(email, password, display_name, &api).await
}
pub fn has_api_token() -> bool {
std::env::var("FLATLAND_API_TOKEN")
.map(|t| !t.is_empty())
.unwrap_or(false)
}
pub async fn session_is_valid() -> bool {
if has_api_token() {
return true;
}
if !credentials_available() {
return false;
}
let Ok(auth) = load_play_auth() else {
return false;
};
if auth.kind == PlayAuthKind::ApiToken {
return true;
}
let client = ControlPlaneClient::new(&auth.api_base);
client
.get::<serde_json::Value>("/v1/me", &auth.bearer)
.await
.is_ok()
}
pub async fn try_restore_expired_session() -> bool {
if session_is_valid().await {
return true;
}
if !SessionConfig::exists() {
return false;
}
let Some(saved) = SavedLogin::load() else {
return false;
};
if saved.email.trim().is_empty() || saved.password.is_empty() {
return false;
}
let api = saved
.api_base
.clone()
.unwrap_or_else(default_api_base);
login(&saved.email, &saved.password, &api).await.is_ok()
}
pub async fn needs_interactive_login() -> bool {
if has_api_token() {
return false;
}
if session_is_valid().await {
return false;
}
if try_restore_expired_session().await {
return false;
}
true
}
pub async fn logout() -> anyhow::Result<()> {
if let Ok(session) = SessionConfig::load() {
let client = ControlPlaneClient::new(&session.api_base);
let _ = client
.post::<serde_json::Value, _>(
"/v1/auth/logout",
&serde_json::json!({}),
Some(&session.session_token),
)
.await;
}
SessionConfig::clear()?;
Ok(())
}