use crate::{AuthError, AuthResult, Credentials};
use leetcode_tui_config::utils::get_config_file_path;
use std::path::Path;
use toml_edit::DocumentMut;
pub fn save(credentials: &Credentials) -> AuthResult<()> {
save_to(&get_config_file_path(), credentials)
}
pub fn clear() -> AuthResult<()> {
clear_at(&get_config_file_path())
}
pub fn clear_at(path: &Path) -> AuthResult<()> {
if !path.exists() {
return Ok(());
}
let existing = std::fs::read_to_string(path)?;
let mut document = existing
.parse::<DocumentMut>()
.map_err(|error| AuthError::Persist(error.to_string()))?;
document.remove("lc_session");
document.remove("csrftoken");
std::fs::write(path, document.to_string())?;
restrict_permissions(path);
Ok(())
}
pub fn save_to(path: &Path, credentials: &Credentials) -> AuthResult<()> {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
let existing = std::fs::read_to_string(path).unwrap_or_default();
let mut document = if existing.trim().is_empty() {
DocumentMut::new()
} else {
existing
.parse::<DocumentMut>()
.map_err(|error| AuthError::Persist(error.to_string()))?
};
document["lc_session"] = toml_edit::value(credentials.session.clone());
document["csrftoken"] = toml_edit::value(credentials.csrf.clone());
std::fs::write(path, document.to_string())?;
restrict_permissions(path);
log::info!("saved LeetCode session to {}", path.display());
Ok(())
}
fn restrict_permissions(path: &Path) {
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600));
}
#[cfg(not(unix))]
{
let _ = path;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Source;
fn temp_config(name: &str) -> std::path::PathBuf {
let dir = std::env::temp_dir().join(format!("leetui-auth-{}-{name}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
dir.join("config.toml")
}
#[test]
fn keeps_unrelated_settings() {
let path = temp_config("keeps-settings");
std::fs::write(
&path,
"solutions_dir = \"/tmp/leetui-solutions\"\n\n[theme]\nname = \"default\"\n",
)
.unwrap();
let credentials = Credentials::new("session-value", "csrf-value", Source::Webview);
save_to(&path, &credentials).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
assert!(written.contains("solutions_dir = \"/tmp/leetui-solutions\""));
assert!(written.contains("name = \"default\""));
assert!(written.contains("lc_session = \"session-value\""));
assert!(written.contains("csrftoken = \"csrf-value\""));
}
#[test]
fn creates_the_file_when_missing() {
let path = temp_config("creates-file");
let _ = std::fs::remove_file(&path);
let credentials = Credentials::new("session-value", "csrf-value", Source::Env);
save_to(&path, &credentials).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
assert!(written.contains("lc_session = \"session-value\""));
}
#[cfg(unix)]
#[test]
fn restricts_permissions() {
use std::os::unix::fs::PermissionsExt;
let path = temp_config("permissions");
let credentials = Credentials::new("session-value", "csrf-value", Source::Env);
save_to(&path, &credentials).unwrap();
let mode = std::fs::metadata(&path).unwrap().permissions().mode();
assert_eq!(mode & 0o777, 0o600);
}
#[test]
fn clear_removes_only_the_credentials() {
let path = temp_config("clear");
std::fs::write(&path, "solutions_dir = \"/tmp/leetui-solutions\"\n").unwrap();
let credentials = Credentials::new("session-value", "csrf-value", Source::Webview);
save_to(&path, &credentials).unwrap();
clear_at(&path).unwrap();
let written = std::fs::read_to_string(&path).unwrap();
assert!(!written.contains("lc_session"));
assert!(!written.contains("csrftoken"));
assert!(written.contains("solutions_dir = \"/tmp/leetui-solutions\""));
}
}