use crate::oath::HashFunction;
use crate::oath::{
DEFAULT_OTP_HASH, DEFAULT_OTP_OUT_BASE, DEFAULT_OTP_OUT_LEN, DEFAULT_TOTP_PERIOD,
DEFAULT_TOTP_T0,
};
use std::collections::HashMap;
use url::Url;
macro_rules! do_insert_param {
($s: ident, $uri: ident, $elem: expr, $name: expr, $def: expr, $is_gauth: expr, $force_param: expr) => {{
let incl = if $force_param {
true
} else {
match $s.parameters_visibility {
ParametersVisibility::ShowAll => true,
ParametersVisibility::ShowNonDefault => $elem != $def,
ParametersVisibility::GAuthOnly => $is_gauth,
ParametersVisibility::GAuthNonDefaultExt => $is_gauth || $elem != $def,
ParametersVisibility::HideAll => false,
}
};
if incl {
$uri.query_pairs_mut()
.append_pair($name, &$elem.to_string());
}
}};
}
macro_rules! insert_param {
($s: ident, $uri: ident, $elem: expr, $name: expr, $def: expr, $is_gauth: expr) => {{ do_insert_param!($s, $uri, $elem, $name, $def, $is_gauth, false) }};
}
macro_rules! insert_param_opt {
($s: ident, $uri: ident, $elem: expr, $name: expr, $def: expr, $is_gauth: expr) => {{
if let Some(e) = $elem {
do_insert_param!($s, $uri, e, $name, $def, $is_gauth, false);
}
}};
}
macro_rules! insert_param_opt_f {
($s: ident, $uri: ident, $elem: expr, $name: expr, $def: expr, $is_gauth: expr) => {{
if let Some(e) = $elem {
do_insert_param!($s, $uri, e, $name, $def, $is_gauth, true);
}
}};
}
#[derive(Eq, PartialEq)]
pub(crate) enum UriType {
Totp,
Hotp,
}
#[derive(Eq, PartialEq)]
pub enum ParametersVisibility {
ShowAll,
ShowNonDefault,
GAuthOnly,
GAuthNonDefaultExt,
HideAll,
}
pub struct KeyUriBuilder<'a> {
pub(crate) parameters_visibility: ParametersVisibility,
pub(crate) uri_type: UriType,
pub(crate) key: &'a Vec<u8>,
pub(crate) issuer: &'a str,
pub(crate) account_name: &'a str,
pub(crate) custom_label: Option<&'a str>,
pub(crate) custom_parameters: HashMap<&'a str, &'a str>,
pub(crate) algo: HashFunction,
pub(crate) output_len: usize,
pub(crate) output_base: &'a str,
pub(crate) counter: Option<u64>,
pub(crate) period: Option<u32>,
pub(crate) initial_time: Option<u64>,
}
impl<'a> KeyUriBuilder<'a> {
pub fn parameters_visibility_policy(mut self, policy: ParametersVisibility) -> Self {
self.parameters_visibility = policy;
self
}
pub fn overwrite_label(mut self, label: &'a str) -> Self {
self.custom_label = Some(label);
self
}
pub fn add_parameter(mut self, key: &'a str, value: &'a str) -> Self {
self.custom_parameters.insert(key, value);
self
}
#[allow(clippy::cognitive_complexity)]
pub fn finalize(&self) -> String {
let mut uri = Url::parse("otpauth://").unwrap();
let uri_type_final = match self.uri_type {
UriType::Totp => "totp",
UriType::Hotp => "hotp",
};
uri.set_host(Some(uri_type_final)).unwrap();
let label_final = match self.custom_label {
Some(label) => format!("/{}", label),
None => format!("/{}:{}", self.issuer, self.account_name),
};
uri.set_path(&label_final);
let secret_final = base32::encode(
base32::Alphabet::Rfc4648 { padding: false },
self.key.as_slice(),
);
uri.query_pairs_mut().append_pair("secret", &secret_final);
insert_param!(self, uri, self.issuer, "issuer", "", true);
insert_param!(self, uri, self.algo, "algorithm", DEFAULT_OTP_HASH, true);
insert_param!(
self,
uri,
self.output_len,
"digits",
DEFAULT_OTP_OUT_LEN,
true
);
insert_param!(
self,
uri,
self.output_base,
"base",
DEFAULT_OTP_OUT_BASE,
false
);
insert_param_opt_f!(self, uri, self.counter, "counter", 0, true);
insert_param_opt!(self, uri, self.period, "period", DEFAULT_TOTP_PERIOD, true);
insert_param_opt!(self, uri, self.initial_time, "t0", DEFAULT_TOTP_T0, false);
if !self.custom_parameters.is_empty() {
for (k, v) in &self.custom_parameters {
uri.query_pairs_mut().append_pair(k, v);
}
return uri.into();
}
uri.into()
}
}