diff --git a/Cargo.toml b/Cargo.toml index e150eee..93bd8bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,8 @@ features = [ "qr", "serde_support" ] [features] default = [] -qr = ["qrcodegen", "image", "base64"] +otpauth = ["url"] +qr = ["qrcodegen", "image", "base64", "otpauth"] serde_support = ["serde"] [dependencies] @@ -25,7 +26,7 @@ sha2 = "~0.10.2" sha-1 = "~0.10.0" hmac = "~0.12.1" base32 = "~0.4" -url = "2.2.2" +url = { version = "2.2.2", optional = true } constant_time_eq = "~0.2.1" qrcodegen = { version = "~1.8", optional = true } image = { version = "~0.24.2", features = ["png"], optional = true, default-features = false} diff --git a/README.md b/README.md index 4f6ee51..8ef67f5 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,11 @@ Be aware that some authenticator apps will accept the `SHA256` and `SHA512` algo ## Features --- ### qr -With optional feature "qr", you can use it to generate a base64 png qrcode +With optional feature "qr", you can use it to generate a base64 png qrcode. This will enable feature `otpauth` +### otpauth +With optional feature "otpauth", support parsing the TOTP parameters from an `otpauth` URL, and generating an `otpauth` URL ### serde_support -With optional feature "serde_support", library-defined types will be Deserialize-able and Serialize-able +With optional feature "serde_support", library-defined types `TOTP` and `Algorithm` and will be Deserialize-able and Serialize-able ## How to use --- @@ -36,8 +38,6 @@ fn main() { Some("Github".to_string()), "constantoine@github.com".to_string(), ).unwrap(); - let url = totp.get_url(); - println!("{}", url); let token = totp.generate_current().unwrap(); println!("{}", token); } @@ -82,8 +82,9 @@ features = ["serde_support"] Add it to your `Cargo.toml`: ```toml -[dependencies] -totp-rs = "^2.0" +[dependencies.totp-rs] +version = "^2.0" +features = ["otpauth"] ``` You can then do something like: ```Rust diff --git a/src/lib.rs b/src/lib.rs index 4dd8d5d..92fce39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,8 +21,6 @@ //! Some("Github".to_string()), //! "constantoine@github.com".to_string(), //! ).unwrap(); -//! let url = totp.get_url(); -//! println!("{}", url); //! let token = totp.generate_current().unwrap(); //! println!("{}", token); //! ``` @@ -40,6 +38,8 @@ //! Some("Github".to_string()), //! "constantoine@github.com".to_string(), //! ).unwrap(); +//! let url = totp.get_url(); +//! println!("{}", url); //! let code = totp.get_qr().unwrap(); //! println!("{}", code); //! # } @@ -55,6 +55,7 @@ use core::fmt; #[cfg(feature = "qr")] use {base64, image::Luma, qrcodegen}; +#[cfg(feature = "otpauth")] use url::{Host, ParseError, Url}; use hmac::Mac; @@ -116,6 +117,7 @@ fn system_time() -> Result { #[derive(Debug, Eq, PartialEq)] pub enum TotpUrlError { + #[cfg(feature = "otpauth")] Url(ParseError), Scheme, Host, @@ -248,6 +250,7 @@ impl> TOTP { } /// Generate a TOTP from the standard otpauth URL + #[cfg(feature = "otpauth")] pub fn from_url>(url: S) -> Result>, TotpUrlError> { let url = Url::parse(url.as_ref()).map_err(|err| TotpUrlError::Url(err))?; if url.scheme() != "otpauth" { @@ -322,6 +325,7 @@ impl> TOTP { /// /// Label and issuer will be URL-encoded if needed be /// Secret will be base 32'd without padding, as per RFC. + #[cfg(feature = "otpauth")] pub fn get_url(&self) -> String { let label: String; let account_name: String = url::form_urlencoded::byte_serialize(self.account_name.as_bytes()).collect(); @@ -481,6 +485,7 @@ mod tests { } #[test] + #[cfg(feature = "otpauth")] fn url_for_secret_matches_sha1_without_issuer() { let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret", None, "constantoine@github.com".to_string()).unwrap(); let url = totp.get_url(); @@ -488,6 +493,7 @@ mod tests { } #[test] + #[cfg(feature = "otpauth")] fn url_for_secret_matches_sha1() { let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap(); let url = totp.get_url(); @@ -495,6 +501,7 @@ mod tests { } #[test] + #[cfg(feature = "otpauth")] fn url_for_secret_matches_sha256() { let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap(); let url = totp.get_url(); @@ -502,6 +509,7 @@ mod tests { } #[test] + #[cfg(feature = "otpauth")] fn url_for_secret_matches_sha512() { let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, "TestSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap(); let url = totp.get_url(); @@ -566,6 +574,7 @@ mod tests { } #[test] + #[cfg(feature = "otpauth")] fn from_url_err() { assert!(TOTP::>::from_url("otpauth://hotp/123").is_err()); assert!(TOTP::>::from_url("otpauth://totp/GitHub:test").is_err()); @@ -573,6 +582,7 @@ mod tests { } #[test] + #[cfg(feature = "otpauth")] fn from_url_default() { let totp = TOTP::>::from_url("otpauth://totp/GitHub:test?secret=ABC").unwrap(); assert_eq!(totp.secret, base32::decode(base32::Alphabet::RFC4648 { padding: false }, "ABC").unwrap()); @@ -583,6 +593,7 @@ mod tests { } #[test] + #[cfg(feature = "otpauth")] fn from_url_query() { let totp = TOTP::>::from_url("otpauth://totp/GitHub:test?secret=ABC&digits=8&period=60&algorithm=SHA256").unwrap(); assert_eq!(totp.secret, base32::decode(base32::Alphabet::RFC4648 { padding: false }, "ABC").unwrap()); @@ -593,6 +604,7 @@ mod tests { } #[test] + #[cfg(feature = "otpauth")] fn from_url_query_different_issuers() { let totp = TOTP::>::from_url("otpauth://totp/GitHub:test?issuer=Gitlab&secret=ABC&digits=8&period=60&algorithm=SHA256"); assert_eq!(totp.is_err(), true);