pub use qrcode::EcLevel;
use super::ThotpError;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
use qrcode::{self, render::svg::Color, QrCode};
use std::fmt::Write;
pub fn generate_code_svg(
otp_uri: &str,
width: Option<u32>,
height: Option<u32>,
ec_level: EcLevel,
) -> Result<String, ThotpError> {
let width = if let Some(width) = width { width } else { 200 };
let height = if let Some(height) = height {
height
} else {
200
};
let code = QrCode::with_error_correction_level(otp_uri, ec_level)?;
Ok(code
.render()
.min_dimensions(width, height)
.dark_color(Color("#000000"))
.light_color(Color("#ffffff"))
.build())
}
pub fn otp_uri(
otp_type: &str,
secret: &str,
label: &str,
issuer: &str,
counter: Option<u64>,
) -> Result<String, ThotpError> {
if otp_type != "totp" && otp_type != "hotp" {
return Err(ThotpError::InvalidUri(String::from(
"Invalid otp type provided, accepted values are \"hotp\" and \"totp\"",
)));
}
let label = utf8_percent_encode(label, NON_ALPHANUMERIC);
let issuer = utf8_percent_encode(issuer, NON_ALPHANUMERIC);
let mut uri = format!(
"otpauth://{}/{}?secret={}&issuer={}",
otp_type, label, secret, issuer
);
if otp_type == "hotp" {
if let Some(counter) = counter {
write!(uri, "&counter={}", counter)?;
} else {
write!(uri, "&counter=0")?;
}
}
Ok(uri)
}
#[cfg(feature = "custom")]
pub fn uri_append_params(
otp_uri: &mut String,
algorithm: Option<&str>,
digits: Option<u8>,
time_step: Option<u8>,
) -> Result<(), ThotpError> {
if let Some(algorithm) = algorithm {
if algorithm != "SHA1" && algorithm != "SHA256" && algorithm != "SHA512" {
return Err(ThotpError::InvalidUri(String::from(
"Invalid algorithm provided, accepted values are \"SHA1\", \"SHA256\" and \"SHA512\"",
)));
}
write!(otp_uri, "&algorithm={}", algorithm)?;
}
if let Some(digits) = digits {
if !(6..=10).contains(&digits) {
return Err(ThotpError::InvalidDigits);
}
write!(otp_uri, "&digits={}", digits)?;
}
if let Some(time_step) = time_step {
write!(otp_uri, "&period={}", time_step)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::super::encoding::{decode, encode};
use super::super::ThotpError;
use super::super::{generate_secret, otp, TIME_STEP};
use super::*;
use std::time::{SystemTime, UNIX_EPOCH};
#[test]
fn uri() -> Result<(), ThotpError> {
let mut uri = otp_uri("totp", "super_secret", "biblius", "bedgalopolis", None)?;
assert_eq!(
uri,
"otpauth://totp/biblius?secret=super_secret&issuer=bedgalopolis"
);
uri_append_params(&mut uri, Some("SHA256"), Some(7), None)?;
assert_eq!(uri, "otpauth://totp/biblius?secret=super_secret&issuer=bedgalopolis&algorithm=SHA256&digits=7");
Ok(())
}
#[test]
fn qr() -> Result<(), ThotpError> {
let secret = generate_secret(160);
let secret = &encode(&secret, data_encoding::BASE32);
let uri = otp_uri("totp", secret, "biblius", "bedgalopolis", None)?;
let _code = generate_code_svg(&uri, Some(400), Some(400), EcLevel::H).unwrap();
Ok(())
}
#[test]
fn totp_now() -> Result<(), ThotpError> {
let secret = "6RPFBC2M7HKNEAMQ435XFIEGBGW4ZLN2NT6DPYNVMV7R7REDIBGTXCKIN5S7BSNZTUBPXT6ZILNU6Q5LW6UZGUJZNJMWF55QE67PIZ3KRBWYS35FDKQ6I34XWQOHVR76NEFLZEBBEWGY2UK2KRMGI3BC7AXAV3OBO3J2DXXGVNBLHB5VFCFJF65CZCXUYP5LVAMHJSV4M6ZJ5FD3D5OU66NBE3M2FAXFBKQBHNMWCVZ7NZXAG3DVHMORDMJNDFWT";
let secret = decode(secret, data_encoding::BASE32)?;
let nonce = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
/ TIME_STEP as u64;
let _totp = otp(&secret, nonce)?;
Ok(())
}
#[test]
fn hotp_now() -> Result<(), ThotpError> {
let secret = "ACTGTGXN6K5SIAWMTDPAUULYEZI2RFA3NFJC27U4EO4PNL6UEMUB3ZOD7BGOIRAFF54RDGBAKAZKTCX2CDRLPQ3GPW42AXVD4SEKLWNTBM56O4EXP7HUBBGKEEUHM4IF";
let secret = decode(secret, data_encoding::BASE32)?;
let nonce = 3;
let _totp = otp(&secret, nonce)?;
Ok(())
}
}