The return of otpauth
Signed-off-by: constantoine <cleo.rebert@gmail.com>
This commit is contained in:
parent
eba97c0ffa
commit
ff6f562ab7
|
@ -16,7 +16,8 @@ features = [ "qr", "serde_support" ]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
qr = ["qrcodegen", "image", "base64"]
|
otpauth = ["url"]
|
||||||
|
qr = ["qrcodegen", "image", "base64", "otpauth"]
|
||||||
serde_support = ["serde"]
|
serde_support = ["serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -25,7 +26,7 @@ sha2 = "~0.10.2"
|
||||||
sha-1 = "~0.10.0"
|
sha-1 = "~0.10.0"
|
||||||
hmac = "~0.12.1"
|
hmac = "~0.12.1"
|
||||||
base32 = "~0.4"
|
base32 = "~0.4"
|
||||||
url = "2.2.2"
|
url = { version = "2.2.2", optional = true }
|
||||||
constant_time_eq = "~0.2.1"
|
constant_time_eq = "~0.2.1"
|
||||||
qrcodegen = { version = "~1.8", optional = true }
|
qrcodegen = { version = "~1.8", optional = true }
|
||||||
image = { version = "~0.24.2", features = ["png"], optional = true, default-features = false}
|
image = { version = "~0.24.2", features = ["png"], optional = true, default-features = false}
|
||||||
|
|
13
README.md
13
README.md
|
@ -10,9 +10,11 @@ Be aware that some authenticator apps will accept the `SHA256` and `SHA512` algo
|
||||||
## Features
|
## Features
|
||||||
---
|
---
|
||||||
### qr
|
### 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
|
### 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
|
## How to use
|
||||||
---
|
---
|
||||||
|
@ -36,8 +38,6 @@ fn main() {
|
||||||
Some("Github".to_string()),
|
Some("Github".to_string()),
|
||||||
"constantoine@github.com".to_string(),
|
"constantoine@github.com".to_string(),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
let url = totp.get_url();
|
|
||||||
println!("{}", url);
|
|
||||||
let token = totp.generate_current().unwrap();
|
let token = totp.generate_current().unwrap();
|
||||||
println!("{}", token);
|
println!("{}", token);
|
||||||
}
|
}
|
||||||
|
@ -82,8 +82,9 @@ features = ["serde_support"]
|
||||||
|
|
||||||
Add it to your `Cargo.toml`:
|
Add it to your `Cargo.toml`:
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies.totp-rs]
|
||||||
totp-rs = "^2.0"
|
version = "^2.0"
|
||||||
|
features = ["otpauth"]
|
||||||
```
|
```
|
||||||
You can then do something like:
|
You can then do something like:
|
||||||
```Rust
|
```Rust
|
||||||
|
|
16
src/lib.rs
16
src/lib.rs
|
@ -21,8 +21,6 @@
|
||||||
//! Some("Github".to_string()),
|
//! Some("Github".to_string()),
|
||||||
//! "constantoine@github.com".to_string(),
|
//! "constantoine@github.com".to_string(),
|
||||||
//! ).unwrap();
|
//! ).unwrap();
|
||||||
//! let url = totp.get_url();
|
|
||||||
//! println!("{}", url);
|
|
||||||
//! let token = totp.generate_current().unwrap();
|
//! let token = totp.generate_current().unwrap();
|
||||||
//! println!("{}", token);
|
//! println!("{}", token);
|
||||||
//! ```
|
//! ```
|
||||||
|
@ -40,6 +38,8 @@
|
||||||
//! Some("Github".to_string()),
|
//! Some("Github".to_string()),
|
||||||
//! "constantoine@github.com".to_string(),
|
//! "constantoine@github.com".to_string(),
|
||||||
//! ).unwrap();
|
//! ).unwrap();
|
||||||
|
//! let url = totp.get_url();
|
||||||
|
//! println!("{}", url);
|
||||||
//! let code = totp.get_qr().unwrap();
|
//! let code = totp.get_qr().unwrap();
|
||||||
//! println!("{}", code);
|
//! println!("{}", code);
|
||||||
//! # }
|
//! # }
|
||||||
|
@ -55,6 +55,7 @@ use core::fmt;
|
||||||
#[cfg(feature = "qr")]
|
#[cfg(feature = "qr")]
|
||||||
use {base64, image::Luma, qrcodegen};
|
use {base64, image::Luma, qrcodegen};
|
||||||
|
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
use url::{Host, ParseError, Url};
|
use url::{Host, ParseError, Url};
|
||||||
|
|
||||||
use hmac::Mac;
|
use hmac::Mac;
|
||||||
|
@ -116,6 +117,7 @@ fn system_time() -> Result<u64, SystemTimeError> {
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq)]
|
#[derive(Debug, Eq, PartialEq)]
|
||||||
pub enum TotpUrlError {
|
pub enum TotpUrlError {
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
Url(ParseError),
|
Url(ParseError),
|
||||||
Scheme,
|
Scheme,
|
||||||
Host,
|
Host,
|
||||||
|
@ -248,6 +250,7 @@ impl<T: AsRef<[u8]>> TOTP<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a TOTP from the standard otpauth URL
|
/// Generate a TOTP from the standard otpauth URL
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
pub fn from_url<S: AsRef<str>>(url: S) -> Result<TOTP<Vec<u8>>, TotpUrlError> {
|
pub fn from_url<S: AsRef<str>>(url: S) -> Result<TOTP<Vec<u8>>, TotpUrlError> {
|
||||||
let url = Url::parse(url.as_ref()).map_err(|err| TotpUrlError::Url(err))?;
|
let url = Url::parse(url.as_ref()).map_err(|err| TotpUrlError::Url(err))?;
|
||||||
if url.scheme() != "otpauth" {
|
if url.scheme() != "otpauth" {
|
||||||
|
@ -322,6 +325,7 @@ impl<T: AsRef<[u8]>> TOTP<T> {
|
||||||
///
|
///
|
||||||
/// Label and issuer will be URL-encoded if needed be
|
/// Label and issuer will be URL-encoded if needed be
|
||||||
/// Secret will be base 32'd without padding, as per RFC.
|
/// Secret will be base 32'd without padding, as per RFC.
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
pub fn get_url(&self) -> String {
|
pub fn get_url(&self) -> String {
|
||||||
let label: String;
|
let label: String;
|
||||||
let account_name: String = url::form_urlencoded::byte_serialize(self.account_name.as_bytes()).collect();
|
let account_name: String = url::form_urlencoded::byte_serialize(self.account_name.as_bytes()).collect();
|
||||||
|
@ -481,6 +485,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn url_for_secret_matches_sha1_without_issuer() {
|
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 totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret", None, "constantoine@github.com".to_string()).unwrap();
|
||||||
let url = totp.get_url();
|
let url = totp.get_url();
|
||||||
|
@ -488,6 +493,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn url_for_secret_matches_sha1() {
|
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 totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, "TestSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
||||||
let url = totp.get_url();
|
let url = totp.get_url();
|
||||||
|
@ -495,6 +501,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn url_for_secret_matches_sha256() {
|
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 totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, "TestSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
||||||
let url = totp.get_url();
|
let url = totp.get_url();
|
||||||
|
@ -502,6 +509,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn url_for_secret_matches_sha512() {
|
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 totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, "TestSecret", Some("Github".to_string()), "constantoine@github.com".to_string()).unwrap();
|
||||||
let url = totp.get_url();
|
let url = totp.get_url();
|
||||||
|
@ -566,6 +574,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn from_url_err() {
|
fn from_url_err() {
|
||||||
assert!(TOTP::<Vec<u8>>::from_url("otpauth://hotp/123").is_err());
|
assert!(TOTP::<Vec<u8>>::from_url("otpauth://hotp/123").is_err());
|
||||||
assert!(TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test").is_err());
|
assert!(TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test").is_err());
|
||||||
|
@ -573,6 +582,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn from_url_default() {
|
fn from_url_default() {
|
||||||
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?secret=ABC").unwrap();
|
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?secret=ABC").unwrap();
|
||||||
assert_eq!(totp.secret, base32::decode(base32::Alphabet::RFC4648 { padding: false }, "ABC").unwrap());
|
assert_eq!(totp.secret, base32::decode(base32::Alphabet::RFC4648 { padding: false }, "ABC").unwrap());
|
||||||
|
@ -583,6 +593,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn from_url_query() {
|
fn from_url_query() {
|
||||||
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?secret=ABC&digits=8&period=60&algorithm=SHA256").unwrap();
|
let totp = TOTP::<Vec<u8>>::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());
|
assert_eq!(totp.secret, base32::decode(base32::Alphabet::RFC4648 { padding: false }, "ABC").unwrap());
|
||||||
|
@ -593,6 +604,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(feature = "otpauth")]
|
||||||
fn from_url_query_different_issuers() {
|
fn from_url_query_different_issuers() {
|
||||||
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?issuer=Gitlab&secret=ABC&digits=8&period=60&algorithm=SHA256");
|
let totp = TOTP::<Vec<u8>>::from_url("otpauth://totp/GitHub:test?issuer=Gitlab&secret=ABC&digits=8&period=60&algorithm=SHA256");
|
||||||
assert_eq!(totp.is_err(), true);
|
assert_eq!(totp.is_err(), true);
|
||||||
|
|
Loading…
Reference in New Issue