diff --git a/src/interface/app.rs b/src/interface/app.rs index 80d26a02..dadde8a6 100644 --- a/src/interface/app.rs +++ b/src/interface/app.rs @@ -276,11 +276,18 @@ impl<'a> App<'a> { " Type: {} Algorithm: {} + Period: {} {} Counter: {} Pin: {} ", element.type_, element.algorithm, + element.period, + if element.period == 1u64 { + "second" + } else { + "seconds" + }, element .counter .map_or_else(|| String::from("N/A"), |e| e.to_string()), diff --git a/src/interface/handlers/main_window.rs b/src/interface/handlers/main_window.rs index 08b11817..ec9ca955 100644 --- a/src/interface/handlers/main_window.rs +++ b/src/interface/handlers/main_window.rs @@ -18,11 +18,10 @@ use super::{handle_exit, show_popup}; pub(super) fn main_handler(key_event: KeyEvent, app: &mut App) { match key_event.code { // exit application on ESC or Q - KeyCode::Esc | KeyCode::Char('q' | 'Q') => { - if app.focus != Focus::SearchBar { - handle_exit(app); - } + KeyCode::Esc | KeyCode::Char('q' | 'Q') if app.focus != Focus::SearchBar => { + handle_exit(app); } + // exit application on Ctrl-D KeyCode::Char('d' | 'D' | 'c') => { if key_event.modifiers == KeyModifiers::CONTROL { @@ -91,10 +90,8 @@ pub(super) fn main_handler(key_event: KeyEvent, app: &mut App) { ); } - KeyCode::Char('f' | 'F') => { - if key_event.modifiers == KeyModifiers::CONTROL { - app.focus = Focus::SearchBar; - } + KeyCode::Char('f' | 'F') if key_event.modifiers == KeyModifiers::CONTROL => { + app.focus = Focus::SearchBar; } KeyCode::Char('/') => app.focus = Focus::SearchBar, diff --git a/src/otp/algorithms/steam_otp_maker.rs b/src/otp/algorithms/steam_otp_maker.rs index 6fc13add..63d04369 100644 --- a/src/otp/algorithms/steam_otp_maker.rs +++ b/src/otp/algorithms/steam_otp_maker.rs @@ -6,9 +6,10 @@ use crate::otp::otp_error::OtpError; use super::totp_maker::totp; const STEAM_ALPHABET: &str = "23456789BCDFGHJKMNPQRTVWXY"; +const STEAM_OTP_PERIOD: u64 = 30; pub fn steam(secret: &str, algorithm: OTPAlgorithm, digits: usize) -> Result { - totp(secret, algorithm).map(|v| to_steam_string(v as usize, digits)) + totp(secret, algorithm, STEAM_OTP_PERIOD).map(|v| to_steam_string(v as usize, digits)) } fn to_steam_string(mut code: usize, digits: usize) -> String { diff --git a/src/otp/algorithms/totp_maker.rs b/src/otp/algorithms/totp_maker.rs index 7f447a2d..c82d4157 100644 --- a/src/otp/algorithms/totp_maker.rs +++ b/src/otp/algorithms/totp_maker.rs @@ -5,12 +5,12 @@ use crate::otp::otp_error::OtpError; use super::hotp_maker::hotp; -pub fn totp(secret: &str, algorithm: OTPAlgorithm) -> Result { +pub fn totp(secret: &str, algorithm: OTPAlgorithm, period: u64) -> Result { let time = SystemTime::now() .duration_since(SystemTime::UNIX_EPOCH) .unwrap() .as_secs(); - generate_totp(secret, algorithm, time, 30, 0) + generate_totp(secret, algorithm, time, period, 0) } fn generate_totp( @@ -25,7 +25,10 @@ fn generate_totp( #[cfg(test)] mod tests { - use crate::otp::{algorithms::totp_maker::generate_totp, otp_algorithm::OTPAlgorithm}; + use crate::otp::{ + algorithms::totp_maker::generate_totp, otp_algorithm::OTPAlgorithm, + otp_element::format_code, + }; #[test] fn test_totp() { @@ -34,4 +37,22 @@ mod tests { generate_totp("BASE32SECRET3232", OTPAlgorithm::Sha1, 0, 30, 0).unwrap() ); } + + #[test] + fn test_totp_with_60_seconds_period() { + // Arrange / Act + let raw_code = generate_totp( + "DEADBEEFDEADBEEFDEADBEEFDEADBEEF", + OTPAlgorithm::Sha1, + 1777799540, + 60, + 0, + ) + .unwrap(); + + let code = format_code(6, raw_code).unwrap(); + + // Assert + assert_eq!("295439", code) + } } diff --git a/src/otp/migrations/mod.rs b/src/otp/migrations/mod.rs index b773d913..18b04265 100644 --- a/src/otp/migrations/mod.rs +++ b/src/otp/migrations/mod.rs @@ -16,7 +16,7 @@ fn migrate_to_2(database: &mut OTPDatabase) -> color_eyre::Result<()> { pub fn migrate(database: &mut OTPDatabase) -> color_eyre::Result<()> { let mut binding = MIGRATIONS_LIST; let migrations = binding.as_mut(); - migrations.sort_unstable_by(|c1, c2| c1.to_version.cmp(&c2.to_version)); + migrations.sort_unstable_by_key(|c1| c1.to_version); for i in migrations { if database.version < i.to_version { // Do the migration diff --git a/src/otp/otp_element.rs b/src/otp/otp_element.rs index 5bd3e35e..a8ff6e60 100644 --- a/src/otp/otp_element.rs +++ b/src/otp/otp_element.rs @@ -191,7 +191,7 @@ impl OTPElement { match self.type_ { OTPType::Totp => { - let code = totp(&self.secret, self.algorithm)?; + let code = totp(&self.secret, self.algorithm, self.period)?; Ok(self.format_code(code)?) } @@ -227,15 +227,18 @@ impl OTPElement { } fn format_code(&self, value: u32) -> Result { - // Get the formatted code - let exponential = 10_u64 - .checked_pow(self.digits as u32) - .ok_or(OtpError::InvalidDigits)?; - let s = (value as u64 % exponential).to_string(); - Ok("0".repeat((self.digits as usize).saturating_sub(s.chars().count())) + s.as_str()) + format_code(self.digits, value) } } +pub(crate) fn format_code(digits: u64, value: u32) -> Result { + let exponential = 10_u64 + .checked_pow(digits as u32) + .ok_or(OtpError::InvalidDigits)?; + let s = (value as u64 % exponential).to_string(); + Ok("0".repeat((digits as usize).saturating_sub(s.chars().count())) + s.as_str()) +} + impl OTPElementBuilder { /// Makes the secret insertion case insensitive pub fn secret>(&mut self, value: VALUE) -> &mut Self {