tuwunel_api_client/account/
change_password.rs1use axum::extract::State;
2use ruma::{
3 OwnedUserId,
4 api::client::{
5 account::change_password,
6 uiaa::{AuthData, EmailIdentity, ThirdpartyIdCredentials},
7 },
8 thirdparty::Medium,
9};
10use tuwunel_core::{Err, Error, Result, info};
11
12use crate::{ClientIp, Ruma, router::auth_uiaa};
13
14#[tracing::instrument(skip_all, fields(%client), name = "change_password")]
35pub(crate) async fn change_password_route(
36 State(services): State<crate::State>,
37 ClientIp(client): ClientIp,
38 body: Ruma<change_password::v3::Request>,
39) -> Result<change_password::v3::Response> {
40 let sender_user = match (body.sender_user.as_ref(), body.auth.as_ref()) {
41 | (None, Some(AuthData::EmailIdentity(EmailIdentity { thirdparty_id_creds, .. }))) =>
42 redeem_password_reset(services, thirdparty_id_creds).await?,
43 | (None, _) => return Err!(Request(MissingToken("Missing access token."))),
44 | (Some(_), _) => auth_uiaa(&services, &body).await?,
45 };
46
47 services
48 .users
49 .set_password_if_active(&sender_user, Some(&body.new_password))
50 .await?;
51
52 if body.logout_devices {
53 services
55 .users
56 .remove_devices(&sender_user, body.sender_device.as_deref())
57 .await;
58 }
59
60 info!("User {sender_user} changed their password.");
61
62 services
63 .admin
64 .notify(&format!("User {sender_user} changed their password."))
65 .await;
66
67 Ok(change_password::v3::Response {})
68}
69
70#[tracing::instrument(level = "debug", skip_all)]
71async fn redeem_password_reset(
72 services: crate::State,
73 creds: &ThirdpartyIdCredentials,
74) -> Result<OwnedUserId> {
75 let association = match services
76 .threepid
77 .redeem_validated(creds.sid.as_str(), creds.client_secret.as_str())
78 .await
79 {
80 | Ok(association) => association,
81 | Err(error) if error.is_not_found() || matches!(&error, Error::Request(..)) => {
82 return Err!(Request(Forbidden("Invalid email identity proof.")));
83 },
84 | Err(error) => return Err(error),
85 };
86
87 if association.medium != Medium::Email {
88 return Err!(Request(Forbidden("Invalid email identity proof.")));
89 }
90
91 let Some(user_id) = services
92 .threepid
93 .user_id_for_email(&association.address)
94 .await?
95 else {
96 return Err!(Request(Forbidden("Invalid email identity proof.")));
97 };
98
99 services.users.deactivated_check(&user_id).await?;
102
103 Ok(user_id)
104}