Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
refactor(relay): 复用HTTP客户端和请求体序列化结果 feat(kernel): 添加获取单个审批记录的方法 fix(store): 改进SaaS连接错误分类和降级处理 docs: 更新审计文档和系统架构文档 refactor(prompt): 优化SQL查询参数化绑定 refactor(migration): 使用静态SQL和COALESCE更新配置项 feat(commands): 添加审批执行状态追踪和事件通知 chore: 更新启动脚本以支持Admin后台 fix(auth-guard): 优化授权状态管理和错误处理 refactor(db): 使用异步密码哈希函数 refactor(totp): 使用异步密码验证函数 style: 清理无用文件和注释 docs: 更新功能全景和审计文档 refactor(service): 优化HTTP客户端重用和请求处理 fix(connection): 改进SaaS不可用时的降级处理 refactor(handlers): 使用异步密码验证函数 chore: 更新依赖和工具链配置
67 lines
2.4 KiB
Rust
67 lines
2.4 KiB
Rust
//! 密码哈希 (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<String> {
|
||
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<bool> {
|
||
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<String> {
|
||
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<bool> {
|
||
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());
|
||
}
|
||
}
|