use std::rc::Rc; use cli_clipboard; use cursive::view::{Nameable, Resizable}; use cursive::views::{Button, Dialog, DummyView, EditView, LinearLayout, TextView}; use cursive::Cursive; use ovunto_security::constants::TOTP_ISSUER; use ovunto_security::{TOTPGenerator, TOTP}; use crate::screen::Screen; use crate::{get_content, info, ClientBox}; pub struct TOTPScreen { client: ClientBox, totp_generator: Option, label: Option, callback: Option, } impl TOTPScreen { pub fn with_label(mut self, label: String) -> Self { self.label = Some(label); self } pub fn with_generator(mut self, totp_generator: TOTPGenerator) -> Self { self.totp_generator = Some(totp_generator); self } pub fn and_then(mut self, callback: fn(&mut Cursive, &ClientBox, bool)) -> Self { self.callback = Some(callback); self } } impl Screen for TOTPScreen { const NAME: &'static str = "totp"; fn construct(client: ClientBox) -> Self { Self { client, totp_generator: None, callback: None, label: None, } } fn render_dialog(&self) -> Dialog { let totp_generator = self.totp_generator.expect("No TOTP Generator provided"); let label = self.label.as_ref().expect("No label provided"); let callback = self.callback.expect("No callback provided"); fn verify( s: &mut Cursive, cl: &ClientBox, totp: &str, totp_generator: TOTPGenerator, callback: fn(&mut Cursive, &ClientBox, bool), ) { match totp.parse::() { Ok(totp) => { if totp_generator.verify(totp) { s.pop_layer(); callback(s, cl, true); } else { info(s, "Invalid code, please retry"); } } Err(err) => info(s, &err), } } let cl = Rc::clone(&self.client); let totp_input = EditView::new() .max_content_width(6) .on_submit(move |s, totp| verify(s, &cl, totp, totp_generator, callback)) .with_name("totp") .fixed_width(20); let cl = Rc::clone(&self.client); let cl2 = Rc::clone(&self.client); let totp_uri = totp_generator.get_uri(label.clone(), TOTP_ISSUER.to_string()); Dialog::around( LinearLayout::vertical() .child(TextView::new(format!( "Please add this in your authentication app:\n{}", totp_uri.clone() ))) .child(Button::new("Copy", move |_| { cli_clipboard::set_contents(totp_uri.clone()).unwrap() })) .child(DummyView) .child(TextView::new("Then enter code to confirm:")) .child(totp_input), ) .title("Two Factor Authentication") .button("Cancel", move |s| { s.pop_layer(); callback(s, &cl, false); }) .button("Next", move |s| { let totp = &s.call_on_name("totp", get_content).unwrap(); verify(s, &cl2, totp, totp_generator, callback); }) } }