feat(auth): add change password API and frontend page
Backend: - Add ChangePasswordReq DTO with validation (current + new password) - Add AuthService::change_password() method with credential verification, password rehash, and token revocation - Add POST /api/v1/auth/change-password endpoint with utoipa annotation Frontend: - Add changePassword() API function in auth.ts - Add ChangePassword.tsx page with form validation and confirmation - Add "修改密码" tab in Settings page After password change, all refresh tokens are revoked and the user is redirected to the login page.
This commit is contained in:
@@ -210,6 +210,60 @@ impl AuthService {
|
||||
TokenService::revoke_all_user_tokens(user_id, tenant_id, db).await
|
||||
}
|
||||
|
||||
/// Change password for the authenticated user.
|
||||
///
|
||||
/// Steps:
|
||||
/// 1. Verify current password
|
||||
/// 2. Hash the new password
|
||||
/// 3. Update the credential record
|
||||
/// 4. Revoke all existing refresh tokens (force re-login)
|
||||
pub async fn change_password(
|
||||
user_id: Uuid,
|
||||
tenant_id: Uuid,
|
||||
current_password: &str,
|
||||
new_password: &str,
|
||||
db: &sea_orm::DatabaseConnection,
|
||||
) -> AuthResult<()> {
|
||||
// 1. Find the user's password credential
|
||||
let cred = user_credential::Entity::find()
|
||||
.filter(user_credential::Column::UserId.eq(user_id))
|
||||
.filter(user_credential::Column::TenantId.eq(tenant_id))
|
||||
.filter(user_credential::Column::CredentialType.eq("password"))
|
||||
.filter(user_credential::Column::DeletedAt.is_null())
|
||||
.one(db)
|
||||
.await
|
||||
.map_err(|e| AuthError::Validation(e.to_string()))?
|
||||
.ok_or_else(|| AuthError::Validation("用户凭证不存在".to_string()))?;
|
||||
|
||||
// 2. Verify current password
|
||||
let stored_hash = cred
|
||||
.credential_data
|
||||
.as_ref()
|
||||
.and_then(|v| v.get("hash").and_then(|h| h.as_str()))
|
||||
.ok_or_else(|| AuthError::Validation("用户凭证异常".to_string()))?;
|
||||
|
||||
if !password::verify_password(current_password, stored_hash)? {
|
||||
return Err(AuthError::Validation("当前密码不正确".to_string()));
|
||||
}
|
||||
|
||||
// 3. Hash new password and update credential
|
||||
let new_hash = password::hash_password(new_password)?;
|
||||
let mut cred_active: user_credential::ActiveModel = cred.into();
|
||||
cred_active.credential_data = Set(Some(serde_json::json!({ "hash": new_hash })));
|
||||
cred_active.updated_at = Set(Utc::now());
|
||||
cred_active.version = Set(2);
|
||||
cred_active
|
||||
.update(db)
|
||||
.await
|
||||
.map_err(|e| AuthError::Validation(e.to_string()))?;
|
||||
|
||||
// 4. Revoke all refresh tokens — force re-login on all devices
|
||||
TokenService::revoke_all_user_tokens(user_id, tenant_id, db).await?;
|
||||
|
||||
tracing::info!(user_id = %user_id, "Password changed successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Fetch role details for a user, returning RoleResp DTOs.
|
||||
async fn get_user_role_resps(
|
||||
user_id: Uuid,
|
||||
|
||||
Reference in New Issue
Block a user