use anyhow::Result;
use inquire::{Password, Text};
use serde::Serialize;
use tangled_config::session::SessionManager;
use crate::cli::{AuthCommand, AuthLoginArgs, Cli, OutputFormat};
pub async fn run(cli: &Cli, cmd: AuthCommand) -> Result<()> {
match cmd {
AuthCommand::Login(args) => login(cli, args).await,
AuthCommand::Status => status(cli).await,
AuthCommand::Logout => logout(cli).await,
}
}
async fn login(_cli: &Cli, mut args: AuthLoginArgs) -> Result<()> {
let handle: String = match args.handle.take() {
Some(h) => h,
None => Text::new("Handle").prompt()?,
};
let use_app_password = args.app_password || args.password.is_some();
if !use_app_password {
let mut session = crate::ops::oauth::login(&handle).await?;
session.handle = if handle.starts_with("did:") {
let auth = crate::ops::auth::PdsAuth::from_session(&session)?;
let pds = session.pds.clone().unwrap_or_default();
crate::ops::repo::resolve_did_to_handle(&pds, &session.did, &auth)
.await
.unwrap_or_else(|_| handle.clone())
} else {
handle.clone()
};
SessionManager::default().save(&session)?;
println!(
"Logged in as '{}' ({}) via OAuth",
session.handle, session.did
);
return Ok(());
}
let password: String = match args.password.take() {
Some(p) => p,
None => Password::new("Password").prompt()?,
};
let pds = args
.pds
.unwrap_or_else(|| "https://bsky.social".to_string());
let mut session = match crate::ops::session::login_with_password(
&pds, &handle, &password,
)
.await
{
Ok(sess) => sess,
Err(e) => {
println!("\x1b[93mIf you're on your own PDS, make sure to pass the --pds flag\x1b[0m");
return Err(e);
}
};
session.pds = Some(pds.clone());
SessionManager::default().save(&session)?;
println!("Logged in as '{}' ({})", session.handle, session.did);
Ok(())
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
struct AuthStatusOutput {
authenticated: bool,
#[serde(skip_serializing_if = "Option::is_none")]
handle: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
did: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
auth_type: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
pds: Option<String>,
}
async fn status(cli: &Cli) -> Result<()> {
let mgr = SessionManager::default();
match mgr.load()? {
Some(session) => {
let (auth_type, auth_display) = if session.oauth.is_some() {
("oauth", "OAuth")
} else {
("appPassword", "app password")
};
if matches!(cli.format, OutputFormat::Json | OutputFormat::Yaml) {
return crate::util::print_serialized(
cli.format,
&AuthStatusOutput {
authenticated: true,
handle: Some(session.handle),
did: Some(session.did),
auth_type: Some(auth_type),
pds: session.pds,
},
);
}
println!(
"Logged in as '{}' ({}) [{}]",
session.handle, session.did, auth_display
);
}
None if matches!(
cli.format,
OutputFormat::Json | OutputFormat::Yaml
) =>
{
crate::util::print_serialized(
cli.format,
&AuthStatusOutput {
authenticated: false,
handle: None,
did: None,
auth_type: None,
pds: None,
},
)?;
}
None => println!("Not logged in. Run: tang auth login"),
}
Ok(())
}
async fn logout(_cli: &Cli) -> Result<()> {
let mgr = SessionManager::default();
if mgr.load()?.is_some() {
mgr.clear()?;
println!("Logged out");
} else {
println!("No session found");
}
Ok(())
}