continuwuity/src/web/pages/password_reset.rs
2026-03-18 12:42:55 -04:00

117 lines
2.9 KiB
Rust

use askama::Template;
use axum::{
Router,
extract::{Query, State},
http::StatusCode,
response::{Html, IntoResponse, Response},
routing::get,
};
use conduwuit_service::password_reset::PASSWORD_RESET_PATH;
use serde::Deserialize;
use validator::Validate;
use crate::{
WebError, form,
pages::components::{UserCard, form::Form},
};
#[derive(Deserialize)]
struct PasswordResetQuery {
token: String,
}
#[derive(Debug, Template)]
#[template(path = "password_reset.html.j2")]
struct PasswordReset<'a> {
user_card: UserCard<'a>,
body: PasswordResetBody,
}
#[derive(Debug)]
enum PasswordResetBody {
Form(Form<'static>),
Success,
}
form! {
struct PasswordResetForm {
#[validate(length(min = 1, message = "Password cannot be empty"))]
new_password: String where {
input_type: "password",
label: "New password",
autocomplete: "new-password"
},
#[validate(must_match(other = "new_password", message = "Passwords must match"))]
confirm_new_password: String where {
input_type: "password",
label: "Confirm new password",
autocomplete: "new-password"
}
submit: "Reset Password"
}
}
pub(crate) fn build() -> Router<crate::State> {
Router::new().route(PASSWORD_RESET_PATH, get(get_password_reset).post(post_password_reset))
}
async fn password_reset_form(
services: crate::State,
query: PasswordResetQuery,
reset_form: Form<'static>,
) -> Result<impl IntoResponse, WebError> {
let Some(token) = services.password_reset.check_token(&query.token).await else {
return Err(WebError::BadRequest("Invalid reset token".to_owned()));
};
let user_card = UserCard::for_local_user(&services, &token.info.user).await;
let template = PasswordReset {
user_card,
body: PasswordResetBody::Form(reset_form),
};
Ok(Html(template.render()?))
}
async fn get_password_reset(
State(services): State<crate::State>,
Query(query): Query<PasswordResetQuery>,
) -> Result<impl IntoResponse, WebError> {
password_reset_form(services, query, PasswordResetForm::build(None)).await
}
async fn post_password_reset(
State(services): State<crate::State>,
Query(query): Query<PasswordResetQuery>,
axum::Form(form): axum::Form<PasswordResetForm>,
) -> Result<Response, WebError> {
match form.validate() {
| Ok(()) => {
let Some(token) = services.password_reset.check_token(&query.token).await else {
return Err(WebError::BadRequest("Invalid reset token".to_owned()));
};
let user_id = token.info.user.clone();
services
.password_reset
.consume_token(token, &form.new_password)
.await?;
let user_card = UserCard::for_local_user(&services, &user_id).await;
let template = PasswordReset {
user_card,
body: PasswordResetBody::Success,
};
Ok(Html(template.render()?).into_response())
},
| Err(err) => Ok((
StatusCode::BAD_REQUEST,
password_reset_form(services, query, PasswordResetForm::build(Some(err))).await,
)
.into_response()),
}
}