use crossterm::event::
{
KeyCode,
KeyEvent,
KeyModifiers,
};
use crate::network::client::TofuRequest;
use super::
{
theme,
state::App,
};
pub const CHALLENGE: &str = "yes";
#[derive(PartialEq)]
pub enum Stage
{
Warn, Confirm, }
pub struct Prompt
{
pub host: String,
pub hash: String,
pub pinned: Option<String>, pub mismatch: bool, pub accept: bool, pub stage: Stage,
pub typed: String, pub wrong: bool, request: TofuRequest,
}
impl Prompt
{
pub fn new(request: TofuRequest) -> Self
{
Self
{
host: request.host.clone(),
hash: request.hash.clone(),
pinned: request.pinned.clone(),
mismatch: request.mismatch,
accept: false,
stage: Stage::Warn,
typed: String::new(),
wrong: false,
request,
}
}
pub fn title(&self) -> &'static str
{
match (self.mismatch, &self.stage)
{
(_, Stage::Confirm) => " Confirm the new server key ",
(true, Stage::Warn) => " Server identity changed ",
(false, Stage::Warn) => " Unknown server identity ",
}
}
pub fn fingerprint(&self) -> Vec<String>
{
Self::group(&self.hash)
}
pub fn pinned_fingerprint(&self) -> Vec<String>
{
self.pinned.as_deref().map(Self::group).unwrap_or_default()
}
fn group(hash: &str) -> Vec<String>
{
let groups = hash.as_bytes()
.chunks(8)
.map(|group| String::from_utf8_lossy(group).into_owned())
.collect::<Vec<String>>();
groups.chunks(4).map(|row| row.join(" ")).collect()
}
}
pub fn handle_key(app: &mut App, key: KeyEvent)
{
let Some(prompt) = app.tofu.as_mut() else { return };
if key.code == KeyCode::Esc
{
answer(app, false);
return;
}
match prompt.stage
{
Stage::Warn => match key.code
{
KeyCode::Left | KeyCode::Right | KeyCode::Tab | KeyCode::BackTab => prompt.accept = !prompt.accept,
KeyCode::Enter =>
{
let accept = prompt.accept;
if accept && prompt.mismatch
{
prompt.stage = Stage::Confirm;
prompt.typed.clear();
prompt.wrong = false;
} else { answer(app, accept); }
},
_ => {},
},
Stage::Confirm => match key.code
{
KeyCode::Char(character)
if character.is_ascii_alphabetic()
&& prompt.typed.chars().count() < CHALLENGE.chars().count()
&& !key.modifiers.intersects(KeyModifiers::CONTROL | KeyModifiers::ALT) =>
{
prompt.typed.push(character.to_ascii_lowercase());
prompt.wrong = false;
},
KeyCode::Backspace =>
{
prompt.typed.pop();
prompt.wrong = false;
},
KeyCode::Left | KeyCode::BackTab =>
{
prompt.stage = Stage::Warn;
prompt.accept = false;
prompt.typed.clear();
prompt.wrong = false;
},
KeyCode::Enter =>
{
if prompt.typed == CHALLENGE { answer(app, true); } else { prompt.wrong = true; }
},
_ => {},
},
}
app.dirty = true;
}
fn answer(app: &mut App, accept: bool)
{
let Some(prompt) = app.tofu.take() else { return };
let _ = prompt.request.reply.send(accept);
if accept
{
app.push_styled(format!("Server identity for {} accepted and saved. Reconnecting...", prompt.host),
theme::OK);
}
app.dirty = true;
}