wae_authentication/totp/
service.rs1use crate::totp::{
4 HotpConfig, SecretFormat, TotpAlgorithm, TotpConfig, TotpSecret, generate_hotp, generate_totp, verify_hotp, verify_totp,
5};
6use std::time::{SystemTime, UNIX_EPOCH};
7use wae_types::WaeError;
8
9pub type TotpResult<T> = Result<T, WaeError>;
11
12#[derive(Debug, Clone)]
14pub struct TotpService {
15 config: TotpConfig,
16 secret: TotpSecret,
17}
18
19impl TotpService {
20 pub fn new(config: TotpConfig) -> TotpResult<Self> {
25 let secret = TotpSecret::from_base32(&config.secret)?;
26 Ok(Self { config, secret })
27 }
28
29 pub fn create(issuer: impl Into<String>, account_name: impl Into<String>) -> TotpResult<Self> {
35 let secret = TotpSecret::generate_default()?;
36 let config = TotpConfig::new(issuer, account_name, secret.as_base32());
37 Ok(Self { config, secret })
38 }
39
40 pub fn generate_code(&self) -> TotpResult<String> {
42 let timestamp = self.current_timestamp();
43 generate_totp(self.secret.as_bytes(), timestamp, self.config.time_step, self.config.digits, self.config.algorithm)
44 }
45
46 pub fn verify(&self, code: &str) -> TotpResult<bool> {
51 let timestamp = self.current_timestamp();
52 verify_totp(
53 self.secret.as_bytes(),
54 code,
55 timestamp,
56 self.config.time_step,
57 self.config.digits,
58 self.config.algorithm,
59 self.config.valid_window,
60 )
61 }
62
63 pub fn verify_strict(&self, code: &str) -> TotpResult<bool> {
65 let timestamp = self.current_timestamp();
66 verify_totp(
67 self.secret.as_bytes(),
68 code,
69 timestamp,
70 self.config.time_step,
71 self.config.digits,
72 self.config.algorithm,
73 0,
74 )
75 }
76
77 pub fn remaining_seconds(&self) -> u64 {
79 let timestamp = self.current_timestamp();
80 crate::totp::algorithm::get_remaining_seconds(timestamp, self.config.time_step)
81 }
82
83 pub fn progress(&self) -> f32 {
85 let remaining = self.remaining_seconds() as f32;
86 1.0 - (remaining / self.config.time_step as f32)
87 }
88
89 pub fn to_uri(&self) -> String {
91 self.config.to_uri()
92 }
93
94 pub fn secret(&self) -> &TotpSecret {
96 &self.secret
97 }
98
99 pub fn formatted_secret(&self, format: SecretFormat) -> String {
101 self.secret.format(format)
102 }
103
104 pub fn config(&self) -> &TotpConfig {
106 &self.config
107 }
108
109 fn current_timestamp(&self) -> u64 {
110 SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs()
111 }
112}
113
114#[derive(Debug, Clone)]
116pub struct HotpService {
117 config: HotpConfig,
118 secret: TotpSecret,
119}
120
121impl HotpService {
122 pub fn new(config: HotpConfig) -> TotpResult<Self> {
124 let secret = TotpSecret::from_base32(&config.secret)?;
125 Ok(Self { config, secret })
126 }
127
128 pub fn create(issuer: impl Into<String>, account_name: impl Into<String>) -> TotpResult<Self> {
130 let secret = TotpSecret::generate_default()?;
131 let config = HotpConfig::new(issuer, account_name, secret.as_base32());
132 Ok(Self { config, secret })
133 }
134
135 pub fn generate_code(&self) -> TotpResult<String> {
137 generate_hotp(self.secret.as_bytes(), self.config.counter, self.config.digits, self.config.algorithm)
138 }
139
140 pub fn generate_code_at(&self, counter: u64) -> TotpResult<String> {
142 generate_hotp(self.secret.as_bytes(), counter, self.config.digits, self.config.algorithm)
143 }
144
145 pub fn verify(&self, code: &str) -> TotpResult<bool> {
147 verify_hotp(self.secret.as_bytes(), code, self.config.counter, self.config.digits, self.config.algorithm)
148 }
149
150 pub fn verify_and_increment(&mut self, code: &str) -> TotpResult<bool> {
152 let valid = self.verify(code)?;
153 if valid {
154 self.config.counter += 1;
155 }
156 Ok(valid)
157 }
158
159 pub fn counter(&self) -> u64 {
161 self.config.counter
162 }
163
164 pub fn set_counter(&mut self, counter: u64) {
166 self.config.counter = counter;
167 }
168
169 pub fn to_uri(&self) -> String {
171 self.config.to_uri()
172 }
173
174 pub fn secret(&self) -> &TotpSecret {
176 &self.secret
177 }
178
179 pub fn config(&self) -> &HotpConfig {
181 &self.config
182 }
183}
184
185pub fn create_totp(issuer: impl Into<String>, account_name: impl Into<String>) -> TotpResult<TotpService> {
187 TotpService::create(issuer, account_name)
188}
189
190pub fn totp_from_secret(
192 issuer: impl Into<String>,
193 account_name: impl Into<String>,
194 secret: impl Into<String>,
195) -> TotpResult<TotpService> {
196 let config = TotpConfig::new(issuer, account_name, secret);
197 TotpService::new(config)
198}
199
200pub fn generate_totp_code(secret: &str) -> TotpResult<String> {
202 let secret = TotpSecret::from_base32(secret)?;
203 let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
204 generate_totp(secret.as_bytes(), timestamp, 30, 6, TotpAlgorithm::default())
205}
206
207pub fn verify_totp_code(secret: &str, code: &str) -> TotpResult<bool> {
209 let secret = TotpSecret::from_base32(secret)?;
210 let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
211 verify_totp(secret.as_bytes(), code, timestamp, 30, 6, TotpAlgorithm::default(), 1)
212}