Merge pull request #6 from marknijboer/master
Added unit tests, replaced String for &str and removed println!
This commit is contained in:
commit
a8c78041b3
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "totp-rs"
|
name = "totp-rs"
|
||||||
version = "0.3.2"
|
version = "0.4.0"
|
||||||
authors = ["Cleo Rebert <cleo.rebert@gmail.com>"]
|
authors = ["Cleo Rebert <cleo.rebert@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
10
README.md
10
README.md
|
@ -7,14 +7,13 @@ This library permits the creation of 2FA authentification tokens per TOTP, the v
|
||||||
Add it to your `Cargo.toml`:
|
Add it to your `Cargo.toml`:
|
||||||
```toml
|
```toml
|
||||||
[dependencies]
|
[dependencies]
|
||||||
totp-rs = "~0.3"
|
totp-rs = "~0.4"
|
||||||
```
|
```
|
||||||
You can then do something like:
|
You can then do something like:
|
||||||
```Rust
|
```Rust
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
use totp_rs::{Algorithm, TOTP};
|
use totp_rs::{Algorithm, TOTP};
|
||||||
|
|
||||||
let username = "example".to_owned();
|
|
||||||
let totp = TOTP::new(
|
let totp = TOTP::new(
|
||||||
Algorithm::SHA1,
|
Algorithm::SHA1,
|
||||||
6,
|
6,
|
||||||
|
@ -25,7 +24,7 @@ let totp = TOTP::new(
|
||||||
let time = SystemTime::now()
|
let time = SystemTime::now()
|
||||||
.duration_since(SystemTime::UNIX_EPOCH).unwrap()
|
.duration_since(SystemTime::UNIX_EPOCH).unwrap()
|
||||||
.as_secs();
|
.as_secs();
|
||||||
let url = totp.get_url(format!("account:{}", username), "my-org.com".to_owned());
|
let url = totp.get_url("user@example.com", "my-org.com");
|
||||||
println!("{}", url);
|
println!("{}", url);
|
||||||
let token = totp.generate(time);
|
let token = totp.generate(time);
|
||||||
println!("{}", token);
|
println!("{}", token);
|
||||||
|
@ -36,14 +35,13 @@ println!("{}", token);
|
||||||
Add it to your `Cargo.toml`:
|
Add it to your `Cargo.toml`:
|
||||||
```toml
|
```toml
|
||||||
[dependencies.totp-rs]
|
[dependencies.totp-rs]
|
||||||
version = "~0.3"
|
version = "~0.4"
|
||||||
features = ["qr"]
|
features = ["qr"]
|
||||||
```
|
```
|
||||||
You can then do something like:
|
You can then do something like:
|
||||||
```Rust
|
```Rust
|
||||||
use totp_rs::{Algorithm, TOTP};
|
use totp_rs::{Algorithm, TOTP};
|
||||||
|
|
||||||
let username = "example".to_owned();
|
|
||||||
let totp = TOTP::new(
|
let totp = TOTP::new(
|
||||||
Algorithm::SHA1,
|
Algorithm::SHA1,
|
||||||
6,
|
6,
|
||||||
|
@ -51,6 +49,6 @@ let totp = TOTP::new(
|
||||||
30,
|
30,
|
||||||
"supersecret".to_owned().into_bytes(),
|
"supersecret".to_owned().into_bytes(),
|
||||||
);
|
);
|
||||||
let code = totp.get_qr(format!("account:{}", username), "my-org.com".to_owned())?;
|
let code = totp.get_qr("user@example.com", "my-org.com")?;
|
||||||
println!("{}", code);
|
println!("{}", code);
|
||||||
```
|
```
|
||||||
|
|
96
src/lib.rs
96
src/lib.rs
|
@ -6,7 +6,6 @@
|
||||||
//! use std::time::SystemTime;
|
//! use std::time::SystemTime;
|
||||||
//! use totp_rs::{Algorithm, TOTP};
|
//! use totp_rs::{Algorithm, TOTP};
|
||||||
//!
|
//!
|
||||||
//! let username = "example".to_owned();
|
|
||||||
//! let totp = TOTP::new(
|
//! let totp = TOTP::new(
|
||||||
//! Algorithm::SHA1,
|
//! Algorithm::SHA1,
|
||||||
//! 6,
|
//! 6,
|
||||||
|
@ -17,7 +16,7 @@
|
||||||
//! let time = SystemTime::now()
|
//! let time = SystemTime::now()
|
||||||
//! .duration_since(SystemTime::UNIX_EPOCH).unwrap()
|
//! .duration_since(SystemTime::UNIX_EPOCH).unwrap()
|
||||||
//! .as_secs();
|
//! .as_secs();
|
||||||
//! let url = totp.get_url(format!("account:{}", username), "my-org.com".to_owned());
|
//! let url = totp.get_url("user@example.com", "my-org.com");
|
||||||
//! println!("{}", url);
|
//! println!("{}", url);
|
||||||
//! let token = totp.generate(time);
|
//! let token = totp.generate(time);
|
||||||
//! println!("{}", token);
|
//! println!("{}", token);
|
||||||
|
@ -26,7 +25,6 @@
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use totp_rs::{Algorithm, TOTP};
|
//! use totp_rs::{Algorithm, TOTP};
|
||||||
//!
|
//!
|
||||||
//! let username = "example".to_owned();
|
|
||||||
//! let totp = TOTP::new(
|
//! let totp = TOTP::new(
|
||||||
//! Algorithm::SHA1,
|
//! Algorithm::SHA1,
|
||||||
//! 6,
|
//! 6,
|
||||||
|
@ -34,7 +32,7 @@
|
||||||
//! 30,
|
//! 30,
|
||||||
//! "supersecret".to_owned().into_bytes(),
|
//! "supersecret".to_owned().into_bytes(),
|
||||||
//! );
|
//! );
|
||||||
//! let code = totp.get_qr(format!("account:{}", username), "my-org.com".to_owned())?;
|
//! let code = totp.get_qr("user@example.com", "my-org.com").unwrap();
|
||||||
//! println!("{}", code);
|
//! println!("{}", code);
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
@ -127,11 +125,10 @@ impl TOTP {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Will check if token is valid by current time, accounting [skew](struct.TOTP.html#structfield.skew)
|
/// Will check if token is valid by current time, accounting [skew](struct.TOTP.html#structfield.skew)
|
||||||
pub fn check(&self, token: String, time: u64) -> bool {
|
pub fn check(&self, token: &str, time: u64) -> bool {
|
||||||
let basestep = time / self.step - (self.skew as u64);
|
let basestep = time / self.step - (self.skew as u64);
|
||||||
for i in 0..self.skew * 2 + 1 {
|
for i in 0..self.skew * 2 + 1 {
|
||||||
let step_time = (basestep + (i as u64)) * (self.step as u64);
|
let step_time = (basestep + (i as u64)) * (self.step as u64);
|
||||||
println!("{}", self.generate(step_time));
|
|
||||||
if self.generate(step_time) == token {
|
if self.generate(step_time) == token {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -139,20 +136,24 @@ impl TOTP {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Will return the base32 representation of the secret, which might be useful when users want to manually add the secret to their authenticator
|
||||||
|
pub fn get_secret_base32(&self) -> String {
|
||||||
|
base32::encode(base32::Alphabet::RFC4648 { padding: false }, &self.secret)
|
||||||
|
}
|
||||||
|
|
||||||
/// Will generate a standard URL used to automatically add TOTP auths. Usually used with qr codes
|
/// Will generate a standard URL used to automatically add TOTP auths. Usually used with qr codes
|
||||||
pub fn get_url(&self, label: String, issuer: String) -> String {
|
pub fn get_url(&self, label: &str, issuer: &str) -> String {
|
||||||
let algorithm: String;
|
let algorithm = match self.algorithm {
|
||||||
match self.algorithm {
|
Algorithm::SHA1 => "SHA1",
|
||||||
Algorithm::SHA1 => algorithm = "SHA1".to_owned(),
|
Algorithm::SHA256 => "SHA256",
|
||||||
Algorithm::SHA256 => algorithm = "SHA256".to_owned(),
|
Algorithm::SHA512 => "SHA512",
|
||||||
Algorithm::SHA512 => algorithm = "SHA512".to_owned(),
|
};
|
||||||
}
|
|
||||||
format!(
|
format!(
|
||||||
"otpauth://totp/{}?secret={}&issuer={}&digits={}&algorithm={}",
|
"otpauth://totp/{}?secret={}&issuer={}&digits={}&algorithm={}",
|
||||||
label,
|
label,
|
||||||
base32::encode(base32::Alphabet::RFC4648 { padding: false }, &self.secret),
|
self.get_secret_base32(),
|
||||||
issuer,
|
issuer,
|
||||||
self.digits.to_string(),
|
self.digits,
|
||||||
algorithm,
|
algorithm,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -167,8 +168,8 @@ impl TOTP {
|
||||||
#[cfg(feature = "qr")]
|
#[cfg(feature = "qr")]
|
||||||
pub fn get_qr(
|
pub fn get_qr(
|
||||||
&self,
|
&self,
|
||||||
label: String,
|
label: &str,
|
||||||
issuer: String,
|
issuer: &str,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
let url = self.get_url(label, issuer);
|
let url = self.get_url(label, issuer);
|
||||||
let code = QrCode::new(&url)?;
|
let code = QrCode::new(&url)?;
|
||||||
|
@ -184,3 +185,64 @@ impl TOTP {
|
||||||
Ok(base64::encode(vec))
|
Ok(base64::encode(vec))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn url_for_secret_matches() {
|
||||||
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
|
||||||
|
let url = totp.get_url("test_url", "totp-rs");
|
||||||
|
assert_eq!(url.as_str(), "otpauth://totp/test_url?secret=KRSXG5CTMVRXEZLU&issuer=totp-rs&digits=6&algorithm=SHA1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn returns_base32() {
|
||||||
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
|
||||||
|
assert_eq!(totp.get_secret_base32().as_str(), "KRSXG5CTMVRXEZLU");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generates_token() {
|
||||||
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
|
||||||
|
assert_eq!(totp.generate(1000).as_str(), "718996");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generates_token_sha256() {
|
||||||
|
let totp = TOTP::new(Algorithm::SHA256, 6, 1, 1, String::from("TestSecret").into_bytes());
|
||||||
|
assert_eq!(totp.generate(1000).as_str(), "423657");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn generates_token_sha512() {
|
||||||
|
let totp = TOTP::new(Algorithm::SHA512, 6, 1, 1, String::from("TestSecret").into_bytes());
|
||||||
|
assert_eq!(totp.generate(1000).as_str(), "416767");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn checks_token() {
|
||||||
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
|
||||||
|
assert!(totp.check("718996", 1000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn checks_token_with_skew() {
|
||||||
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
|
||||||
|
assert!(totp.check("527544", 2000) && totp.check("712039", 2000) && totp.check("714250", 2000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(feature = "qr")]
|
||||||
|
fn generates_qr() {
|
||||||
|
use sha1::{Sha1, Digest};
|
||||||
|
|
||||||
|
let totp = TOTP::new(Algorithm::SHA1, 6, 1, 1, String::from("TestSecret").into_bytes());
|
||||||
|
let qr = totp.get_qr("test_url", "totp-rs").unwrap();
|
||||||
|
|
||||||
|
// Create hash from image
|
||||||
|
let hash_digest = Sha1::digest(qr.as_bytes());
|
||||||
|
assert_eq!(format!("{:x}", hash_digest).as_str(), "3abc0127e7a2b1013fb25c97ef14422c1fe9e878");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue