otp_std/
totp.rs

1//! Time-based One-Time Password (TOTP) functionality.
2
3use 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/// Represents TOTP configurations.
28#[derive(Debug, Clone, PartialEq, Eq, Hash, Builder)]
29#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30pub struct Totp<'t> {
31    /// The base configuration.
32    #[cfg_attr(feature = "serde", serde(flatten))]
33    pub base: Base<'t>,
34    /// The skew to apply.
35    #[builder(default)]
36    #[cfg_attr(feature = "serde", serde(default))]
37    pub skew: Skew,
38    /// The period to use.
39    #[builder(default)]
40    #[cfg_attr(feature = "serde", serde(default))]
41    pub period: Period,
42}
43
44impl<'t> Totp<'t> {
45    /// Returns the base configuration.
46    pub const fn base(&self) -> &Base<'t> {
47        &self.base
48    }
49
50    /// Returns the mutable base configuration.
51    pub fn base_mut(&mut self) -> &mut Base<'t> {
52        &mut self.base
53    }
54
55    /// Consumes [`Self`], returning the base configuration.
56    pub fn into_base(self) -> Base<'t> {
57        self.base
58    }
59}
60
61impl Totp<'_> {
62    /// Returns the input value corresponding to the given time.
63    pub const fn input_at(&self, time: u64) -> u64 {
64        time / self.period.get()
65    }
66
67    /// Returns the time corresponding to the next period from the given time.
68    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    /// Tries to return the time corresponding to the next period from the current time.
75    ///
76    /// # Errors
77    ///
78    /// Returns [`time::Error`] if the system time is before the epoch.
79    pub fn try_next_period(&self) -> Result<u64, time::Error> {
80        now().map(|time| self.next_period_at(time))
81    }
82
83    /// Returns the time corresponding to the next period from the current time.
84    ///
85    /// # Panics
86    ///
87    /// Panics if the system time is before the epoch.
88    pub fn next_period(&self) -> u64 {
89        self.next_period_at(expect_now())
90    }
91
92    /// Returns the time to live of the code for the given time.
93    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    /// Tries to return the time to live of the code for the current time.
100    ///
101    /// # Errors
102    ///
103    /// Returns [`time::Error`] if the system time is before the epoch.
104    pub fn try_time_to_live(&self) -> Result<u64, time::Error> {
105        now().map(|time| self.time_to_live_at(time))
106    }
107
108    /// Returns the time to live of the code for the current time.
109    ///
110    /// # Panics
111    ///
112    /// Panics if the system time is before the epoch.
113    pub fn time_to_live(&self) -> u64 {
114        self.time_to_live_at(expect_now())
115    }
116
117    /// Generates the code for the given time.
118    pub fn generate_at(&self, time: u64) -> u32 {
119        self.base.generate(self.input_at(time))
120    }
121
122    /// Generates the string code for the given time.
123    pub fn generate_string_at(&self, time: u64) -> String {
124        self.base.generate_string(self.input_at(time))
125    }
126
127    /// Tries to generate the code for the current time.
128    ///
129    /// # Errors
130    ///
131    /// Returns [`time::Error`] if the system time is before the epoch.
132    pub fn try_generate(&self) -> Result<u32, time::Error> {
133        now().map(|time| self.generate_at(time))
134    }
135
136    /// Generates the code for the current time.
137    ///
138    /// # Panics
139    ///
140    /// Panics if the system time is before the epoch.
141    pub fn generate(&self) -> u32 {
142        self.generate_at(expect_now())
143    }
144
145    /// Tries to generate the string code for the current time.
146    ///
147    /// # Errors
148    ///
149    /// Returns [`time::Error`] if the system time is before the epoch.
150    pub fn try_generate_string(&self) -> Result<String, time::Error> {
151        now().map(|time| self.generate_string_at(time))
152    }
153
154    /// Generates the string code for the current time.
155    ///
156    /// # Panics
157    ///
158    /// Panics if the system time is before the epoch.
159    pub fn generate_string(&self) -> String {
160        self.generate_string_at(expect_now())
161    }
162
163    /// Verifies the given code for the given time.
164    pub fn verify_exact_at(&self, time: u64, code: u32) -> bool {
165        self.base.verify(self.input_at(time), code)
166    }
167
168    /// Verifies the given string code for the given time.
169    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    /// Tries to verify the given code for the current time *exactly*.
174    ///
175    /// # Errors
176    ///
177    /// Returns [`time::Error`] if the system time is before the epoch.
178    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    /// Verifies the given code for the current time *exactly*.
183    ///
184    /// # Panics
185    ///
186    /// Panics if the system time is before the epoch.
187    pub fn verify_exact(&self, code: u32) -> bool {
188        self.verify_exact_at(expect_now(), code)
189    }
190
191    /// Tries to verify the given string code for the current time *exactly*.
192    ///
193    /// # Errors
194    ///
195    /// Returns [`time::Error`] if the system time is before the epoch.
196    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    /// Verifies the given string code for the current time *exactly*.
201    ///
202    /// # Panics
203    ///
204    /// Panics if the system time is before the epoch.
205    pub fn verify_string_exact<S: AsRef<str>>(&self, code: S) -> bool {
206        self.verify_string_exact_at(expect_now(), code)
207    }
208
209    /// Verifies the given code for the given time, accounting for *skews*.
210    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    /// Verifies the given string code for the given time, accounting for *skews*.
223    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    /// Tries to verify the given code for the current time, accounting for *skews*.
228    ///
229    /// # Errors
230    ///
231    /// Returns [`time::Error`] if the system time is before the epoch.
232    pub fn try_verify(&self, code: u32) -> Result<bool, time::Error> {
233        now().map(|time| self.verify_at(time, code))
234    }
235
236    /// Verifies the given code for the current time, accounting for *skews*.
237    ///
238    /// # Panics
239    ///
240    /// Panics if the system time is before the epoch.
241    pub fn verify(&self, code: u32) -> bool {
242        self.verify_at(expect_now(), code)
243    }
244
245    /// Tries to verify the given string code for the current time, accounting for *skews*.
246    ///
247    /// # Errors
248    ///
249    /// Returns [`time::Error`] if the system time is before the epoch.
250    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    /// Verifies the given string code for the current time, accounting for *skews*.
255    ///
256    /// # Panics
257    ///
258    /// Panics if the system time is before the epoch.
259    pub fn verify_string<S: AsRef<str>>(&self, code: S) -> bool {
260        self.verify_string_at(expect_now(), code)
261    }
262}
263
264/// The `period` literal.
265#[cfg(feature = "auth")]
266pub const PERIOD: &str = "period";
267
268/// Represents sources of errors that can occur when extracting TOTP configurations
269/// from OTP URLs.
270#[cfg(feature = "auth")]
271#[derive(Debug, Error, Diagnostic)]
272#[error(transparent)]
273#[diagnostic(transparent)]
274pub enum ErrorSource {
275    /// The base configuration could not be extracted.
276    Base(#[from] base::Error),
277    /// The period could not be parsed.
278    Period(#[from] period::ParseError),
279}
280
281/// Represents errors that can occur when extracting TOTP configurations from OTP URLs.
282#[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    /// The source of this error.
288    #[source]
289    #[diagnostic_source]
290    pub source: ErrorSource,
291}
292
293#[cfg(feature = "auth")]
294impl Error {
295    /// Constructs [`Self`].
296    pub const fn new(source: ErrorSource) -> Self {
297        Self { source }
298    }
299
300    /// Constructs [`Self`] from [`base::Error`].
301    pub fn base(error: base::Error) -> Self {
302        Self::new(error.into())
303    }
304
305    /// Constructs [`Self`] from [`period::ParseError`].
306    pub fn period(error: period::ParseError) -> Self {
307        Self::new(error.into())
308    }
309}
310
311#[cfg(feature = "auth")]
312impl Totp<'_> {
313    /// Applies the HOTP configuration to the given URL.
314    ///
315    /// Note that this method applies the base configuration on its own.
316    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    /// Extracts the TOTP configuration from the given query.
325    ///
326    /// # Errors
327    ///
328    /// Returns [`struct@Error`] if extraction fails.
329    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
347/// Represents owned [`Totp`].
348pub type Owned = Totp<'static>;
349
350impl Totp<'_> {
351    /// Converts [`Self`] into [`Owned`].
352    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}