Files
gitks/service/auth/change_password.rs
T
zhenyi 420dedbc1e feat(service): expand service layer with new domain operations
- Add IM service modules: audit, channel roles, custom emojis, forum
  tags, integrations, invitations, repo links, slash commands, stages,
  voice, webhooks
- Add PR service modules: review requests, templates
- Add repo service modules: contributors, release assets, git extras
  (archive, branch rename, commit extras, diff/merge, tag, tree)
- Add user service: social (follow/block)
- Add internal auth service
- Update existing service modules with expanded functionality
- Remove deleted IM modules: articles, delivery trace, drafts,
  follows, messages, polls, presence, reactions, threads
2026-06-10 18:49:32 +08:00

80 lines
2.5 KiB
Rust

use argon2::{
Argon2, PasswordHasher,
password_hash::{PasswordHash, PasswordVerifier, SaltString},
};
use chrono::Utc;
use serde::{Deserialize, Serialize};
use sqlx::Row;
use crate::error::AppError;
use crate::service::AuthService;
use crate::session::Session;
#[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)]
pub struct ChangePasswordParams {
pub current_password: String,
pub new_password: String,
}
impl AuthService {
pub async fn auth_change_password(
&self,
session: &Session,
params: ChangePasswordParams,
) -> Result<(), AppError> {
let user_uid = session.user().ok_or(AppError::Unauthorized)?;
let current_password = self
.auth_rsa_decode(session, params.current_password)
.await?;
let row = sqlx::query("SELECT password_hash FROM user_password WHERE user_id = $1")
.bind(user_uid)
.fetch_optional(self.ctx.db.writer())
.await
.map_err(AppError::Database)?
.ok_or(AppError::UserNotFound)?;
let hash: String = row.try_get("password_hash").map_err(AppError::Database)?;
let password_hash = PasswordHash::new(&hash).map_err(|_| AppError::InvalidPassword)?;
if Argon2::default()
.verify_password(current_password.as_bytes(), &password_hash)
.is_err()
{
return Err(AppError::InvalidPassword);
}
let new_password = self.auth_rsa_decode(session, params.new_password).await?;
crate::service::util::validate_password_strength(&new_password)?;
let salt = SaltString::generate(&mut rand::thread_rng());
let new_hash = Argon2::default()
.hash_password(new_password.as_bytes(), &salt)
.map_err(|e| AppError::PasswordHashError(e.to_string()))?
.to_string();
let now = Utc::now();
let result = sqlx::query(
"UPDATE user_password SET password_hash = $1, password_updated_at = $2, updated_at = $2 \
WHERE user_id = $3",
)
.bind(&new_hash)
.bind(now)
.bind(user_uid)
.execute(self.ctx.db.writer())
.await
.map_err(AppError::Database)?;
if result.rows_affected() == 0 {
return Err(AppError::UserNotFound);
}
session.remove(Self::RSA_PRIVATE_KEY);
session.remove(Self::RSA_PUBLIC_KEY);
tracing::info!(user_uid = %user_uid, "Password changed successfully");
Ok(())
}
}