use leetcode_tui_config::{AuthConfig, CONFIG};
pub mod persist;
#[cfg(feature = "webview-login")]
pub mod webview;
pub const SESSION_COOKIE: &str = "LEETCODE_SESSION";
pub const CSRF_COOKIE: &str = "csrftoken";
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Source {
Env,
Config,
Webview,
None,
}
impl Source {
pub fn as_str(&self) -> &str {
match self {
Source::Env => "environment variables",
Source::Config => "config file",
Source::Webview => "login window",
Source::None => "none",
}
}
}
impl std::fmt::Display for Source {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.as_str())
}
}
#[derive(Debug, Clone)]
pub struct Credentials {
pub session: String,
pub csrf: String,
pub source: Source,
}
impl Credentials {
pub fn new(session: impl Into<String>, csrf: impl Into<String>, source: Source) -> Self {
Self {
session: session.into(),
csrf: csrf.into(),
source,
}
}
pub fn anonymous() -> Self {
Self::new(String::new(), String::new(), Source::None)
}
pub fn is_valid(&self) -> bool {
!self.session.trim().is_empty() && !self.csrf.trim().is_empty()
}
}
#[derive(Debug, thiserror::Error)]
pub enum AuthError {
#[error("No LeetCode credentials are available. Run `leetui login` to sign in.")]
NoCredentials,
#[error("The login window was closed before signing in.")]
LoginCancelled,
#[error("Could not open the login window: {0}")]
Webview(String),
#[error("Could not save the session: {0}")]
Persist(String),
#[error(transparent)]
Io(#[from] std::io::Error),
}
pub type AuthResult<T> = Result<T, AuthError>;
pub fn from_env() -> Option<Credentials> {
let session = std::env::var(SESSION_COOKIE).ok()?;
let csrf = std::env::var("LEETCODE_CSRFTOKEN")
.ok()
.or_else(|| std::env::var("CSRFTOKEN").ok())?;
let credentials = Credentials::new(session, csrf, Source::Env);
credentials.is_valid().then_some(credentials)
}
pub fn from_config() -> Option<Credentials> {
let config = CONFIG.as_ref();
let credentials = Credentials::new(
config.lc_session.as_ref()?,
config.csrftoken.as_ref()?,
Source::Config,
);
credentials.is_valid().then_some(credentials)
}
pub fn from_config_file() -> Option<Credentials> {
let contents =
std::fs::read_to_string(leetcode_tui_config::utils::get_config_file_path()).ok()?;
let document = contents.parse::<toml_edit::DocumentMut>().ok()?;
let session = document.get("lc_session")?.as_str()?;
let csrf = document.get("csrftoken")?.as_str()?;
let credentials = Credentials::new(session, csrf, Source::Config);
credentials.is_valid().then_some(credentials)
}
pub fn stored() -> Option<Credentials> {
from_env().or_else(from_config_file).or_else(from_config)
}
pub fn reset_webview_profile(auth_config: &AuthConfig) -> AuthResult<()> {
#[cfg(feature = "webview-login")]
{
webview::reset_profile(auth_config)
}
#[cfg(not(feature = "webview-login"))]
{
let _ = auth_config;
Ok(())
}
}
#[derive(Debug, Default, Clone)]
pub struct LoginOptions;
pub fn login(_options: LoginOptions, previous: Option<&Credentials>) -> AuthResult<Credentials> {
let auth_config = CONFIG.as_ref().auth.clone();
#[cfg(feature = "webview-login")]
{
if auth_config.webview {
return match webview::run(&auth_config, previous) {
Ok(credentials) => Ok(credentials),
Err(AuthError::LoginCancelled) => Err(AuthError::LoginCancelled),
Err(error) => {
log::warn!("login window failed: {error}");
Err(AuthError::Webview(error.to_string()))
}
};
}
}
Err(AuthError::NoCredentials)
}