fix(dimentorin): verify-email validates OTP before activating user
- new app_otp_cache table + OtpCache entity (ResourceEnum::OtpCache) - PostgresOtpRepository upsert/find/delete keyed by email - register/resend persist otp_hash+expiry after email sent (no orphan OTP) - verify_email validates via OtpManager::validate_otp_hash, single-use delete - 8 unit tests pass, e2e verified: wrong OTP 400, correct OTP 200
This commit is contained in:
@@ -27,14 +27,24 @@ impl OtpManager {
|
||||
}
|
||||
|
||||
pub fn validate_otp(stored: &OtpData, user_otp: u32) -> bool {
|
||||
if Utc::now() > stored.expires_at {
|
||||
Self::validate_otp_hash(&stored.hash, &stored.expires_at, user_otp)
|
||||
}
|
||||
|
||||
/// Validate a user-supplied OTP against a stored hash + expiry (e.g. from a
|
||||
/// cache table where the plaintext code is not persisted).
|
||||
pub fn validate_otp_hash(
|
||||
stored_hash: &str,
|
||||
expires_at: &DateTime<Utc>,
|
||||
user_otp: u32,
|
||||
) -> bool {
|
||||
if Utc::now() > *expires_at {
|
||||
return false;
|
||||
}
|
||||
let user_otp_str = user_otp.to_string();
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(user_otp_str.as_bytes());
|
||||
let user_hash = format!("{:x}", hasher.finalize());
|
||||
user_hash == stored.hash
|
||||
user_hash == stored_hash
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +73,37 @@ mod tests {
|
||||
assert!(!OtpManager::validate_otp(&otp, 123456));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_otp_hash_valid() {
|
||||
let otp = OtpManager::generate_otp();
|
||||
assert!(OtpManager::validate_otp_hash(
|
||||
&otp.hash,
|
||||
&otp.expires_at,
|
||||
otp.code
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_otp_hash_invalid() {
|
||||
let otp = OtpManager::generate_otp();
|
||||
assert!(!OtpManager::validate_otp_hash(
|
||||
&otp.hash,
|
||||
&otp.expires_at,
|
||||
123456
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_otp_hash_expired() {
|
||||
let mut otp = OtpManager::generate_otp();
|
||||
otp.expires_at = Utc::now() - chrono::Duration::seconds(1);
|
||||
assert!(!OtpManager::validate_otp_hash(
|
||||
&otp.hash,
|
||||
&otp.expires_at,
|
||||
otp.code
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_validate_otp_expired() {
|
||||
let mut otp = OtpManager::generate_otp();
|
||||
|
||||
Reference in New Issue
Block a user