use crate::auth::AuthedClient; use crate::ui; use anyhow::{anyhow, Result}; use argon2::password_hash::SaltString; use argon2::{Argon2, PasswordHasher}; use rand::rngs::OsRng; use rsh_types::{OpReq, OpResp}; pub fn hash_password(password: &str) -> Result { let salt = SaltString::generate(&mut OsRng); Argon2::default() .hash_password(password.as_bytes(), &salt) .map(|h| h.to_string()) .map_err(|e| anyhow!("hash: {e}")) } pub async fn create(client: &AuthedClient, name: String) -> Result<()> { let pw = inquire::Password::new("password (empty for none):") .without_confirmation() .with_display_mode(inquire::PasswordDisplayMode::Masked) .prompt() .map_err(|e| anyhow!("prompt: {e}"))?; let password_hash = if pw.is_empty() { None } else { Some(hash_password(&pw)?) }; match client.req(OpReq::SessionCreate { name: name.clone(), password_hash }).await? { OpResp::Ok => ui::print_ok(&format!("session '{name}' created")), OpResp::Err(e) => return Err(anyhow!(e)), other => return Err(anyhow!("unexpected: {other:?}")), } Ok(()) } pub async fn delete(client: &AuthedClient, name: String, yes: bool, disconnect: bool) -> Result<()> { if !yes { let ok = inquire::Confirm::new(&format!("delete session '{name}'?")) .with_default(false) .prompt() .map_err(|e| anyhow!("prompt: {e}"))?; if !ok { ui::print_info("cancelled"); return Ok(()); } } match client.req(OpReq::SessionDelete { name: name.clone(), disconnect }).await? { OpResp::Ok => ui::print_ok(&format!("session '{name}' deleted")), OpResp::Err(e) => return Err(anyhow!(e)), other => return Err(anyhow!("unexpected: {other:?}")), } Ok(()) } pub async fn update( client: &AuthedClient, name: String, pw_flag: Option>, disconnect: bool, ) -> Result<()> { let set_password_hash = match pw_flag { None => None, Some(Some(p)) => Some(Some(hash_password(&p)?)), Some(None) => { let entered = inquire::Password::new("new password (empty clears):") .without_confirmation() .with_display_mode(inquire::PasswordDisplayMode::Masked) .prompt() .map_err(|e| anyhow!("prompt: {e}"))?; if entered.is_empty() { Some(None) } else { Some(Some(hash_password(&entered)?)) } } }; match client .req(OpReq::SessionUpdate { name: name.clone(), set_password_hash, disconnect }) .await? { OpResp::Ok => ui::print_ok(&format!("session '{name}' updated")), OpResp::Err(e) => return Err(anyhow!(e)), other => return Err(anyhow!("unexpected: {other:?}")), } Ok(()) } pub async fn list(client: &AuthedClient) -> Result<()> { match client.req(OpReq::SessionList).await? { OpResp::Sessions(s) => { if s.is_empty() { ui::print_info("no sessions"); } else { println!("{}", ui::sessions_table(&s)); } } OpResp::Err(e) => return Err(anyhow!(e)), other => return Err(anyhow!("unexpected: {other:?}")), } Ok(()) }