1use std::time::Duration;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
7pub enum TotpAlgorithm {
8 #[default]
10 SHA1,
11 SHA256,
13 SHA512,
15}
16
17impl TotpAlgorithm {
18 pub fn as_str(&self) -> &'static str {
20 match self {
21 TotpAlgorithm::SHA1 => "SHA1",
22 TotpAlgorithm::SHA256 => "SHA256",
23 TotpAlgorithm::SHA512 => "SHA512",
24 }
25 }
26
27 pub fn oid(&self) -> &'static str {
29 match self {
30 TotpAlgorithm::SHA1 => "SHA1",
31 TotpAlgorithm::SHA256 => "SHA256",
32 TotpAlgorithm::SHA512 => "SHA512",
33 }
34 }
35}
36
37#[derive(Debug, Clone)]
39pub struct TotpConfig {
40 pub issuer: String,
42
43 pub account_name: String,
45
46 pub secret: String,
48
49 pub algorithm: TotpAlgorithm,
51
52 pub digits: u32,
54
55 pub time_step: u64,
57
58 pub valid_window: u32,
60}
61
62impl TotpConfig {
63 pub fn new(issuer: impl Into<String>, account_name: impl Into<String>, secret: impl Into<String>) -> Self {
70 Self {
71 issuer: issuer.into(),
72 account_name: account_name.into(),
73 secret: secret.into(),
74 algorithm: TotpAlgorithm::default(),
75 digits: 6,
76 time_step: 30,
77 valid_window: 1,
78 }
79 }
80
81 pub fn with_algorithm(mut self, algorithm: TotpAlgorithm) -> Self {
83 self.algorithm = algorithm;
84 self
85 }
86
87 pub fn with_digits(mut self, digits: u32) -> Self {
89 self.digits = digits;
90 self
91 }
92
93 pub fn with_time_step(mut self, time_step: Duration) -> Self {
95 self.time_step = time_step.as_secs();
96 self
97 }
98
99 pub fn with_valid_window(mut self, window: u32) -> Self {
101 self.valid_window = window;
102 self
103 }
104
105 pub fn to_uri(&self) -> String {
107 let label = wae_types::url_encode(&format!("{}:{}", self.issuer, self.account_name));
108 let params = [
109 ("secret", self.secret.as_str()),
110 ("issuer", &self.issuer),
111 ("algorithm", self.algorithm.oid()),
112 ("digits", &self.digits.to_string()),
113 ("period", &self.time_step.to_string()),
114 ];
115
116 let query = params.iter().map(|(k, v)| format!("{}={}", k, wae_types::url_encode(v))).collect::<Vec<_>>().join("&");
117
118 format!("otpauth://totp/{}?{}", label, query)
119 }
120}
121
122#[derive(Debug, Clone)]
124pub struct HotpConfig {
125 pub issuer: String,
127
128 pub account_name: String,
130
131 pub secret: String,
133
134 pub algorithm: TotpAlgorithm,
136
137 pub digits: u32,
139
140 pub counter: u64,
142}
143
144impl HotpConfig {
145 pub fn new(issuer: impl Into<String>, account_name: impl Into<String>, secret: impl Into<String>) -> Self {
147 Self {
148 issuer: issuer.into(),
149 account_name: account_name.into(),
150 secret: secret.into(),
151 algorithm: TotpAlgorithm::default(),
152 digits: 6,
153 counter: 0,
154 }
155 }
156
157 pub fn with_counter(mut self, counter: u64) -> Self {
159 self.counter = counter;
160 self
161 }
162
163 pub fn with_digits(mut self, digits: u32) -> Self {
165 self.digits = digits;
166 self
167 }
168
169 pub fn with_algorithm(mut self, algorithm: TotpAlgorithm) -> Self {
171 self.algorithm = algorithm;
172 self
173 }
174
175 pub fn to_uri(&self) -> String {
177 let label = wae_types::url_encode(&format!("{}:{}", self.issuer, self.account_name));
178 let params = [
179 ("secret", self.secret.as_str()),
180 ("issuer", &self.issuer),
181 ("algorithm", self.algorithm.oid()),
182 ("digits", &self.digits.to_string()),
183 ("counter", &self.counter.to_string()),
184 ];
185
186 let query = params.iter().map(|(k, v)| format!("{}={}", k, wae_types::url_encode(v))).collect::<Vec<_>>().join("&");
187
188 format!("otpauth://hotp/{}?{}", label, query)
189 }
190}