Skip to main content

rskit_auth/password/
reset.rs

1use std::time::Duration;
2
3use base64::Engine;
4use chrono::{DateTime, Utc};
5use rand::RngCore;
6
7/// Generates short-lived opaque reset tokens (random bytes, base64-URL encoded).
8#[derive(Debug, Clone)]
9pub struct ResetTokenGenerator {
10    /// How long the generated token is valid.
11    pub ttl: Duration,
12}
13
14impl ResetTokenGenerator {
15    /// Create a new generator with the given TTL.
16    pub const fn new(ttl: Duration) -> Self {
17        Self { ttl }
18    }
19
20    /// Generate a random token and its expiry time.
21    ///
22    /// Returns `(token_string, expires_at)`.
23    pub fn generate(&self) -> (String, DateTime<Utc>) {
24        let mut bytes = [0u8; 32];
25        rand::rng().fill_bytes(&mut bytes);
26        let token = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(bytes);
27        let expires_at = Utc::now() + chrono::Duration::from_std(self.ttl).unwrap_or_default();
28        (token, expires_at)
29    }
30}
31
32#[cfg(test)]
33mod tests {
34    use super::*;
35
36    #[test]
37    fn generates_unique_tokens() {
38        let generator = ResetTokenGenerator::new(Duration::from_mins(5));
39        let (t1, _) = generator.generate();
40        let (t2, _) = generator.generate();
41        assert_ne!(t1, t2);
42    }
43
44    #[test]
45    fn expiry_is_in_the_future() {
46        let generator = ResetTokenGenerator::new(Duration::from_mins(5));
47        let (_, exp) = generator.generate();
48        assert!(exp > Utc::now());
49    }
50}