use clap::Args;
use dialoguer::{Confirm, Input, Select};
use crate::auth::AuthService;
use crate::config::ConfigStore;
use crate::error::Result;
use crate::util::fmt;
#[derive(Debug, Args)]
pub struct LoginArgs {
#[arg(short, long)]
pub force: bool,
#[arg(short, long)]
pub name: Option<String>,
}
pub async fn run(args: LoginArgs) -> Result<()> {
let mut auth = AuthService::global();
auth.init(None);
if auth.is_logged_in() && !args.force {
let sources = auth.list_credentials();
let current = auth.current_credentials();
if current.is_none() {
fmt::error("No valid credentials found.");
return Ok(());
}
let current = current.unwrap();
if sources.len() <= 1 {
fmt::success(&format!(
"You are already logged in as {}.",
current.email
));
let add_another = Confirm::new()
.with_prompt("Would you like to login as another user?")
.default(false)
.interact()
.unwrap_or(false);
if !add_another {
return Ok(());
}
} else {
fmt::success("You are already logged in.");
eprintln!(
"Current source: {} ({})",
current.name, current.email
);
let add_another = Confirm::new()
.with_prompt("Would you like to add another credentials?")
.default(false)
.interact()
.unwrap_or(false);
if !add_another {
return Ok(());
}
}
}
let methods = &["Google OAuth", "Email (OTP)"];
let selection = Select::new()
.with_prompt("Choose login method")
.items(methods)
.default(0)
.interact()
.unwrap_or(0);
let creds_before = auth.list_credentials().len();
let cred = match selection {
0 => {
fmt::info("Starting OAuth login...");
let cred = auth.login_interactive_blocking(args.name.as_deref())?;
fmt::success(&format!("Logged in as {}", cred.email));
Some(cred)
}
_ => {
let last_email = {
let store = ConfigStore::global();
store.get_str("login.email").map(String::from)
};
let email: String = Input::new()
.with_prompt("Email address")
.default(last_email.unwrap_or_default())
.interact_text()
.map_err(|_| crate::error::Error::Other("Cancelled".into()))?;
if email.is_empty() {
fmt::error("Email address is required.");
return Ok(());
}
fmt::info(&format!("Sending OTP to {email}..."));
if args.force {
auth.logout_current();
}
auth.send_otp(&email)?;
fmt::success("OTP sent. Check your email.");
let otp: String = Input::new()
.with_prompt("Enter the code received by email")
.interact_text()
.map_err(|_| crate::error::Error::Other("Cancelled".into()))?;
match auth.verify_otp(&email, &otp, args.name.as_deref()) {
Ok(cred) => {
fmt::success(&format!("Logged in as {}", cred.email));
Some(cred)
}
Err(_) => {
fmt::error("Login failed. Invalid OTP.");
return Ok(());
}
}
}
};
if let Some(ref cred) = cred {
if creds_before == 0 {
let _ = auth.set_default(&cred.name);
} else {
fmt::success(&format!(
"Credentials '{}' created!",
cred.label()
));
eprintln!("Email: {}", cred.email);
eprintln!("Type: {}", cred.cred_type);
let set_default = Confirm::new()
.with_prompt(format!(
"Set '{}' as the default credentials?",
cred.name
))
.default(true)
.interact()
.unwrap_or(false);
if set_default {
let _ = auth.set_default(&cred.name);
fmt::success(&format!(
"'{}' set as default credentials.",
cred.name
));
}
eprintln!();
auth.show_login_info();
}
} else {
fmt::error("Login failed.");
}
Ok(())
}