diff --git a/Cargo.toml b/Cargo.toml index ec56a91..6a25c01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ categories = ["authentication", "web-programming"] features = [ "qr", "serde_support", "otpauth" ] [features] -default = [] +default = ["qr", "gen_secret"] otpauth = ["url", "urlencoding"] qr = ["qrcodegen", "image", "base64", "otpauth"] serde_support = ["serde"] diff --git a/src/lib.rs b/src/lib.rs index 74b5c40..329002d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -750,6 +750,31 @@ mod tests { assert_eq!(url.as_str(), "otpauth://totp/Github:constantoine%40github.com?issuer=Github&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=6&algorithm=SHA512"); } + #[test] + #[cfg(all(feature = "otpauth", feature = "gen_secret"))] + fn ttl() { + let secret = Secret::default(); + let totp_rfc = Rfc6238::with_defaults(secret.to_bytes().unwrap()).unwrap(); + let totp = TOTP::from_rfc6238(totp_rfc); + assert!(totp.is_ok()); + } + + #[test] + #[cfg(feature = "otpauth")] + fn ttl_ok() { + let totp = TOTP::new( + Algorithm::SHA512, + 6, + 1, + 1, + "TestSecretSuperSecret", + Some("Github".to_string()), + "constantoine@github.com".to_string(), + ) + .unwrap(); + assert!(totp.ttl().is_ok()); + } + #[test] #[cfg(not(feature = "otpauth"))] fn returns_base32() { @@ -889,6 +914,24 @@ mod tests { assert_eq!(totp.step, 60); } + #[test] + #[cfg(feature = "otpauth")] + fn from_url_query_sha512() { + let totp = TOTP::>::from_url("otpauth://totp/GitHub:test?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=8&period=60&algorithm=SHA512").unwrap(); + assert_eq!( + totp.secret, + base32::decode( + base32::Alphabet::RFC4648 { padding: false }, + "KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ" + ) + .unwrap() + ); + assert_eq!(totp.algorithm, Algorithm::SHA512); + assert_eq!(totp.digits, 8); + assert_eq!(totp.skew, 1); + assert_eq!(totp.step, 60); + } + #[test] #[cfg(feature = "otpauth")] fn from_url_to_url() { @@ -906,6 +949,24 @@ mod tests { assert_eq!(totp.get_url(), totp_bis.get_url()); } + #[test] + #[cfg(feature = "otpauth")] + fn from_url_unknown_param() { + let totp = TOTP::>::from_url("otpauth://totp/GitHub:test?secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=8&period=60&algorithm=SHA256&foo=bar").unwrap(); + assert_eq!( + totp.secret, + base32::decode( + base32::Alphabet::RFC4648 { padding: false }, + "KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ" + ) + .unwrap() + ); + assert_eq!(totp.algorithm, Algorithm::SHA256); + assert_eq!(totp.digits, 8); + assert_eq!(totp.skew, 1); + assert_eq!(totp.step, 60); + } + #[test] #[cfg(feature = "otpauth")] fn from_url_issuer_special() { @@ -943,6 +1004,24 @@ mod tests { assert_eq!(totp.issuer.unwrap(), "GitHub"); } + #[test] + #[cfg(feature = "otpauth")] + fn from_url_wrong_scheme() { + let totp = TOTP::>::from_url("http://totp/GitHub:test?issuer=GitHub&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=8&period=60&algorithm=SHA256"); + assert!(totp.is_err()); + let err = totp.unwrap_err(); + assert!(matches!(err, TotpUrlError::Scheme(_))); + } + + #[test] + #[cfg(feature = "otpauth")] + fn from_url_wrong_algo() { + let totp = TOTP::>::from_url("otpauth://totp/GitHub:test?issuer=GitHub&secret=KRSXG5CTMVRXEZLUKN2XAZLSKNSWG4TFOQ&digits=8&period=60&algorithm=MD5"); + assert!(totp.is_err()); + let err = totp.unwrap_err(); + assert!(matches!(err, TotpUrlError::Algorithm(_))); + } + #[test] #[cfg(feature = "otpauth")] fn from_url_query_different_issuers() { @@ -981,4 +1060,21 @@ mod tests { "025809c9db9c2c918930e018549c90929a083ee757156737812bad40ded64312c1526c73d8f2f59d5c203b97141ddfc331b1192e234f4f43257f50a6d05e382f" ); } + + #[test] + #[cfg(feature = "qr")] + fn generates_qr_ok() { + let totp = TOTP::new( + Algorithm::SHA1, + 6, + 1, + 1, + "TestSecretSuperSecret", + Some("Github".to_string()), + "constantoine@github.com".to_string(), + ) + .unwrap(); + let qr = totp.get_qr(); + assert!(qr.is_ok()); + } } diff --git a/src/rfc.rs b/src/rfc.rs index 94dda8b..34204a7 100644 --- a/src/rfc.rs +++ b/src/rfc.rs @@ -313,6 +313,21 @@ mod tests { assert!(totp.is_ok()); } + #[test] + #[cfg(feature = "otpauth")] + fn rfc_with_default_set_values() { + let mut rfc = Rfc6238::with_defaults(GOOD_SECRET.to_string()).unwrap(); + let ok = rfc.digits(8); + assert!(ok.is_ok()); + assert_eq!(rfc.account_name, ""); + assert_eq!(rfc.issuer, Some("".to_string())); + rfc.issuer("Github".to_string()); + rfc.account_name("constantoine".to_string()); + assert_eq!(rfc.account_name, "constantoine"); + assert_eq!(rfc.issuer, Some("Github".to_string())); + assert_eq!(rfc.digits, 8) + } + #[test] #[cfg(not(feature = "otpauth"))] fn rfc_with_default_set_values() { @@ -325,4 +340,25 @@ mod tests { assert!(ok.is_ok()); assert_eq!(rfc.digits, 8) } + + #[test] + #[cfg(not(feature = "otpauth"))] + fn digits_error() { + let error = crate::Rfc6238Error::InvalidDigits(9); + assert_eq!( + error.to_string(), + "Implementations MUST extract a 6-digit code at a minimum and possibly 7 and 8-digit code. 9 digits is not allowed".to_string() + ) + } + + #[test] + #[cfg(not(feature = "otpauth"))] + fn secret_length_error() { + let error = Rfc6238Error::SecretTooSmall(120); + assert_eq!( + error.to_string(), + "The length of the shared secret MUST be at least 128 bits. 120 bits is not enough" + .to_string() + ) + } } diff --git a/src/secret.rs b/src/secret.rs index 115566e..6edde97 100644 --- a/src/secret.rs +++ b/src/secret.rs @@ -237,9 +237,33 @@ mod tests { #[test] #[cfg(feature = "gen_secret")] fn secret_gen_secret() { - match Secret::generate_secret() { - Secret::Raw(secret) => assert_eq!(secret.len(), 20), - Secret::Encoded(_) => panic!("should be raw"), - } + let sec = Secret::generate_secret(); + + assert!(matches!(sec, Secret::Raw(_))); + assert_eq!(sec.to_bytes().unwrap().len(), 20); + } + + #[test] + #[cfg(feature = "gen_secret")] + fn secret_gen_default() { + let sec = Secret::default(); + + assert!(matches!(sec, Secret::Raw(_))); + assert_eq!(sec.to_bytes().unwrap().len(), 20); + } + + #[test] + #[cfg(feature = "gen_secret")] + fn secret_empty() { + let non_ascii = vec![240, 159, 146, 150]; + let sec = Secret::Encoded(std::str::from_utf8(&non_ascii).unwrap().to_owned()); + + let to_r = sec.to_raw(); + + assert!(to_r.is_err()); + + let to_b = sec.to_bytes(); + + assert!(to_b.is_err()); } }