Cleanup the other auth forms
Somewhat kinda progressively enhanced, but at least I'm using page partials now... mostly.
This commit is contained in:
parent
7abffb2729
commit
6c23b3aaa3
@ -2,7 +2,7 @@ use axum::extract::{Query, State};
|
|||||||
use axum::response::Response;
|
use axum::response::Response;
|
||||||
use axum::{Form, TypedHeader};
|
use axum::{Form, TypedHeader};
|
||||||
use lettre::SmtpTransport;
|
use lettre::SmtpTransport;
|
||||||
use maud::html;
|
use maud::{html, Markup};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_with::{serde_as, NoneAsEmptyString};
|
use serde_with::{serde_as, NoneAsEmptyString};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
@ -24,6 +24,46 @@ pub struct ConfirmEmailQuery {
|
|||||||
pub token_id: Option<Base62Uuid>,
|
pub token_id: Option<Base62Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ConfirmEmailPageProps<'a> {
|
||||||
|
pub hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
|
pub layout: Layout,
|
||||||
|
pub form_props: ConfirmEmailFormProps,
|
||||||
|
pub header: Option<&'a str>,
|
||||||
|
pub desc: Option<Markup>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn confirm_email_page(
|
||||||
|
ConfirmEmailPageProps {
|
||||||
|
hx_target,
|
||||||
|
layout,
|
||||||
|
form_props,
|
||||||
|
header,
|
||||||
|
desc,
|
||||||
|
}: ConfirmEmailPageProps,
|
||||||
|
) -> Response {
|
||||||
|
layout
|
||||||
|
.with_subtitle("confirm email")
|
||||||
|
.targeted(hx_target)
|
||||||
|
.render(html! {
|
||||||
|
div class="center-horizontal" {
|
||||||
|
header class="center-text" {
|
||||||
|
h2 { (header.unwrap_or("Confirm your email address")) }
|
||||||
|
}
|
||||||
|
@if let Some(desc) = desc {
|
||||||
|
(desc)
|
||||||
|
} @else {
|
||||||
|
p class="readable-width" {
|
||||||
|
"Enter your email to resend the confirmation email. If you don't have an account yet, create one "
|
||||||
|
a href="/register" { "here" }
|
||||||
|
"."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(confirm_email_form(form_props))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
auth: AuthContext,
|
auth: AuthContext,
|
||||||
@ -38,36 +78,33 @@ pub async fn get(
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Error::NotFoundUuid(_, _) = err {
|
if let Error::NotFoundUuid(_, _) = err {
|
||||||
warn!(token_id = %token_id.as_uuid(), "token not found in database");
|
warn!(token_id = %token_id.as_uuid(), "token not found in database");
|
||||||
return Ok(layout
|
return Ok(confirm_email_page(ConfirmEmailPageProps {
|
||||||
.with_subtitle("confirm email")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ConfirmEmailFormProps::default(),
|
||||||
div class="center-horizontal" {
|
header: Some("Email verification token not found"),
|
||||||
header class="center-text" {
|
..Default::default()
|
||||||
h2 { "Email verification token not found" }
|
}));
|
||||||
}
|
|
||||||
p class="readable-width" { "Enter your email to resend the confirmation email. If you don't have an account yet, create one " a href="/register" { "here" } "." }
|
|
||||||
(confirm_email_form(ConfirmEmailFormProps::default()))
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if token.expired() {
|
if token.expired() {
|
||||||
warn!(token_id = %token.token_id, "token expired");
|
warn!(token_id = %token.token_id, "token expired");
|
||||||
Ok(layout
|
Ok(confirm_email_page(ConfirmEmailPageProps {
|
||||||
.with_subtitle("confirm email")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ConfirmEmailFormProps {
|
||||||
div class="center-horizontal" {
|
token: Some(token),
|
||||||
header class="center-text" {
|
..Default::default()
|
||||||
h2 { "Email verification token is expired" }
|
},
|
||||||
}
|
header: Some("Email verification token is expired"),
|
||||||
p class="readable-width" { "Click the button below to resend a new confirmation email. The link in the email will be valid for another 24 hours."}
|
desc: Some(html! {
|
||||||
(confirm_email_form(ConfirmEmailFormProps { token: Some(token), email: None }))
|
p class="readable-width" {
|
||||||
|
"Click the button below to resend a new confirmation email. The link in the email will be valid for another 24 hours."
|
||||||
}
|
}
|
||||||
}))
|
}),
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
info!(token_id = %token.token_id, "token valid, verifying email");
|
info!(token_id = %token.token_id, "token valid, verifying email");
|
||||||
User::verify_email(&pool, token.user_id).await?;
|
User::verify_email(&pool, token.user_id).await?;
|
||||||
@ -88,23 +125,15 @@ pub async fn get(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(layout
|
Ok(confirm_email_page(ConfirmEmailPageProps {
|
||||||
.with_subtitle("confirm email")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ConfirmEmailFormProps {
|
||||||
div class="center-horizontal" {
|
email: auth.current_user.map(|u| u.email),
|
||||||
header class="center-text" {
|
..Default::default()
|
||||||
h2 { "Confirm your email address" }
|
},
|
||||||
}
|
..Default::default()
|
||||||
p class="readable-width" { "An email was sent to your email address upon registration containing a link that will confirm your email address. If you can't find it or it has been more than 24 hours since it was sent, you can resend the email by submitting the form below:"}
|
}))
|
||||||
(confirm_email_form(
|
|
||||||
ConfirmEmailFormProps {
|
|
||||||
token: None,
|
|
||||||
email: auth.current_user.map(|u| u.email),
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -172,21 +201,17 @@ pub async fn post(
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Ok(layout
|
Ok(confirm_email_page(ConfirmEmailPageProps {
|
||||||
.with_subtitle("confirm email")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ConfirmEmailFormProps::default(),
|
||||||
div class="center-horizontal" {
|
header: Some("Email verification token not found"),
|
||||||
header class="center-text" {
|
desc: Some(html! {
|
||||||
h2 { "Email verification token not found" }
|
p class="readable-width" {
|
||||||
}
|
"Enter your email to resend the confirmation email. If you don't have an account yet, create one "
|
||||||
p class="readable-width" { "Enter your email to resend the confirmation email." }
|
a href="/register" { "here" }
|
||||||
p class="readable-width" {
|
"."
|
||||||
"If you don't have an account yet, create one "
|
|
||||||
a href="/register" { "here" }
|
|
||||||
"."
|
|
||||||
}
|
|
||||||
(confirm_email_form(ConfirmEmailFormProps::default()))
|
|
||||||
}
|
}
|
||||||
}))
|
}),
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,26 @@ pub fn forgot_password_page(
|
|||||||
.into_response()
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn confirm_forgot_password_sent_page(
|
||||||
|
hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
|
layout: Layout,
|
||||||
|
) -> Response {
|
||||||
|
layout
|
||||||
|
.with_subtitle("forgot password")
|
||||||
|
.targeted(hx_target)
|
||||||
|
.render(html! {
|
||||||
|
div class="center-horizontal" {
|
||||||
|
header class="center-text" {
|
||||||
|
h2 { "Reset password email sent" }
|
||||||
|
}
|
||||||
|
p class="readable-width" {
|
||||||
|
"If the email you entered matched an existing account with a verified email, then a password reset email was sent. Please follow the link sent in the email."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
auth: AuthContext,
|
auth: AuthContext,
|
||||||
hx_target: Option<TypedHeader<HXTarget>>,
|
hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
@ -76,19 +96,7 @@ pub async fn post(
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Error::NotFoundString(_, _) = err {
|
if let Error::NotFoundString(_, _) = err {
|
||||||
info!(email = forgot_password.email, "invalid email");
|
info!(email = forgot_password.email, "invalid email");
|
||||||
return Ok(layout
|
return Ok(confirm_forgot_password_sent_page(hx_target, layout));
|
||||||
.with_subtitle("forgot password")
|
|
||||||
.targeted(hx_target)
|
|
||||||
.render(html! {
|
|
||||||
div class="center-horizontal" {
|
|
||||||
header class="center-text" {
|
|
||||||
h2 { "Reset password email sent" }
|
|
||||||
}
|
|
||||||
p class="readable-width" {
|
|
||||||
"If the email you entered matched an existing account with a verified email, then a password reset email was sent. Please follow the link sent in the email."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
@ -107,17 +115,5 @@ pub async fn post(
|
|||||||
} else {
|
} else {
|
||||||
warn!(user_id = %user.user_id, "user exists with unverified email, skip sending password reset email");
|
warn!(user_id = %user.user_id, "user exists with unverified email, skip sending password reset email");
|
||||||
}
|
}
|
||||||
Ok(layout
|
Ok(confirm_forgot_password_sent_page(hx_target, layout))
|
||||||
.with_subtitle("forgot password")
|
|
||||||
.targeted(hx_target)
|
|
||||||
.render(html! {
|
|
||||||
div class="center-horizontal" {
|
|
||||||
header class="center-text" {
|
|
||||||
h2 { "Reset password email sent" }
|
|
||||||
}
|
|
||||||
p class="readable-width" {
|
|
||||||
"If the email you entered matched an existing account with a verified email, then a password reset email was sent. Please follow the link sent in the email."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,6 @@ pub fn register_page(
|
|||||||
(register_form(form_props))
|
(register_form(form_props))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into_response()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(hx_target: Option<TypedHeader<HXTarget>>, layout: Layout) -> Result<Response> {
|
pub async fn get(hx_target: Option<TypedHeader<HXTarget>>, layout: Layout) -> Result<Response> {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use axum::extract::Query;
|
use axum::extract::Query;
|
||||||
use axum::response::{IntoResponse, Response};
|
use axum::response::Response;
|
||||||
use axum::TypedHeader;
|
use axum::TypedHeader;
|
||||||
use axum::{extract::State, Form};
|
use axum::{extract::State, Form};
|
||||||
use axum_client_ip::SecureClientIp;
|
use axum_client_ip::SecureClientIp;
|
||||||
@ -36,26 +36,84 @@ pub struct ResetPasswordQuery {
|
|||||||
pub token_id: Option<Base62Uuid>,
|
pub token_id: Option<Base62Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_password_page(
|
#[derive(Debug, Default)]
|
||||||
hx_target: Option<TypedHeader<HXTarget>>,
|
pub struct InvalidTokenPageProps<'a> {
|
||||||
layout: Layout,
|
pub hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
form_props: ResetPasswordFormProps,
|
pub layout: Layout,
|
||||||
|
pub header: Option<&'a str>,
|
||||||
|
pub desc: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn invalid_token_page(
|
||||||
|
InvalidTokenPageProps {
|
||||||
|
hx_target,
|
||||||
|
layout,
|
||||||
|
header,
|
||||||
|
desc,
|
||||||
|
}: InvalidTokenPageProps,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
layout
|
layout
|
||||||
.with_subtitle("forgot password")
|
.with_subtitle("reset password")
|
||||||
.targeted(hx_target)
|
.targeted(hx_target)
|
||||||
.render(html! {
|
.render(html! {
|
||||||
div class="center-horizontal" {
|
div class="center-horizontal" {
|
||||||
header class="center-text" {
|
header class="center-text" {
|
||||||
h2 { "Reset Password" }
|
h2 { (header.unwrap_or("Reset Password")) }
|
||||||
}
|
}
|
||||||
p {
|
@if let Some(desc) = desc {
|
||||||
|
p class="readable-width" { (desc) }
|
||||||
|
}
|
||||||
|
p class="readable-width" {
|
||||||
|
a href="/forgot-password" {
|
||||||
|
"Follow this link to request a new password reset email"
|
||||||
|
}
|
||||||
|
"."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct ResetPasswordPageProps<'a> {
|
||||||
|
pub hx_target: Option<TypedHeader<HXTarget>>,
|
||||||
|
pub layout: Layout,
|
||||||
|
pub form_props: ResetPasswordFormProps,
|
||||||
|
pub header: Option<&'a str>,
|
||||||
|
pub post_form_error: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_password_page(
|
||||||
|
ResetPasswordPageProps {
|
||||||
|
hx_target,
|
||||||
|
layout,
|
||||||
|
form_props,
|
||||||
|
header,
|
||||||
|
post_form_error,
|
||||||
|
}: ResetPasswordPageProps,
|
||||||
|
) -> Response {
|
||||||
|
layout
|
||||||
|
.with_subtitle("reset password")
|
||||||
|
.targeted(hx_target)
|
||||||
|
.render(html! {
|
||||||
|
div class="center-horizontal" {
|
||||||
|
header class="center-text" {
|
||||||
|
h2 { (header.unwrap_or("Reset Password")) }
|
||||||
|
}
|
||||||
|
p class="readable-width" {
|
||||||
"A password reset email will be sent if the email submitted matches an account in the system and the email is verfied. If your email is not verified, " a href="/confirm-email" { "please verify your email first" } "."
|
"A password reset email will be sent if the email submitted matches an account in the system and the email is verfied. If your email is not verified, " a href="/confirm-email" { "please verify your email first" } "."
|
||||||
}
|
}
|
||||||
(reset_password_form(form_props))
|
(reset_password_form(form_props))
|
||||||
|
@if let Some(post_form_error) = post_form_error {
|
||||||
|
p class="error readable-width" { (post_form_error) }
|
||||||
|
p class="readable-width" {
|
||||||
|
a href="/forgot-password" {
|
||||||
|
"Follow this link to request a new password reset email"
|
||||||
|
}
|
||||||
|
". The link in the email will be valid for 24 hours."
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.into_response()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
@ -71,68 +129,45 @@ pub async fn get(
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Error::NotFoundUuid(_, _) = err {
|
if let Error::NotFoundUuid(_, _) = err {
|
||||||
warn!(token_id = %token_id.as_uuid(), "token not found in database");
|
warn!(token_id = %token_id.as_uuid(), "token not found in database");
|
||||||
return Ok(layout
|
return Ok(invalid_token_page(InvalidTokenPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
header: Some("Password reset token not found"),
|
||||||
div class="center-horizontal" {
|
desc: Some("The reset password link has already been used or is invalid."),
|
||||||
header class="center-text" {
|
}));
|
||||||
h2 { "Password reset token not found" }
|
|
||||||
}
|
|
||||||
p class="readable-width" { "The reset password link has already been used or is invalid." }
|
|
||||||
p class="readable-width" { a href="/forgot-password" { "Follow this link to request a new password reset email" } "." }
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if token.expired() {
|
if token.expired() {
|
||||||
warn!(token_id = %token.token_id, "token expired");
|
warn!(token_id = %token.token_id, "token expired");
|
||||||
Ok(layout
|
Ok(invalid_token_page(InvalidTokenPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
header: Some("Password reset token expired"),
|
||||||
div class="center-horizontal" {
|
..Default::default()
|
||||||
header class="center-text" {
|
}))
|
||||||
h2 { "Password reset token is expired" }
|
|
||||||
}
|
|
||||||
p class="readable-width" { a href="/forgot-password" { "Follow this link to request a new password reset email" } ". The link in the email will be valid for 24 hours." }
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
} else {
|
} else {
|
||||||
info!(token_id = %token.token_id, "token valid, showing reset password form");
|
info!(token_id = %token.token_id, "token valid, showing reset password form");
|
||||||
let user = User::get(&pool, token.user_id).await?;
|
let user = User::get(&pool, token.user_id).await?;
|
||||||
Ok(layout
|
Ok(reset_password_page(ResetPasswordPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ResetPasswordFormProps {
|
||||||
div class="center-horizontal" {
|
token: token.token_id,
|
||||||
header class="center-text" {
|
email: user.email,
|
||||||
h2 { "Reset Password" }
|
..Default::default()
|
||||||
}
|
},
|
||||||
(reset_password_form(ResetPasswordFormProps {
|
..Default::default()
|
||||||
token: token.token_id,
|
}))
|
||||||
email: user.email,
|
|
||||||
password_error: None,
|
|
||||||
general_error: None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(layout
|
Ok(invalid_token_page(InvalidTokenPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
header: Some("Missing password reset token"),
|
||||||
div class="center-horizontal" {
|
desc: Some("Passwords can only be reset by requesting a password reset email and following the unique link within the email."),
|
||||||
header class="center-text" {
|
}))
|
||||||
h2 { "Missing password reset token" }
|
|
||||||
}
|
|
||||||
p class="readable-width" { "Passwords can only be reset by requesting a password reset email and following the unique link within the email."}
|
|
||||||
p class="readable-width" { a href="/forgot-password" { "Follow this link to request a new password reset email" } "." }
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,94 +182,75 @@ pub async fn post(
|
|||||||
Form(reset_password): Form<ResetPassword>,
|
Form(reset_password): Form<ResetPassword>,
|
||||||
) -> Result<Response> {
|
) -> Result<Response> {
|
||||||
if reset_password.password != reset_password.password_confirmation {
|
if reset_password.password != reset_password.password_confirmation {
|
||||||
return Ok(layout
|
return Ok(reset_password_page(ResetPasswordPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ResetPasswordFormProps {
|
||||||
div class="center-horizontal" {
|
token: reset_password.token,
|
||||||
header class="center-text" {
|
email: reset_password.email,
|
||||||
h2 { "Reset Password" }
|
password_error: Some("passwords do not match".to_string()),
|
||||||
}
|
..Default::default()
|
||||||
(reset_password_form(ResetPasswordFormProps {
|
},
|
||||||
token: reset_password.token,
|
..Default::default()
|
||||||
email: reset_password.email,
|
}));
|
||||||
password_error: Some("passwords do not match".to_string()),
|
|
||||||
general_error: None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
let token = match UserPasswordResetToken::get(&pool, reset_password.token).await {
|
let token = match UserPasswordResetToken::get(&pool, reset_password.token).await {
|
||||||
Ok(token) => token,
|
Ok(token) => token,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Error::NotFoundUuid(_, _) = err {
|
if let Error::NotFoundUuid(_, _) = err {
|
||||||
warn!(token_id = %reset_password.token, "token not found in database");
|
warn!(token_id = %reset_password.token, "token not found in database");
|
||||||
return Ok(layout
|
return Ok(reset_password_page(ResetPasswordPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ResetPasswordFormProps {
|
||||||
div class="center-horizontal" {
|
token: reset_password.token,
|
||||||
header class="center-text" {
|
email: reset_password.email,
|
||||||
h2 { "Reset Password" }
|
general_error: Some("token not found".to_string()),
|
||||||
}
|
..Default::default()
|
||||||
(reset_password_form(ResetPasswordFormProps {
|
},
|
||||||
token: reset_password.token,
|
post_form_error: Some(
|
||||||
email: reset_password.email,
|
"The reset password link has already been used or is invalid.",
|
||||||
password_error: None,
|
),
|
||||||
general_error: Some("token not found".to_string()),
|
..Default::default()
|
||||||
}))
|
}));
|
||||||
p class="error readable-width" { "The reset password link has already been used or is invalid." }
|
|
||||||
p class="readable-width" { a href="/forgot-password" { "Follow this link to request a new password reset email" } "." }
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if token.expired() {
|
if token.expired() {
|
||||||
warn!(token_id = %token.token_id, "token expired");
|
warn!(token_id = %token.token_id, "token expired");
|
||||||
return Ok(layout
|
return Ok(reset_password_page(ResetPasswordPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ResetPasswordFormProps {
|
||||||
div class="center-horizontal" {
|
token: reset_password.token,
|
||||||
header class="center-text" {
|
email: reset_password.email,
|
||||||
h2 { "Reset Password" }
|
general_error: Some("token expired".to_string()),
|
||||||
}
|
..Default::default()
|
||||||
(reset_password_form(ResetPasswordFormProps {
|
},
|
||||||
token: reset_password.token,
|
post_form_error: Some("The reset password link has expired."),
|
||||||
email: reset_password.email,
|
..Default::default()
|
||||||
password_error: None,
|
}));
|
||||||
general_error: Some("token expired".to_string()),
|
|
||||||
}))
|
|
||||||
p class="error readable-width" { "The reset password link has expired." }
|
|
||||||
p class="readable-width" { a href="/forgot-password" { "Follow this link to request a new password reset email" } ". The link in the email will be valid for 24 hours." }
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
let user = match User::get(&pool, token.user_id).await {
|
let user = match User::get(&pool, token.user_id).await {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Error::NotFoundString(_, _) = err {
|
if let Error::NotFoundString(_, _) = err {
|
||||||
info!(user_id = %token.user_id, email = reset_password.email, "invalid token user_id");
|
info!(user_id = %token.user_id, email = reset_password.email, "invalid token user_id");
|
||||||
return Ok(layout
|
return Ok(reset_password_page(ResetPasswordPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ResetPasswordFormProps {
|
||||||
div class="center-horizontal" {
|
token: reset_password.token,
|
||||||
header class="center-text" {
|
email: reset_password.email,
|
||||||
h2 { "Reset Password" }
|
general_error: Some("user not found".to_string()),
|
||||||
}
|
..Default::default()
|
||||||
(reset_password_form(ResetPasswordFormProps {
|
},
|
||||||
token: reset_password.token,
|
post_form_error: Some(
|
||||||
email: reset_password.email,
|
"The user associated with this password reset could not be found.",
|
||||||
password_error: None,
|
),
|
||||||
general_error: Some("user not found".to_string()),
|
..Default::default()
|
||||||
}))
|
}));
|
||||||
p class="error readable-width" { "The user associated with this password reset could not be found." }
|
|
||||||
p class="readable-width" { a href="/forgot-password" { "Follow this link to request a new password reset email" } "." }
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else {
|
} else {
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
@ -256,28 +272,23 @@ pub async fn post(
|
|||||||
Err(err) => {
|
Err(err) => {
|
||||||
if let Error::InvalidEntity(validation_errors) = err {
|
if let Error::InvalidEntity(validation_errors) = err {
|
||||||
let field_errors = validation_errors.field_errors();
|
let field_errors = validation_errors.field_errors();
|
||||||
return Ok(layout
|
return Ok(reset_password_page(ResetPasswordPageProps {
|
||||||
.with_subtitle("reset password")
|
hx_target,
|
||||||
.targeted(hx_target)
|
layout,
|
||||||
.render(html! {
|
form_props: ResetPasswordFormProps {
|
||||||
div class="center-horizontal" {
|
token: reset_password.token,
|
||||||
header class="center-text" {
|
email: reset_password.email,
|
||||||
h2 { "Reset Password" }
|
password_error: field_errors.get("password").map(|&errors| {
|
||||||
}
|
errors
|
||||||
(reset_password_form(ResetPasswordFormProps {
|
.iter()
|
||||||
token: reset_password.token,
|
.filter_map(|error| error.message.clone().map(|m| m.to_string()))
|
||||||
email: reset_password.email,
|
.collect::<Vec<String>>()
|
||||||
password_error: field_errors.get("password").map(|&errors| {
|
.join(", ")
|
||||||
errors
|
}),
|
||||||
.iter()
|
..Default::default()
|
||||||
.filter_map(|error| error.message.clone().map(|m| m.to_string()))
|
},
|
||||||
.collect::<Vec<String>>()
|
..Default::default()
|
||||||
.join(", ")
|
}));
|
||||||
}),
|
|
||||||
general_error: None,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,13 @@ pub struct ConfirmEmailFormProps {
|
|||||||
pub fn confirm_email_form(props: ConfirmEmailFormProps) -> Markup {
|
pub fn confirm_email_form(props: ConfirmEmailFormProps) -> Markup {
|
||||||
let ConfirmEmailFormProps { token, email } = props;
|
let ConfirmEmailFormProps { token, email } = props;
|
||||||
html! {
|
html! {
|
||||||
form action="/confirm-email" method="post" class="auth-form-grid" {
|
form
|
||||||
|
action="/confirm-email"
|
||||||
|
method="post"
|
||||||
|
hx-post="/confirm-email"
|
||||||
|
id="confirm-email-form"
|
||||||
|
class="auth-form-grid"
|
||||||
|
{
|
||||||
input
|
input
|
||||||
type="text"
|
type="text"
|
||||||
name="token"
|
name="token"
|
||||||
|
@ -9,7 +9,13 @@ pub struct ForgotPasswordFormProps {
|
|||||||
pub fn forgot_password_form(props: ForgotPasswordFormProps) -> Markup {
|
pub fn forgot_password_form(props: ForgotPasswordFormProps) -> Markup {
|
||||||
let ForgotPasswordFormProps { email, email_error } = props;
|
let ForgotPasswordFormProps { email, email_error } = props;
|
||||||
html! {
|
html! {
|
||||||
form action="forgot-password" method="post" class="auth-form-grid" {
|
form
|
||||||
|
action="/forgot-password"
|
||||||
|
method="post"
|
||||||
|
hx-post="/forgot-password"
|
||||||
|
id="forgot-password-form"
|
||||||
|
class="auth-form-grid"
|
||||||
|
{
|
||||||
label for="email" { "Email" }
|
label for="email" { "Email" }
|
||||||
input
|
input
|
||||||
type="email"
|
type="email"
|
||||||
|
@ -2,10 +2,20 @@ use maud::{html, Markup, PreEscaped};
|
|||||||
|
|
||||||
pub fn opml_import_form() -> Markup {
|
pub fn opml_import_form() -> Markup {
|
||||||
html! {
|
html! {
|
||||||
form id="opml-import-form" hx-post="/import/opml" hx-swap="outerHTML" hx-encoding="multipart/form-data" class="feed-form" {
|
form
|
||||||
|
id="opml-import-form"
|
||||||
|
hx-post="/import/opml"
|
||||||
|
hx-encoding="multipart/form-data"
|
||||||
|
class="feed-form"
|
||||||
|
{
|
||||||
div class="form-grid" {
|
div class="form-grid" {
|
||||||
label for="opml" { "OPML: " }
|
label for="opml" { "OPML: " }
|
||||||
input type="file" id="opml" name="opml" required="true" accept="text/x-opml,application/xml,text/xml";
|
input
|
||||||
|
type="file"
|
||||||
|
id="opml"
|
||||||
|
name="opml"
|
||||||
|
required="true"
|
||||||
|
accept="text/x-opml,application/xml,text/xml";
|
||||||
button type="submit" { "Import Feeds" }
|
button type="submit" { "Import Feeds" }
|
||||||
progress id="opml-upload-progress" max="100" value="0" hidden="true" {}
|
progress id="opml-upload-progress" max="100" value="0" hidden="true" {}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use maud::{html, Markup};
|
use maud::{html, Markup};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct ResetPasswordFormProps {
|
pub struct ResetPasswordFormProps {
|
||||||
pub token: Uuid,
|
pub token: Uuid,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
@ -10,9 +10,20 @@ pub struct ResetPasswordFormProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset_password_form(props: ResetPasswordFormProps) -> Markup {
|
pub fn reset_password_form(props: ResetPasswordFormProps) -> Markup {
|
||||||
let ResetPasswordFormProps { token, email, password_error, general_error } = props;
|
let ResetPasswordFormProps {
|
||||||
|
token,
|
||||||
|
email,
|
||||||
|
password_error,
|
||||||
|
general_error,
|
||||||
|
} = props;
|
||||||
html! {
|
html! {
|
||||||
form action="reset-password" method="post" class="auth-form-grid" {
|
form
|
||||||
|
action="/reset-password"
|
||||||
|
method="post"
|
||||||
|
hx-post="/reset-password"
|
||||||
|
id="reset-password-form"
|
||||||
|
class="auth-form-grid"
|
||||||
|
{
|
||||||
input
|
input
|
||||||
type="text"
|
type="text"
|
||||||
name="token"
|
name="token"
|
||||||
@ -28,12 +39,24 @@ pub fn reset_password_form(props: ResetPasswordFormProps) -> Markup {
|
|||||||
value=(email)
|
value=(email)
|
||||||
required;
|
required;
|
||||||
label for="password" { "Password" }
|
label for="password" { "Password" }
|
||||||
input type="password" name="password" id="password" placeholder="Password" minlength="8" maxlength="255" required;
|
input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
id="password"
|
||||||
|
placeholder="Password"
|
||||||
|
minlength="8"
|
||||||
|
maxlength="255"
|
||||||
|
required;
|
||||||
@if let Some(password_error) = password_error {
|
@if let Some(password_error) = password_error {
|
||||||
span class="error" { (password_error) }
|
span class="error" { (password_error) }
|
||||||
}
|
}
|
||||||
label for="password_confirmation" { "Confirm Password" }
|
label for="password_confirmation" { "Confirm Password" }
|
||||||
input type="password" name="password_confirmation" id="password_confirmation" placeholder="Confirm Password" required;
|
input
|
||||||
|
type="password"
|
||||||
|
name="password_confirmation"
|
||||||
|
id="password_confirmation"
|
||||||
|
placeholder="Confirm Password"
|
||||||
|
required;
|
||||||
button type="submit" { "Reset password" }
|
button type="submit" { "Reset password" }
|
||||||
@if let Some(general_error) = general_error {
|
@if let Some(general_error) = general_error {
|
||||||
span class="error" { (general_error) }
|
span class="error" { (general_error) }
|
||||||
|
Loading…
Reference in New Issue
Block a user