1use crate::prelude::*;
2use rand::Rng;
3use deadpool_redis::redis::AsyncCommands;
4use crate::crypto::crypto::CryptoError;
5
6#[derive(thiserror::Error, Debug)]
8pub enum OtpError {
9 #[error("Redis manager error: {0}")]
10 RedisManager(#[from] RedisManagerError),
11
12 #[error("Redis error: {0}")]
13 Redis(#[from] deadpool_redis::redis::RedisError),
14
15 #[error("Redis pool error: {0}")]
16 RedisPool(#[from] deadpool_redis::PoolError),
17
18 #[error("Crypto error: {0}")]
19 Crypto(#[from] CryptoError),
20}
21
22
23pub type Result<T> = std::result::Result<T, OtpError>;
24
25#[derive(Clone)]
28pub struct OtpConfig {
29 pub secret: String,
30 pub crypto: CryptoService,
31 pub redis: RedisManager,
32}
33
34#[derive(Clone)]
35pub struct OtpService {
36 config: OtpConfig,
37}
38
39impl OtpService {
40 pub fn new(config: OtpConfig) -> Self {
41 Self { config }
42 }
43
44 pub async fn generate_otp(&self, user_id: &str, digits: u32, expiry_time: u64) -> Result<String> {
45 let otp = Self::random_digits(digits);
46 let content_to_hash = format!("{}:{}", self.config.secret, otp);
47 let hash = self.config.crypto.sha256_hash(&content_to_hash)?;
48 let redis_key = format!("otp:{}", user_id);
49
50 let mut con = self.config.redis.get_connection().await?;
51
52 let _res: () = con.set_ex(&redis_key, &hash, expiry_time).await?;
53
54 Ok(otp)
55 }
56
57 pub async fn verify_otp(&self, user_id: &str, otp: &str) -> Result<bool> {
58 let redis_key = format!("otp:{}", user_id);
59
60 let mut con = self.config.redis.get_connection().await?;
61 let stored_hash: Option<String> = con.get(&redis_key).await?;
62
63 let hash_to_check = match stored_hash {
64 Some(h) => h,
65 None => return Ok(false),
66 };
67
68 let content_to_hash = format!("{}:{}", self.config.secret, otp);
69 let calculated_hash = self.config.crypto.sha256_hash(&content_to_hash)?;
70 let ok = calculated_hash == hash_to_check;
71
72 if ok {
73 let _res: () = con.del(&redis_key).await?;
74 }
75
76 Ok(ok)
77 }
78
79 fn random_digits(digits: u32) -> String {
80 let mut bytes = [0u8; 8];
81
82 rand::rng().fill_bytes(&mut bytes);
83
84 let num = u64::from_le_bytes(bytes);
85 let otp = num % 10u64.pow(digits);
86 format!("{:0width$}", otp, width = digits as usize)
87 }
88}