//! 密码哈希 (Argon2id) //! //! Argon2 是 CPU 密集型操作(~100-500ms),不能在 tokio worker 线程上直接执行, //! 否则会阻塞整个异步运行时。所有 async 上下文必须使用 `hash_password_async` //! 和 `verify_password_async`。 use argon2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Argon2, }; use crate::error::{SaasError, SaasResult}; /// 哈希密码(同步版本,仅用于测试和启动时 seed) pub fn hash_password(password: &str) -> SaasResult { let salt = SaltString::generate(&mut OsRng); let argon2 = Argon2::default(); let hash = argon2 .hash_password(password.as_bytes(), &salt) .map_err(|e| SaasError::PasswordHash(e.to_string()))?; Ok(hash.to_string()) } /// 验证密码(同步版本,仅用于测试) pub fn verify_password(password: &str, hash: &str) -> SaasResult { let parsed_hash = PasswordHash::new(hash) .map_err(|e| SaasError::PasswordHash(e.to_string()))?; Ok(Argon2::default() .verify_password(password.as_bytes(), &parsed_hash) .is_ok()) } /// 异步哈希密码 — 在 spawn_blocking 线程池中执行 Argon2 pub async fn hash_password_async(password: String) -> SaasResult { tokio::task::spawn_blocking(move || hash_password(&password)) .await .map_err(|e| SaasError::Internal(format!("spawn_blocking error: {e}")))? } /// 异步验证密码 — 在 spawn_blocking 线程池中执行 Argon2 pub async fn verify_password_async(password: String, hash: String) -> SaasResult { tokio::task::spawn_blocking(move || verify_password(&password, &hash)) .await .map_err(|e| SaasError::Internal(format!("spawn_blocking error: {e}")))? } #[cfg(test)] mod tests { use super::*; #[test] fn test_hash_and_verify() { let hash = hash_password("correct_password").unwrap(); assert!(verify_password("correct_password", &hash).unwrap()); assert!(!verify_password("wrong_password", &hash).unwrap()); } #[test] fn test_different_hashes_for_same_password() { let hash1 = hash_password("same_password").unwrap(); let hash2 = hash_password("same_password").unwrap(); assert_ne!(hash1, hash2); assert!(verify_password("same_password", &hash1).unwrap()); assert!(verify_password("same_password", &hash2).unwrap()); } }