use crate::KenAuthError; use crate::models::users::users; use crate::protos::auth::v1::*; use sea_orm::sea_query::{Expr, ExprTrait}; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; use crate::AuthService; use crate::errors::{AuthError, AuthResult}; use crate::share::{auth_error as error, auth_store as store}; impl AuthService { pub(crate) async fn change_password_impl( &self, request: ChangePasswordRequest, request_id: Option<&str>, ) -> AuthResult { let db = self.db(); let user = store::load_active_user(db, request.user_id).await?; let stored = user.password_hash.clone(); if stored.is_empty() { return Err(AuthError::InvalidPassword); } if !stored.is_empty() { let check = crate::share::password::verify( request.old_password_hash.clone(), stored, request_id, ) .await?; if check == crate::share::password::PasswordCheck::Mismatch { return Err(AuthError::InvalidPassword); } } crate::share::password::validate_new_password( &request.new_password_hash, &self.auth().password_policy, )?; let new_hash = crate::share::password::hash(request.new_password_hash.clone(), request_id).await?; let now = store::now(); users::Entity::update_many() .col_expr(users::Column::PasswordHash, Expr::value(new_hash)) .col_expr(users::Column::PasswordUpdatedAt, Expr::value(Some(now))) .col_expr(users::Column::PasswordMustChange, Expr::value(false)) .col_expr( users::Column::SecurityVersion, Expr::col(users::Column::SecurityVersion).add(1), ) .col_expr(users::Column::UpdatedAt, Expr::value(now)) .filter(users::Column::Id.eq(user.id)) .exec(db.write()) .await?; let keep = match request.keep_session_id.trim() { "" => None, value => uuid::Uuid::parse_str(value).ok(), }; let mut update = crate::models::users::user_sessions::Entity::update_many() .col_expr( crate::models::users::user_sessions::Column::RevokedAt, Expr::value(Some(now)), ) .col_expr( crate::models::users::user_sessions::Column::Version, Expr::col(crate::models::users::user_sessions::Column::Version).add(1), ) .col_expr( crate::models::users::user_sessions::Column::UpdatedAt, Expr::value(now), ) .filter(crate::models::users::user_sessions::Column::UserId.eq(user.id)) .filter(crate::models::users::user_sessions::Column::RevokedAt.is_null()); if let Some(keep) = keep { update = update.filter(crate::models::users::user_sessions::Column::Id.ne(keep)); } let revoked = update.exec(db.write()).await?; if let Err(err) = store::drop_user_session_states(self.cache(), user.id, keep).await { tracing::warn!(%err, user_id = user.id, "failed to drop revoked sessions"); } crate::services::me::invalidate_me_cache(self.cache(), user.id).await; store::audit( db, user.id, Some(user.id), crate::models::users::user_events::UserEventType::PasswordChange, Some(serde_json::json!({ "action": "password_changed" })), request_id, match request.client_ip.trim() { "" => None, value => Some(value.to_owned()), }, None, ) .await?; let security_version = store::load_user(db, user.id).await?.security_version; if let Some(keep) = keep { if let Err(err) = store::refresh_session_version( self.cache(), user.id, keep, security_version, self.session_max_age_secs(), ) .await { tracing::warn!(%err, user_id = user.id, "failed to refresh the kept session version"); } } Ok(ChangePasswordResponse { changed: true, security_version, revoked_sessions: revoked.rows_affected as u32, }) } } impl AuthService { #[tracing::instrument(skip_all, fields(request_id = %envelope::context::resolve_request_id(request_id)))] pub(crate) async fn change_password_inner( &self, request_id: Option<&str>, payload: ChangePasswordRequest, ) -> Result { self.change_password_impl(payload, request_id) .await .map_err(|failure| error::status(&failure, request_id)) } }