use clap::Parser;
use miette::IntoDiagnostic;
use rattler_networking::{Authentication, AuthenticationStorage};
#[derive(Parser, Debug)]
pub struct LoginArgs {
host: String,
#[clap(long)]
token: Option<String>,
#[clap(long)]
username: Option<String>,
#[clap(long)]
password: Option<String>,
#[clap(long)]
conda_token: Option<String>,
}
#[derive(Parser, Debug)]
struct LogoutArgs {
host: String,
}
#[derive(Parser, Debug)]
enum Subcommand {
Login(LoginArgs),
Logout(LogoutArgs),
}
#[derive(Parser, Debug)]
pub struct Args {
#[clap(subcommand)]
subcommand: Subcommand,
}
fn get_url(url: &str) -> miette::Result<String> {
let host = if url.contains("://") {
url::Url::parse(url)
.into_diagnostic()?
.host_str()
.unwrap()
.to_string()
} else {
url.to_string()
};
let host = if host.matches('.').count() == 1 {
format!("*.{}", host)
} else {
host
};
Ok(host)
}
fn login(args: LoginArgs, storage: AuthenticationStorage) -> miette::Result<()> {
let host = get_url(&args.host)?;
println!("Authenticating with {}", host);
let auth = if let Some(conda_token) = args.conda_token {
Authentication::CondaToken(conda_token)
} else if let Some(username) = args.username {
if args.password.is_none() {
miette::bail!("Password must be provided when using basic authentication");
}
let password = args.password.unwrap();
Authentication::BasicHTTP { username, password }
} else if let Some(token) = args.token {
Authentication::BearerToken(token)
} else {
miette::bail!("No authentication method provided");
};
if host.contains("prefix.dev") && !matches!(auth, Authentication::BearerToken(_)) {
miette::bail!(
"Authentication with prefix.dev requires a token. Use `--token` to provide one."
);
}
if host.contains("anaconda.org") && !matches!(auth, Authentication::CondaToken(_)) {
miette::bail!("Authentication with anaconda.org requires a conda token. Use `--conda-token` to provide one.");
}
storage
.store(&host, &auth)
.map_err(|e| miette::miette!(e.to_string()))?;
Ok(())
}
fn logout(args: LogoutArgs, storage: AuthenticationStorage) -> miette::Result<()> {
let host = get_url(&args.host)?;
println!("Removing authentication for {}", host);
storage
.delete(&host)
.map_err(|e| miette::miette!(e.to_string()))?;
Ok(())
}
pub async fn execute(args: Args) -> miette::Result<()> {
let storage = AuthenticationStorage::default();
match args.subcommand {
Subcommand::Login(args) => login(args, storage),
Subcommand::Logout(args) => logout(args, storage),
}
}