Improve TUI OTP readability

This commit is contained in:
Hermes Agent
2026-08-10 12:22:53 +00:00
parent d3427f3be6
commit cec1cc3500
6 changed files with 433 additions and 65 deletions

View File

@@ -433,17 +433,18 @@ pub struct OtpCodeOutcome {
///
/// Frontends use this value instead of deriving TOTP periods or inferring HOTP
/// behavior from display strings. A timed code carries its exclusive Unix-time
/// boundary, while a counter-based code identifies the committed HOTP counter.
/// boundary and complete period for progress presentation, while a
/// counter-based code identifies the committed HOTP counter.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OtpCodeValidity {
Timed { valid_until: u64 },
Timed { valid_until: u64, period: u64 },
CounterBased { counter: u64 },
}
impl OtpCodeValidity {
pub fn valid_until(self) -> Option<u64> {
match self {
Self::Timed { valid_until } => Some(valid_until),
Self::Timed { valid_until, .. } => Some(valid_until),
Self::CounterBased { .. } => None,
}
}
@@ -455,6 +456,13 @@ impl OtpCodeValidity {
}
}
pub fn period(self) -> Option<u64> {
match self {
Self::Timed { period, .. } => Some(period),
Self::CounterBased { .. } => None,
}
}
pub fn remaining_at(self, unix_seconds: u64) -> Option<u64> {
self.valid_until()
.map(|valid_until| valid_until.saturating_sub(unix_seconds))
@@ -695,6 +703,7 @@ impl<'a> OtpService<'a> {
.checked_add(1)
.and_then(|counter| counter.checked_mul(period))
.ok_or(OtpError::CounterOverflow)?,
period,
},
});
}
@@ -743,18 +752,21 @@ impl<'a> OtpService<'a> {
committer: &mut impl EntryCommitter,
) -> Result<OtpCodeOutcome, OtpError> {
match uri.kind() {
OtpKind::Totp => Ok(OtpCodeOutcome {
code: uri.code_at(unix_seconds)?,
validity: OtpCodeValidity::Timed {
valid_until: {
let period = uri.period().ok_or(OtpError::NotTotp)?;
(unix_seconds / period)
.checked_add(1)
.and_then(|counter| counter.checked_mul(period))
.ok_or(OtpError::CounterOverflow)?
OtpKind::Totp => {
let period = uri.period().ok_or(OtpError::NotTotp)?;
Ok(OtpCodeOutcome {
code: uri.code_at(unix_seconds)?,
validity: OtpCodeValidity::Timed {
valid_until: {
(unix_seconds / period)
.checked_add(1)
.and_then(|counter| counter.checked_mul(period))
.ok_or(OtpError::CounterOverflow)?
},
period,
},
},
}),
})
}
OtpKind::Hotp => {
let (counter, incremented) = uri.incremented_hotp()?;
let code = incremented.code_for_counter(counter)?;