233 lines
7.9 KiB
Rust
233 lines
7.9 KiB
Rust
use anyhow::Result;
|
|
use argon2::{
|
|
password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
|
|
Argon2,
|
|
};
|
|
use rand::Rng;
|
|
|
|
use crate::utils::jwt::TokenService;
|
|
use crate::domain::dto::auth::{RegisterRequest, LoginRequest, DeleteUserRequest};
|
|
use crate::domain::entities::users;
|
|
use crate::config::auth::AuthConfig;
|
|
use crate::infra::redis::{redis_client::RedisClient, redis_key::{BusinessType, RedisKey}};
|
|
use crate::repositories::user_repository::UserRepository;
|
|
|
|
pub struct AuthService {
|
|
user_repo: UserRepository,
|
|
redis_client: RedisClient,
|
|
auth_config: AuthConfig,
|
|
}
|
|
|
|
impl AuthService {
|
|
pub fn new(user_repo: UserRepository, redis_client: RedisClient, auth_config: AuthConfig) -> Self {
|
|
Self { user_repo, redis_client, auth_config }
|
|
}
|
|
|
|
/// 哈希密码
|
|
pub fn hash_password(&self, password: &str) -> Result<String> {
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
let argon2 = Argon2::default();
|
|
let password_hash = argon2
|
|
.hash_password(password.as_bytes(), &salt)
|
|
.map_err(|e| anyhow::anyhow!("密码哈希失败: {}", e))?
|
|
.to_string();
|
|
Ok(password_hash)
|
|
}
|
|
|
|
/// 生成用户 ID
|
|
pub fn generate_user_id(&self) -> String {
|
|
let mut rng = rand::thread_rng();
|
|
rng.gen_range(1_000_000_000i64..10_000_000_000i64)
|
|
.to_string()
|
|
}
|
|
|
|
/// 生成唯一的用户 ID
|
|
pub async fn generate_unique_user_id(&self) -> Result<String> {
|
|
let mut attempts = 0;
|
|
const MAX_ATTEMPTS: u32 = 10;
|
|
|
|
loop {
|
|
let candidate_id = self.generate_user_id();
|
|
|
|
let existing = self.user_repo.count_by_id(&candidate_id).await?;
|
|
if existing == 0 {
|
|
return Ok(candidate_id);
|
|
}
|
|
|
|
attempts += 1;
|
|
if attempts >= MAX_ATTEMPTS {
|
|
return Err(anyhow::anyhow!("生成唯一用户 ID 失败"));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 保存 refresh_token 到 Redis
|
|
async fn save_refresh_token(&self, user_id: &str, refresh_token: &str, expiration_days: i64) -> Result<()> {
|
|
let key = RedisKey::new(BusinessType::Auth)
|
|
.add_identifier("refresh_token")
|
|
.add_identifier(user_id);
|
|
|
|
let expiration_seconds = expiration_days * 24 * 3600;
|
|
|
|
self.redis_client
|
|
.set_ex(&key.build(), refresh_token, expiration_seconds as u64)
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("Redis 保存失败: {}", e))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 获取并删除 refresh_token
|
|
async fn get_and_delete_refresh_token(&self, user_id: &str) -> Result<String> {
|
|
let key = RedisKey::new(BusinessType::Auth)
|
|
.add_identifier("refresh_token")
|
|
.add_identifier(user_id);
|
|
|
|
let token: Option<String> = self.redis_client
|
|
.get(&key.build())
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("Redis 查询失败: {}", e))?;
|
|
|
|
if token.is_some() {
|
|
self.redis_client
|
|
.delete_key(&key)
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("Redis 删除失败: {}", e))?;
|
|
}
|
|
|
|
token.ok_or_else(|| anyhow::anyhow!("刷新令牌无效或已过期"))
|
|
}
|
|
|
|
/// 删除用户的 refresh_token
|
|
pub async fn delete_refresh_token(&self, user_id: &str) -> Result<()> {
|
|
let key = RedisKey::new(BusinessType::Auth)
|
|
.add_identifier("refresh_token")
|
|
.add_identifier(user_id);
|
|
|
|
self.redis_client
|
|
.delete_key(&key)
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("Redis 删除失败: {}", e))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// 注册用户
|
|
pub async fn register(
|
|
&self,
|
|
request: RegisterRequest,
|
|
) -> Result<(users::Model, String, String)> {
|
|
// 1. 检查邮箱是否已存在
|
|
let existing = self.user_repo.count_by_email(&request.email).await?;
|
|
|
|
if existing > 0 {
|
|
return Err(anyhow::anyhow!("邮箱已注册"));
|
|
}
|
|
|
|
// 2. 哈希密码
|
|
let password_hash = self.hash_password(&request.password)?;
|
|
|
|
// 3. 生成用户 ID
|
|
let user_id = self.generate_unique_user_id().await?;
|
|
|
|
// 4. 插入数据库并获取包含真实 created_at 的用户对象
|
|
let user = self.user_repo.insert(user_id.clone(), request.email, password_hash).await?;
|
|
|
|
// 5. 生成 token
|
|
let (access_token, refresh_token) = TokenService::generate_token_pair(
|
|
&user_id,
|
|
self.auth_config.access_token_expiration_minutes,
|
|
self.auth_config.refresh_token_expiration_days,
|
|
&self.auth_config.jwt_secret,
|
|
)?;
|
|
|
|
// 6. 保存 refresh_token
|
|
self.save_refresh_token(&user_id, &refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
|
|
|
|
Ok((user, access_token, refresh_token))
|
|
}
|
|
|
|
/// 登录
|
|
pub async fn login(
|
|
&self,
|
|
request: LoginRequest,
|
|
) -> Result<(users::Model, String, String)> {
|
|
// 1. 查询用户
|
|
let user = self.user_repo.find_by_email(&request.email).await?
|
|
.ok_or_else(|| anyhow::anyhow!("邮箱或密码错误"))?;
|
|
|
|
// 2. 验证密码
|
|
let password_hash = self.user_repo.get_password_hash(&request.email).await?
|
|
.ok_or_else(|| anyhow::anyhow!("邮箱或密码错误"))?;
|
|
|
|
let parsed_hash = PasswordHash::new(&password_hash)
|
|
.map_err(|e| anyhow::anyhow!("解析密码哈希失败: {}", e))?;
|
|
let argon2 = Argon2::default();
|
|
|
|
argon2
|
|
.verify_password(request.password.as_bytes(), &parsed_hash)
|
|
.map_err(|_| anyhow::anyhow!("邮箱或密码错误"))?;
|
|
|
|
// 3. 生成 token
|
|
let (access_token, refresh_token) = TokenService::generate_token_pair(
|
|
&user.id,
|
|
self.auth_config.access_token_expiration_minutes,
|
|
self.auth_config.refresh_token_expiration_days,
|
|
&self.auth_config.jwt_secret,
|
|
)?;
|
|
|
|
// 4. 保存 refresh_token
|
|
self.save_refresh_token(&user.id, &refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
|
|
|
|
Ok((user, access_token, refresh_token))
|
|
}
|
|
|
|
/// 使用 refresh_token 刷新 access_token
|
|
pub async fn refresh_access_token(
|
|
&self,
|
|
refresh_token: &str,
|
|
) -> Result<(String, String)> {
|
|
// 1. 从 refresh_token 中解码出 user_id
|
|
let user_id = TokenService::decode_user_id(refresh_token, &self.auth_config.jwt_secret)?;
|
|
|
|
// 2. 从 Redis 获取存储的 token 并删除
|
|
let stored_token = self.get_and_delete_refresh_token(&user_id).await?;
|
|
|
|
// 3. 验证 token 是否匹配
|
|
if stored_token != refresh_token {
|
|
return Err(anyhow::anyhow!("刷新令牌无效"));
|
|
}
|
|
|
|
// 4. 生成新的 token 对
|
|
let (new_access_token, new_refresh_token) = TokenService::generate_token_pair(
|
|
&user_id,
|
|
self.auth_config.access_token_expiration_minutes,
|
|
self.auth_config.refresh_token_expiration_days,
|
|
&self.auth_config.jwt_secret,
|
|
)?;
|
|
|
|
// 5. 保存新的 refresh_token
|
|
self.save_refresh_token(&user_id, &new_refresh_token, self.auth_config.refresh_token_expiration_days as i64).await?;
|
|
|
|
Ok((new_access_token, new_refresh_token))
|
|
}
|
|
|
|
/// 删除用户
|
|
pub async fn delete_user(&self, request: DeleteUserRequest) -> Result<()> {
|
|
let password_hash = self.user_repo.get_password_hash(&request.user_id).await?
|
|
.ok_or_else(|| anyhow::anyhow!("用户不存在"))?;
|
|
|
|
let parsed_hash = PasswordHash::new(&password_hash)
|
|
.map_err(|e| anyhow::anyhow!("解析密码哈希失败: {}", e))?;
|
|
let argon2 = Argon2::default();
|
|
|
|
argon2
|
|
.verify_password(request.password.as_bytes(), &parsed_hash)
|
|
.map_err(|_| anyhow::anyhow!("密码错误"))?;
|
|
|
|
self.user_repo.delete_by_id(&request.user_id).await?;
|
|
|
|
Ok(())
|
|
}
|
|
}
|