Show OTP code validity in frontends
This commit is contained in:
@@ -29,7 +29,7 @@ use ironstorage::{
|
||||
mutation::{
|
||||
MutationError, NoGitTreeCommitter, TreeCommit, TreeCommitError, TreeCommitter, TreeMutator,
|
||||
},
|
||||
otp::{OtpError, OtpInput, OtpService},
|
||||
otp::{OtpCodeOutcome, OtpCodeValidity, OtpError, OtpInput, OtpService},
|
||||
presentation::{
|
||||
ClipboardError, ClipboardTimeout, ClipboardWait, NativeClipboardManager, QrError, QrMatrix,
|
||||
},
|
||||
@@ -837,6 +837,7 @@ fn execute_otp<B: SecretStoreBackend, P: CliPresentation, I: OtpInteraction, O:
|
||||
Err(error) => return operation_error(stderr, error),
|
||||
};
|
||||
if request.clipboard {
|
||||
write_otp_validity(&outcome, timestamp, stderr)?;
|
||||
match presentation.clipboard(
|
||||
outcome.code(),
|
||||
config.clipboard_timeout(),
|
||||
@@ -851,6 +852,7 @@ fn execute_otp<B: SecretStoreBackend, P: CliPresentation, I: OtpInteraction, O:
|
||||
.write_all(outcome.code().expose())
|
||||
.and_then(|()| stdout.write_all(b"\n"))
|
||||
.map_err(|_| ())?;
|
||||
write_otp_validity(&outcome, timestamp, stderr)?;
|
||||
Ok(EXIT_SUCCESS)
|
||||
}
|
||||
}
|
||||
@@ -969,6 +971,25 @@ fn execute_otp<B: SecretStoreBackend, P: CliPresentation, I: OtpInteraction, O:
|
||||
}
|
||||
}
|
||||
|
||||
fn write_otp_validity(
|
||||
outcome: &OtpCodeOutcome,
|
||||
unix_seconds: u64,
|
||||
feedback: &mut dyn Write,
|
||||
) -> Result<(), ()> {
|
||||
match outcome.validity() {
|
||||
OtpCodeValidity::Timed { .. } => {
|
||||
let remaining = outcome.remaining_at(unix_seconds).unwrap_or_default();
|
||||
let unit = if remaining == 1 { "second" } else { "seconds" };
|
||||
writeln!(feedback, "TOTP code is valid for {remaining} {unit}.").map_err(|_| ())
|
||||
}
|
||||
OtpCodeValidity::CounterBased { counter } => writeln!(
|
||||
feedback,
|
||||
"HOTP counter {counter} is counter-based and has no time expiry."
|
||||
)
|
||||
.map_err(|_| ()),
|
||||
}
|
||||
}
|
||||
|
||||
trait OtpInteraction {
|
||||
fn standard_input_is_terminal(&self) -> bool;
|
||||
|
||||
@@ -2169,13 +2190,43 @@ mod tests {
|
||||
assert_eq!(code.len(), 6);
|
||||
assert!(code.iter().all(u8::is_ascii_digit));
|
||||
assert!(!stdout.windows(code.len()).any(|part| part == code));
|
||||
assert!(stderr.is_empty());
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&stderr),
|
||||
"TOTP code is valid for 1 second.\n"
|
||||
);
|
||||
assert_eq!(fs::read(vault.join("otp/totp.gpg"))?, ciphertext_before);
|
||||
assert_eq!(fs::read(&git_config)?, config_before);
|
||||
let git = GitRepository::open(&repository, identity)?;
|
||||
assert_eq!(git.log(None)?.len(), commits_before);
|
||||
|
||||
stdout.clear();
|
||||
stderr.clear();
|
||||
assert_eq!(
|
||||
execute_secure_with_services(
|
||||
&config,
|
||||
&CommandRequest::Otp(OtpRequest::Code(OtpCodeRequest {
|
||||
entry: "otp/totp".to_owned(),
|
||||
clipboard: false,
|
||||
})),
|
||||
&mut secrets,
|
||||
&mut presentation,
|
||||
&mut interaction,
|
||||
|| Ok(59),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
)
|
||||
.expect("memory output cannot fail"),
|
||||
EXIT_SUCCESS
|
||||
);
|
||||
assert_eq!(stdout.len(), 7);
|
||||
assert!(stdout[..6].iter().all(u8::is_ascii_digit));
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&stderr),
|
||||
"TOTP code is valid for 1 second.\n"
|
||||
);
|
||||
|
||||
stdout.clear();
|
||||
stderr.clear();
|
||||
let uri = fs::read(fixtures.join("expected/basic/otp/totp.txt"))?;
|
||||
let uri = uri
|
||||
.split(|byte| *byte == b'\n')
|
||||
@@ -2203,6 +2254,7 @@ mod tests {
|
||||
assert!(stderr.is_empty());
|
||||
|
||||
stdout.clear();
|
||||
stderr.clear();
|
||||
assert_eq!(
|
||||
execute_secure_with_services(
|
||||
&config,
|
||||
@@ -2321,6 +2373,7 @@ mod tests {
|
||||
);
|
||||
|
||||
stdout.clear();
|
||||
stderr.clear();
|
||||
assert_eq!(
|
||||
execute_secure_with_services(
|
||||
&config,
|
||||
@@ -2340,6 +2393,10 @@ mod tests {
|
||||
);
|
||||
assert_eq!(stdout.len(), 9);
|
||||
assert!(stdout[..8].iter().all(u8::is_ascii_digit));
|
||||
assert_eq!(
|
||||
String::from_utf8_lossy(&stderr),
|
||||
"HOTP counter 1 is counter-based and has no time expiry.\n"
|
||||
);
|
||||
|
||||
let repository = Repository::open(&vault)?;
|
||||
let keys = ironstorage::crypto::KeyStore::load(fixtures.join("keys"))?;
|
||||
@@ -2366,7 +2423,6 @@ mod tests {
|
||||
&mut secrets,
|
||||
)?;
|
||||
assert!(hotp.expose().windows(9).any(|part| part == b"counter=1"));
|
||||
assert!(stderr.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user