use dioxus::prelude::*;
#[component]
pub fn PasswordResetForm() -> Element {
let mut homeserver = use_signal(|| "https://matrix.org".to_string());
let mut email = use_signal(|| String::new());
let mut error_message = use_signal(|| Option::<String>::None);
let mut success = use_signal(|| false);
let mut is_loading = use_signal(|| false);
let on_submit = move |evt: Event<FormData>| {
evt.prevent_default();
let hs = homeserver.read().clone();
let email_val = email.read().clone();
if email_val.is_empty() {
error_message.set(Some("Please enter your email address".to_string()));
return;
}
is_loading.set(true);
error_message.set(None);
spawn(async move {
match request_password_reset(&hs, &email_val).await {
Ok(()) => {
success.set(true);
}
Err(e) => {
error_message.set(Some(e));
}
}
is_loading.set(false);
});
};
if *success.read() {
return rsx! {
div {
class: "password-reset__success",
h3 { "Check your email" }
p { "We've sent a password reset link to your email address." }
p { "Follow the instructions in the email to reset your password, then return here to sign in." }
}
};
}
rsx! {
form {
class: "password-reset-form",
onsubmit: on_submit,
if let Some(ref err) = *error_message.read() {
div {
class: "password-reset-form__error",
"{err}"
}
}
p {
class: "password-reset-form__description",
"Enter the email address associated with your account and we'll send you a link to reset your password."
}
div {
class: "password-reset-form__field",
label { r#for: "reset-homeserver", "Homeserver" }
input {
id: "reset-homeserver",
r#type: "url",
placeholder: "https://matrix.org",
value: "{homeserver}",
oninput: move |evt| homeserver.set(evt.value()),
disabled: *is_loading.read(),
}
}
div {
class: "password-reset-form__field",
label { r#for: "reset-email", "Email" }
input {
id: "reset-email",
r#type: "email",
placeholder: "your@email.com",
value: "{email}",
oninput: move |evt| email.set(evt.value()),
disabled: *is_loading.read(),
}
}
button {
r#type: "submit",
class: "password-reset-form__submit",
disabled: *is_loading.read(),
if *is_loading.read() {
"Sending..."
} else {
"Reset Password"
}
}
}
}
}
async fn request_password_reset(homeserver: &str, email: &str) -> Result<(), String> {
let client = crate::client::build_client(homeserver)
.await
.map_err(|e| format!("Failed to connect to homeserver: {e}"))?;
use matrix_sdk::ruma::api::client::account::request_password_change_token_via_email;
let secret_str = uuid::Uuid::new_v4().to_string();
let client_secret: matrix_sdk::ruma::OwnedClientSecret = secret_str.try_into()
.map_err(|_| "Failed to create client secret".to_string())?;
let request = request_password_change_token_via_email::v3::Request::new(
client_secret,
email.to_owned(),
1u32.into(),
);
match client.send(request).await {
Ok(_response) => {
tracing::info!("Password reset email sent to {email}");
Ok(())
}
Err(e) => {
let err_str = e.to_string();
if err_str.contains("M_THREEPID_NOT_FOUND") {
Err("No account found with that email address".to_string())
} else if err_str.contains("M_SERVER_NOT_TRUSTED") {
Err("The identity server is not trusted by this homeserver".to_string())
} else {
Err(format!("Failed to send reset email: {e}"))
}
}
}
}