//! Handles OTP code generation use std::convert::TryFrom; use url::Url; pub enum TwoFactorAuth { Totp(libreauth::oath::TOTP), } pub struct TwoFactorAuthResponse { pub value: String, } impl TwoFactorAuth { pub fn generate(&self) -> TwoFactorAuthResponse { match &self { TwoFactorAuth::Totp(inner) => TwoFactorAuthResponse { value: inner.generate(), }, } } } impl TryFrom<&str> for TwoFactorAuth { type Error = (); fn try_from(key: &str) -> Result { if let Ok(uri) = url::Url::parse(&key) { if let Ok(tfa) = Self::try_from(uri) { return Ok(tfa); } } Ok(TwoFactorAuth::Totp( libreauth::oath::TOTPBuilder::new() .base32_key(&key.replace(" ", "")) .finalize() .unwrap(), )) } } impl TryFrom for TwoFactorAuth { type Error = (); fn try_from(url: Url) -> Result { if url.scheme() != "otpauth" { return Err(()); } if url.host_str() != Some("totp") { return Err(()); } let mut query = url.query_pairs(); let mut builder = &mut libreauth::oath::TOTPBuilder::new(); if let Some(secret) = query.find(|v| v.0 == "secret") { builder = builder.base32_key(&secret.1); } if let Some(digits) = query.find(|v| v.0 == "digits") { builder = builder.output_len(digits.1.parse().map_err(|_| ())?); } if let Some(algorithm) = query.find(|v| v.0 == "algorithm") { builder = builder.hash_function(match algorithm.1.as_ref() { "sha1" => libreauth::hash::HashFunction::Sha1, "sha256" => libreauth::hash::HashFunction::Sha256, "sha512" => libreauth::hash::HashFunction::Sha512, _ => return Err(()), }); } if let Some(period) = query.find(|v| v.0 == "period") { builder = builder.period(period.1.parse().map_err(|_| ())?); } Ok(TwoFactorAuth::Totp(builder.finalize().unwrap())) } }