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
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user