main
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<ChangePasswordResponse> {
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<ChangePasswordResponse, KenAuthError> {
self.change_password_impl(payload, request_id)
.await
.map_err(|failure| error::status(&failure, request_id))
}
}