1use bon::Builder;
4
5#[cfg(feature = "serde")]
6use serde::{Deserialize, Serialize};
7
8#[cfg(feature = "auth")]
9use miette::Diagnostic;
10
11#[cfg(feature = "auth")]
12use thiserror::Error;
13
14#[cfg(feature = "auth")]
15use crate::auth::url::Url;
16
17use crate::{
18 base::Base,
19 period::Period,
20 skew::Skew,
21 time::{self, expect_now, now},
22};
23
24#[cfg(feature = "auth")]
25use crate::{auth::query::Query, base, period};
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash, Builder)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30pub struct Totp<'t> {
31 #[cfg_attr(feature = "serde", serde(flatten))]
33 pub base: Base<'t>,
34 #[builder(default)]
36 #[cfg_attr(feature = "serde", serde(default))]
37 pub skew: Skew,
38 #[builder(default)]
40 #[cfg_attr(feature = "serde", serde(default))]
41 pub period: Period,
42}
43
44impl<'t> Totp<'t> {
45 pub const fn base(&self) -> &Base<'t> {
47 &self.base
48 }
49
50 pub fn base_mut(&mut self) -> &mut Base<'t> {
52 &mut self.base
53 }
54
55 pub fn into_base(self) -> Base<'t> {
57 self.base
58 }
59}
60
61impl Totp<'_> {
62 pub const fn input_at(&self, time: u64) -> u64 {
64 time / self.period.get()
65 }
66
67 pub const fn next_period_at(&self, time: u64) -> u64 {
69 let period = self.period.get();
70
71 (time / period + 1) * period
72 }
73
74 pub fn try_next_period(&self) -> Result<u64, time::Error> {
80 now().map(|time| self.next_period_at(time))
81 }
82
83 pub fn next_period(&self) -> u64 {
89 self.next_period_at(expect_now())
90 }
91
92 pub const fn time_to_live_at(&self, time: u64) -> u64 {
94 let period = self.period.get();
95
96 period - time % period
97 }
98
99 pub fn try_time_to_live(&self) -> Result<u64, time::Error> {
105 now().map(|time| self.time_to_live_at(time))
106 }
107
108 pub fn time_to_live(&self) -> u64 {
114 self.time_to_live_at(expect_now())
115 }
116
117 pub fn generate_at(&self, time: u64) -> u32 {
119 self.base.generate(self.input_at(time))
120 }
121
122 pub fn generate_string_at(&self, time: u64) -> String {
124 self.base.generate_string(self.input_at(time))
125 }
126
127 pub fn try_generate(&self) -> Result<u32, time::Error> {
133 now().map(|time| self.generate_at(time))
134 }
135
136 pub fn generate(&self) -> u32 {
142 self.generate_at(expect_now())
143 }
144
145 pub fn try_generate_string(&self) -> Result<String, time::Error> {
151 now().map(|time| self.generate_string_at(time))
152 }
153
154 pub fn generate_string(&self) -> String {
160 self.generate_string_at(expect_now())
161 }
162
163 pub fn verify_exact_at(&self, time: u64, code: u32) -> bool {
165 self.base.verify(self.input_at(time), code)
166 }
167
168 pub fn verify_string_exact_at<S: AsRef<str>>(&self, time: u64, code: S) -> bool {
170 self.base.verify_string(self.input_at(time), code)
171 }
172
173 pub fn try_verify_exact(&self, code: u32) -> Result<bool, time::Error> {
179 now().map(|time| self.verify_exact_at(time, code))
180 }
181
182 pub fn verify_exact(&self, code: u32) -> bool {
188 self.verify_exact_at(expect_now(), code)
189 }
190
191 pub fn try_verify_string_exact<S: AsRef<str>>(&self, code: S) -> Result<bool, time::Error> {
197 now().map(|time| self.verify_string_exact_at(time, code))
198 }
199
200 pub fn verify_string_exact<S: AsRef<str>>(&self, code: S) -> bool {
206 self.verify_string_exact_at(expect_now(), code)
207 }
208
209 pub fn verify_at(&self, time: u64, code: u32) -> bool {
211 self.skew
212 .apply(self.input_at(time))
213 .any(|input| self.base.verify(input, code))
214 }
215
216 fn verify_str_at(&self, time: u64, code: &str) -> bool {
217 self.skew
218 .apply(self.input_at(time))
219 .any(|input| self.base.verify_string(input, code))
220 }
221
222 pub fn verify_string_at<S: AsRef<str>>(&self, time: u64, code: S) -> bool {
224 self.verify_str_at(time, code.as_ref())
225 }
226
227 pub fn try_verify(&self, code: u32) -> Result<bool, time::Error> {
233 now().map(|time| self.verify_at(time, code))
234 }
235
236 pub fn verify(&self, code: u32) -> bool {
242 self.verify_at(expect_now(), code)
243 }
244
245 pub fn try_verify_string<S: AsRef<str>>(&self, code: S) -> Result<bool, time::Error> {
251 now().map(|time| self.verify_string_at(time, code))
252 }
253
254 pub fn verify_string<S: AsRef<str>>(&self, code: S) -> bool {
260 self.verify_string_at(expect_now(), code)
261 }
262}
263
264#[cfg(feature = "auth")]
266pub const PERIOD: &str = "period";
267
268#[cfg(feature = "auth")]
271#[derive(Debug, Error, Diagnostic)]
272#[error(transparent)]
273#[diagnostic(transparent)]
274pub enum ErrorSource {
275 Base(#[from] base::Error),
277 Period(#[from] period::ParseError),
279}
280
281#[cfg(feature = "auth")]
283#[derive(Debug, Error, Diagnostic)]
284#[error("failed to extract TOTP from OTP URL")]
285#[diagnostic(code(otp_std::totp), help("see the report for more information"))]
286pub struct Error {
287 #[source]
289 #[diagnostic_source]
290 pub source: ErrorSource,
291}
292
293#[cfg(feature = "auth")]
294impl Error {
295 pub const fn new(source: ErrorSource) -> Self {
297 Self { source }
298 }
299
300 pub fn base(error: base::Error) -> Self {
302 Self::new(error.into())
303 }
304
305 pub fn period(error: period::ParseError) -> Self {
307 Self::new(error.into())
308 }
309}
310
311#[cfg(feature = "auth")]
312impl Totp<'_> {
313 pub fn query_for(&self, url: &mut Url) {
317 self.base.query_for(url);
318
319 let period = self.period.to_string();
320
321 url.query_pairs_mut().append_pair(PERIOD, period.as_str());
322 }
323
324 pub fn extract_from(query: &mut Query<'_>) -> Result<Self, Error> {
330 let base = Base::extract_from(query).map_err(Error::base)?;
331
332 let maybe_period = query
333 .remove(PERIOD)
334 .map(|string| string.parse())
335 .transpose()
336 .map_err(Error::period)?;
337
338 let totp = Self::builder()
339 .base(base)
340 .maybe_period(maybe_period)
341 .build();
342
343 Ok(totp)
344 }
345}
346
347pub type Owned = Totp<'static>;
349
350impl Totp<'_> {
351 pub fn into_owned(self) -> Owned {
353 Owned::builder()
354 .base(self.base.into_owned())
355 .skew(self.skew)
356 .period(self.period)
357 .build()
358 }
359}