tui tweaks

This commit is contained in:
Aiden McClelland
2025-11-04 18:11:19 -07:00
parent 01400cb9ce
commit 056a9ff9b6
3 changed files with 66 additions and 28 deletions

View File

@@ -305,7 +305,7 @@ pub async fn reset_password(
let params = SetPasswordParams { let params = SetPasswordParams {
password: base32::encode( password: base32::encode(
base32::Alphabet::Rfc4648Lower { padding: false }, base32::Alphabet::Rfc4648Lower { padding: false },
&rand::random::<[u8; 10]>(), &rand::random::<[u8; 16]>(),
), ),
}; };

View File

@@ -28,7 +28,7 @@ use crate::tunnel::auth::SetPasswordParams;
use crate::tunnel::context::TunnelContext; use crate::tunnel::context::TunnelContext;
use crate::tunnel::db::TunnelDatabase; use crate::tunnel::db::TunnelDatabase;
use crate::util::serde::{HandlerExtSerde, Pem, display_serializable}; use crate::util::serde::{HandlerExtSerde, Pem, display_serializable};
use crate::util::tui::{choose, parse_as, prompt, prompt_multiline}; use crate::util::tui::{choose, choose_custom_display, parse_as, prompt, prompt_multiline};
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)] #[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@@ -229,10 +229,19 @@ pub async fn import_certificate_cli(
cert_string.truncate(0); cert_string.truncate(0);
let cert = cert?; let cert = cert?;
let key = cert.0.public_key()?; let pubkey = cert.0.public_key()?;
if chain.is_empty() {
if !key.public_eq(&pubkey) {
return Err(Error::new(
eyre!("Certificate does not match key!"),
ErrorKind::InvalidSignature,
));
}
}
if let Some(prev) = chain.last() { if let Some(prev) = chain.last() {
if !prev.verify(&key)? { if !prev.verify(&pubkey)? {
return Err(Error::new( return Err(Error::new(
eyre!(concat!( eyre!(concat!(
"Invalid Fullchain: ", "Invalid Fullchain: ",
@@ -243,7 +252,7 @@ pub async fn import_certificate_cli(
} }
} }
let is_root = cert.0.verify(&key)?; let is_root = cert.0.verify(&pubkey)?;
chain.push(cert.0); chain.push(cert.0);
@@ -494,8 +503,7 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
.await?, .await?,
)?; )?;
println!("📝 SSL Certificate:"); println!("📝 SSL Certificate:");
println!("{cert}"); print!("{cert}");
println!();
println!(concat!( println!(concat!(
"If you haven't already, ", "If you haven't already, ",
@@ -507,25 +515,35 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
Err(e) if e.kind == ErrorKind::ParseNetAddress => { Err(e) if e.kind == ErrorKind::ParseNetAddress => {
println!("Select the IP address at which to host the web interface:"); println!("Select the IP address at which to host the web interface:");
let available_ips = from_value::<Vec<IpAddr>>( let mut suggested_addrs = from_value::<Vec<IpAddr>>(
ctx.call_remote::<TunnelContext>("web.get-available-ips", json!({})) ctx.call_remote::<TunnelContext>("web.get-available-ips", json!({}))
.await?, .await?,
)?; )?;
let suggested_addrs = available_ips suggested_addrs.sort_by_cached_key(|a| match a {
.into_iter() IpAddr::V4(a) => {
.filter(|ip| match ip { if a.is_loopback() {
IpAddr::V4(ipv4) => !ipv4.is_private() && !ipv4.is_loopback(), 3
IpAddr::V6(ipv6) => { } else if a.is_private() {
!ipv6.is_loopback() 2
&& !ipv6.is_unique_local() } else {
&& !ipv6.is_unicast_link_local() 0
} }
}) }
.chain([Ipv6Addr::UNSPECIFIED.into()]) IpAddr::V6(a) => {
.collect::<Vec<_>>(); if a.is_loopback() {
5
} else if a.is_unicast_link_local() {
4
} else {
1
}
}
});
let ip = if suggested_addrs.len() > 16 { let ip = if suggested_addrs.is_empty() {
prompt("Listen Address: ", parse_as::<IpAddr>("IP Address"), None).await?
} else if suggested_addrs.len() > 16 {
prompt( prompt(
&format!("Listen Address [{}]: ", suggested_addrs[0]), &format!("Listen Address [{}]: ", suggested_addrs[0]),
parse_as::<IpAddr>("IP Address"), parse_as::<IpAddr>("IP Address"),
@@ -533,7 +551,22 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
) )
.await? .await?
} else { } else {
*choose("Listen Address:", &suggested_addrs).await? *choose_custom_display("Listen Address:", &suggested_addrs, |a| match a {
a if a.is_loopback() => {
format!("{a} (Loopback Address: only use if planning to proxy traffic)")
}
IpAddr::V4(a) if a.is_private() => {
format!("{a} (Private Address: only available from Local Area Network)")
}
IpAddr::V6(a) if a.is_unicast_link_local() => {
format!(
"[{a}] (Private Address: only available from Local Area Network)"
)
}
IpAddr::V6(a) => format!("[{a}]"),
a => a.to_string(),
})
.await?
}; };
println!(concat!( println!(concat!(
@@ -638,8 +671,8 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
println!("Generating a random password..."); println!("Generating a random password...");
let params = SetPasswordParams { let params = SetPasswordParams {
password: base32::encode( password: base32::encode(
base32::Alphabet::Rfc4648 { padding: false }, base32::Alphabet::Rfc4648Lower { padding: false },
&rand::random::<[u8; 10]>(), &rand::random::<[u8; 16]>(),
), ),
}; };
ctx.call_remote::<TunnelContext>("auth.set-password", to_value(&params)?) ctx.call_remote::<TunnelContext>("auth.set-password", to_value(&params)?)

View File

@@ -95,16 +95,14 @@ pub async fn prompt_multiline<
Ok(res) Ok(res)
} }
pub async fn choose<'t, T: std::fmt::Display>( pub async fn choose_custom_display<'t, T: std::fmt::Display>(
prompt: &str, prompt: &str,
choices: &'t [T], choices: &'t [T],
mut display: impl FnMut(&T) -> String,
) -> Result<&'t T, Error> { ) -> Result<&'t T, Error> {
let mut io = DefaultIoDevices::default(); let mut io = DefaultIoDevices::default();
let style = r3bl_tui::readline_async::StyleSheet::default(); let style = r3bl_tui::readline_async::StyleSheet::default();
let string_choices = choices let string_choices = choices.into_iter().map(|c| display(c)).collect::<Vec<_>>();
.into_iter()
.map(|c| c.to_string())
.collect::<Vec<_>>();
let choice = r3bl_tui::readline_async::choose( let choice = r3bl_tui::readline_async::choose(
prompt, prompt,
string_choices.clone(), string_choices.clone(),
@@ -137,3 +135,10 @@ pub async fn choose<'t, T: std::fmt::Display>(
println!("{prompt} {choice}"); println!("{prompt} {choice}");
Ok(&choice) Ok(&choice)
} }
pub async fn choose<'t, T: std::fmt::Display>(
prompt: &str,
choices: &'t [T],
) -> Result<&'t T, Error> {
choose_custom_display(prompt, choices, |t| t.to_string()).await
}