Files
rsh/crates/rshc/src/cmd/session.rs
2026-05-12 21:38:14 +09:00

98 lines
3.3 KiB
Rust

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<String> {
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<Option<String>>,
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(())
}