use crate::commands::api;
use crate::core::auth::GoogleAuth;
use crate::discovery;
use crate::output::{OutputFormat, print_output};
use anyhow::Result;
use clap::Subcommand;
use serde_json::{Value, json};
#[derive(Subcommand, Debug)]
pub enum AuthCommands {
Login(LoginArgs),
Status(StatusArgs),
Setup(crate::commands::setup::SetupArgs),
}
#[derive(clap::Args, Debug)]
pub struct LoginArgs {
#[arg(long)]
pub device: bool,
#[arg(long)]
pub account: Option<String>,
#[arg(short, long, value_enum, default_value = "json")]
pub format: OutputFormat,
}
#[derive(clap::Args, Debug)]
pub struct StatusArgs {
#[arg(long)]
pub account: Option<String>,
#[arg(short, long, value_enum, default_value = "json")]
pub format: OutputFormat,
}
fn token_preview(token: &str) -> String {
format!("{}...", &token[..std::cmp::min(20, token.len())])
}
pub async fn handle_auth_cmd(auth: &GoogleAuth, cmd: AuthCommands) -> Result<()> {
match cmd {
AuthCommands::Setup(_) => unreachable!("auth setup handled before client construction"),
AuthCommands::Login(login) => {
let auth = match &login.account {
Some(name) => auth.with_account_store(name).await?,
None => auth.with_default_account_store().await?,
};
if login.device {
return handle_device_login(&auth, login.format).await;
}
let storage = auth.login().await?;
print_output(
&json!({
"authenticated": true,
"account": login.account,
"token_backend": auth.token_backend(),
"token_preview": token_preview(&storage.access_token)
}),
login.format,
)?;
Ok(())
}
AuthCommands::Status(status) => {
let auth = match &status.account {
Some(name) => auth.with_account_store(name).await?,
None => auth.with_default_account_store().await?,
};
let profile = fetch_profile(&auth).await?;
print_output(
&json!({
"authenticated": true,
"account": status.account,
"email": field(&profile, "emailAddress"),
"messages_total": field(&profile, "messagesTotal"),
"threads_total": field(&profile, "threadsTotal"),
"history_id": field(&profile, "historyId"),
}),
status.format,
)?;
Ok(())
}
}
}
async fn fetch_profile(auth: &GoogleAuth) -> Result<Value> {
let (service, method) =
discovery::resolve("gmail.users.getProfile").map_err(anyhow::Error::msg)?;
let mut params = serde_json::Map::new();
params.insert("userId".into(), json!("me"));
api::call_method(
Some(auth),
service,
method,
params,
api::CallOptions::default(),
)
.await
}
fn field(profile: &Value, key: &str) -> Value {
profile.get(key).cloned().unwrap_or(Value::Null)
}
async fn handle_device_login(auth: &GoogleAuth, format: OutputFormat) -> Result<()> {
let mut challenge = auth.request_device_code().await?;
println!(
"Visit {} and enter code: {}",
challenge.verification_url, challenge.user_code
);
loop {
tokio::time::sleep(challenge.retry_after()).await;
if challenge.is_expired() {
anyhow::bail!("device code expired; rerun `grr auth login --device`");
}
if let Some(storage) = auth.poll_device_code(&mut challenge).await? {
print_output(
&json!({
"authenticated": true,
"token_backend": auth.token_backend(),
"token_preview": token_preview(&storage.access_token)
}),
format,
)?;
return Ok(());
}
}
}