Finish up email verification form
This commit is contained in:
parent
c95334a7e2
commit
e59c6d596e
@ -277,3 +277,7 @@ header.feed-header button {
|
||||
font-size: 16px;
|
||||
grid-column: 2 / 3;
|
||||
}
|
||||
|
||||
.readable-width {
|
||||
max-width: 600px;
|
||||
}
|
||||
|
@ -1,11 +1,13 @@
|
||||
use axum::extract::{Query, State};
|
||||
use axum::response::Response;
|
||||
use axum::{TypedHeader, Form};
|
||||
use axum::{Form, TypedHeader};
|
||||
use axum_login::{extractors::AuthContext, SqlxStore};
|
||||
use lettre::SmtpTransport;
|
||||
use maud::html;
|
||||
use serde::Deserialize;
|
||||
use serde_with::{serde_as, NoneAsEmptyString};
|
||||
use sqlx::PgPool;
|
||||
use tracing::{info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::config::Config;
|
||||
@ -14,25 +16,29 @@ use crate::htmx::HXTarget;
|
||||
use crate::mailers::email_verification::send_confirmation_email;
|
||||
use crate::models::user::User;
|
||||
use crate::models::user_email_verification_token::UserEmailVerificationToken;
|
||||
use crate::partials::confirm_email_form::{confirm_email_form, ConfirmEmailFormProps};
|
||||
use crate::partials::layout::Layout;
|
||||
use crate::partials::confirm_email_form::confirm_email_form;
|
||||
use crate::uuid::Base62Uuid;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct ConfirmEmailQuery {
|
||||
pub token_id: Base62Uuid,
|
||||
pub token_id: Option<Base62Uuid>,
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
State(pool): State<PgPool>,
|
||||
auth: AuthContext<Uuid, User, SqlxStore<PgPool, User>>,
|
||||
hx_target: Option<TypedHeader<HXTarget>>,
|
||||
layout: Layout,
|
||||
query: Query<ConfirmEmailQuery>,
|
||||
) -> Result<Response> {
|
||||
let token = match UserEmailVerificationToken::get(&pool, query.token_id.as_uuid()).await {
|
||||
if let Some(token_id) = query.token_id {
|
||||
info!(token_id = %token_id.as_uuid(), "get with token_id");
|
||||
let token = match UserEmailVerificationToken::get(&pool, token_id.as_uuid()).await {
|
||||
Ok(token) => token,
|
||||
Err(err) => {
|
||||
if let Error::NotFoundUuid(_, _) = err {
|
||||
warn!(token_id = %token_id.as_uuid(), "token not found in database");
|
||||
return Ok(layout
|
||||
.with_subtitle("confirm email")
|
||||
.targeted(hx_target)
|
||||
@ -40,16 +46,17 @@ pub async fn get(
|
||||
div class="center-horizontal" {
|
||||
header class="center-text" {
|
||||
h2 { "Email verification token not found" }
|
||||
p { "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(None))
|
||||
}
|
||||
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);
|
||||
}
|
||||
};
|
||||
if token.expired() {
|
||||
warn!(token_id = %token.token_id, "token expired");
|
||||
Ok(layout
|
||||
.with_subtitle("confirm email")
|
||||
.targeted(hx_target)
|
||||
@ -57,12 +64,13 @@ pub async fn get(
|
||||
div class="center-horizontal" {
|
||||
header class="center-text" {
|
||||
h2 { "Email verification token is expired" }
|
||||
p { "Click the button below to resend a new confirmation email. The email will be valid for another 24 hours."}
|
||||
(confirm_email_form(Some(token)))
|
||||
}
|
||||
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."}
|
||||
(confirm_email_form(ConfirmEmailFormProps { token: Some(token), email: None }))
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
info!(token_id = %token.token_id, "token valid, verifying email");
|
||||
User::verify_email(&pool, token.user_id).await?;
|
||||
UserEmailVerificationToken::delete(&pool, token.token_id).await?;
|
||||
Ok(layout
|
||||
@ -72,11 +80,30 @@ pub async fn get(
|
||||
div class="center-horizontal" {
|
||||
header class="center-text" {
|
||||
h2 { "Your email is now confirmed!" }
|
||||
p {
|
||||
}
|
||||
p class="readable-width" {
|
||||
"Thanks for verifying your email address. "
|
||||
a href="/" { "Return home" }
|
||||
}
|
||||
}
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
Ok(layout
|
||||
.with_subtitle("confirm email")
|
||||
.targeted(hx_target)
|
||||
.render(html! {
|
||||
div class="center-horizontal" {
|
||||
header class="center-text" {
|
||||
h2 { "Confirm your email address" }
|
||||
}
|
||||
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),
|
||||
}
|
||||
))
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -100,10 +127,14 @@ pub async fn post(
|
||||
Form(confirm_email): Form<ConfirmEmail>,
|
||||
) -> Result<Response> {
|
||||
if let Some(token_id) = confirm_email.token {
|
||||
info!(%token_id, "posted with token_id");
|
||||
let token = UserEmailVerificationToken::get(&pool, token_id).await?;
|
||||
let user = User::get(&pool, token.user_id).await?;
|
||||
if !user.email_verified {
|
||||
info!(user_id = %user.user_id, "user exists, resending confirmation email");
|
||||
send_confirmation_email(pool, mailer, config, user);
|
||||
} else {
|
||||
warn!(user_id = %user.user_id, "confirm email submitted for already verified user, skip resend");
|
||||
}
|
||||
return Ok(layout
|
||||
.with_subtitle("confirm email")
|
||||
@ -112,9 +143,9 @@ pub async fn post(
|
||||
div class="center-horizontal" {
|
||||
header class="center-text" {
|
||||
h2 { "Resent confirmation email" }
|
||||
p {
|
||||
"Please follow the link sent in the email."
|
||||
}
|
||||
p class="readable-width" {
|
||||
"Please follow the link sent in the email."
|
||||
}
|
||||
}
|
||||
}));
|
||||
@ -122,7 +153,10 @@ pub async fn post(
|
||||
if let Some(email) = confirm_email.email {
|
||||
if let Ok(user) = User::get_by_email(&pool, email).await {
|
||||
if !user.email_verified {
|
||||
info!(user_id = %user.user_id, "user exists, resending confirmation email");
|
||||
send_confirmation_email(pool, mailer, config, user);
|
||||
} else {
|
||||
warn!(user_id = %user.user_id, "confirm email submitted for already verified user, skip resend");
|
||||
}
|
||||
}
|
||||
return Ok(layout
|
||||
@ -132,9 +166,9 @@ pub async fn post(
|
||||
div class="center-horizontal" {
|
||||
header class="center-text" {
|
||||
h2 { "Resent confirmation email" }
|
||||
p {
|
||||
"If the email you entered matched an existing account, then a confirmation email was sent. Please follow the link sent in the email."
|
||||
}
|
||||
p class="readable-width" {
|
||||
"If the email you entered matched an existing account, then a confirmation email was sent. Please follow the link sent in the email."
|
||||
}
|
||||
}
|
||||
}));
|
||||
@ -146,9 +180,14 @@ pub async fn post(
|
||||
div class="center-horizontal" {
|
||||
header class="center-text" {
|
||||
h2 { "Email verification token not found" }
|
||||
p { "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(None))
|
||||
}
|
||||
p class="readable-width" { "Enter your email to resend the confirmation email." }
|
||||
p class="readable-width" {
|
||||
"If you don't have an account yet, create one "
|
||||
a href="/register" { "here" }
|
||||
"."
|
||||
}
|
||||
(confirm_email_form(ConfirmEmailFormProps::default()))
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ use maud::html;
|
||||
use serde::Deserialize;
|
||||
use serde_with::serde_as;
|
||||
use sqlx::PgPool;
|
||||
use tracing::info;
|
||||
|
||||
use crate::auth::verify_password;
|
||||
use crate::error::{Error, Result};
|
||||
@ -56,6 +57,7 @@ pub async fn post(
|
||||
Ok(user) => user,
|
||||
Err(err) => {
|
||||
if let Error::NotFoundString(_, _) = err {
|
||||
info!(email = login.email, "invalid enail");
|
||||
return Ok(login_page(
|
||||
hx_target,
|
||||
layout,
|
||||
@ -74,6 +76,7 @@ pub async fn post(
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
info!(user_id = %user.user_id, "invalid password");
|
||||
return Ok(login_page(
|
||||
hx_target,
|
||||
layout,
|
||||
@ -84,6 +87,7 @@ pub async fn post(
|
||||
},
|
||||
));
|
||||
}
|
||||
info!(user_id = %user.user_id, "login successful");
|
||||
auth.login(&user)
|
||||
.await
|
||||
.map_err(|_| Error::InternalServerError)?;
|
||||
|
@ -42,11 +42,14 @@ pub fn send_confirmation_email(pool: PgPool, mailer: SmtpTransport, config: Conf
|
||||
return;
|
||||
}
|
||||
};
|
||||
let confirm_link = format!(
|
||||
"{}/confirm-email?token_id={}",
|
||||
config.public_url,
|
||||
Base62Uuid::from(token.token_id)
|
||||
);
|
||||
let mut confirm_link = config
|
||||
.public_url
|
||||
.clone();
|
||||
confirm_link.set_path("confirm-email");
|
||||
confirm_link.query_pairs_mut()
|
||||
.append_pair("token_id", &Base62Uuid::from(token.token_id).to_string());
|
||||
let confirm_link = confirm_link.as_str();
|
||||
|
||||
let email = match Message::builder()
|
||||
.from(config.email_from.clone())
|
||||
.to(mailbox)
|
||||
|
@ -128,6 +128,7 @@ async fn main() -> Result<()> {
|
||||
.route("/register", get(handlers::register::get))
|
||||
.route("/register", post(handlers::register::post))
|
||||
.route("/confirm-email", get(handlers::confirm_email::get))
|
||||
.route("/confirm-email", post(handlers::confirm_email::post))
|
||||
.nest_service("/static", ServeDir::new("static"))
|
||||
.with_state(AppState {
|
||||
pool,
|
||||
|
@ -2,15 +2,30 @@ use maud::{html, Markup};
|
||||
|
||||
use crate::models::user_email_verification_token::UserEmailVerificationToken;
|
||||
|
||||
pub fn confirm_email_form(token: Option<UserEmailVerificationToken>) -> Markup {
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ConfirmEmailFormProps {
|
||||
pub token: Option<UserEmailVerificationToken>,
|
||||
pub email: Option<String>,
|
||||
}
|
||||
|
||||
pub fn confirm_email_form(props: ConfirmEmailFormProps) -> Markup {
|
||||
let ConfirmEmailFormProps { token, email } = props;
|
||||
html! {
|
||||
form action="/confirm-email" method="post" class="auth-form-grid" {
|
||||
@if let Some(token) = token {
|
||||
input type="text" name="token" id="token" value=(token.token_id) style="display:none;";
|
||||
} @else {
|
||||
input
|
||||
type="text"
|
||||
name="token"
|
||||
id="token"
|
||||
value=(token.map(|t| t.token_id.to_string()).unwrap_or_default())
|
||||
style="display:none;";
|
||||
label for="email" { "Email" }
|
||||
input type="email" name="email" id="email" placeholder="Email" required;
|
||||
}
|
||||
input
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
placeholder="Email"
|
||||
value=(email.unwrap_or_default())
|
||||
required;
|
||||
button type="submit" { "Resend confirmation email" }
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user