refactor: reset password
This commit is contained in:
parent
ab648dd4f2
commit
ee4481225e
@ -107,7 +107,7 @@ impl User {
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn read_for_login(pool: &PgPool, email: &str) -> Result<User> {
|
||||
pub async fn read_for_login(pool: &PgPool, email: &str) -> Result<Option<User>> {
|
||||
let record = sqlx::query!(
|
||||
r#"
|
||||
SELECT id,
|
||||
@ -126,25 +126,25 @@ impl User {
|
||||
"#,
|
||||
email,
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
let result = User {
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
email: record.email,
|
||||
password: record.password,
|
||||
salt: record.salt,
|
||||
role: record.role,
|
||||
function: record.function,
|
||||
area_id: record.areaid,
|
||||
let user = record.map(|r| User {
|
||||
id: r.id,
|
||||
name: r.name,
|
||||
email: r.email,
|
||||
password: r.password,
|
||||
salt: r.salt,
|
||||
role: r.role,
|
||||
function: r.function,
|
||||
area_id: r.areaid,
|
||||
area: None,
|
||||
locked: record.locked,
|
||||
last_login: record.lastlogin,
|
||||
receive_notifications: record.receivenotifications,
|
||||
};
|
||||
locked: r.locked,
|
||||
last_login: r.lastlogin,
|
||||
receive_notifications: r.receivenotifications,
|
||||
});
|
||||
|
||||
Ok(result)
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn exists(pool: &PgPool, email: &str) -> Result<Option<i32>> {
|
||||
|
@ -31,7 +31,9 @@ pub async fn get(
|
||||
return Ok(HttpResponse::Found()
|
||||
.insert_header((LOCATION, "/"))
|
||||
.finish());
|
||||
} else if let Some(token) = &query.token {
|
||||
}
|
||||
|
||||
if let Some(token) = &query.token {
|
||||
if let Some(_) = PasswordReset::does_token_exist(pool.get_ref(), token).await? {
|
||||
let template = ResetPasswordTemplate {
|
||||
token,
|
||||
|
@ -4,7 +4,7 @@ use brass_db::models::User;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::utils::auth::hash_plain_password_with_salt;
|
||||
use crate::utils::{auth::hash_plain_password_with_salt, ApplicationError};
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct LoginForm {
|
||||
@ -18,26 +18,33 @@ async fn post(
|
||||
web::Form(form): web::Form<LoginForm>,
|
||||
request: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
) -> impl Responder {
|
||||
if let Ok(user) = User::read_for_login(pool.get_ref(), &form.email.to_lowercase()).await {
|
||||
let salt = user.salt.unwrap();
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
let not_found_response = HttpResponse::BadRequest().body("E-Mail oder Passwort falsch.");
|
||||
|
||||
let hash = hash_plain_password_with_salt(&form.password, &salt).unwrap();
|
||||
if hash == user.password.unwrap() {
|
||||
Identity::login(&request.extensions(), user.id.to_string()).unwrap();
|
||||
let Some(user) = User::read_for_login(pool.get_ref(), &form.email.to_lowercase()).await? else {
|
||||
return Ok(not_found_response);
|
||||
};
|
||||
|
||||
User::update_login_timestamp(pool.get_ref(), user.id)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let location = form.next.unwrap_or("/".to_string());
|
||||
|
||||
return HttpResponse::Found()
|
||||
.insert_header(("LOCATION", location.clone()))
|
||||
.insert_header(("HX-LOCATION", location))
|
||||
.finish();
|
||||
}
|
||||
if user.password.is_none() || user.salt.is_none() {
|
||||
return Ok(not_found_response);
|
||||
}
|
||||
|
||||
HttpResponse::BadRequest().body("E-Mail oder Passwort falsch.")
|
||||
let password = user.password.unwrap();
|
||||
let salt = user.salt.unwrap();
|
||||
let hash = hash_plain_password_with_salt(&form.password, &salt)?;
|
||||
|
||||
if hash != password {
|
||||
return Ok(not_found_response);
|
||||
}
|
||||
|
||||
Identity::login(&request.extensions(), user.id.to_string()).unwrap();
|
||||
|
||||
User::update_login_timestamp(pool.get_ref(), user.id).await?;
|
||||
|
||||
let location = form.next.unwrap_or("/".to_string());
|
||||
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header(("LOCATION", location.clone()))
|
||||
.insert_header(("HX-LOCATION", location))
|
||||
.finish())
|
||||
}
|
||||
|
@ -1,90 +1,89 @@
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use actix_web::{web, Either, HttpResponse, Responder};
|
||||
use maud::html;
|
||||
use serde::Deserialize;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
mail::Mailer,
|
||||
utils::{password_change::PasswordChangeBuilder, ApplicationError},
|
||||
utils::{password_change::PasswordChangeBuilder, ApplicationError, HtmxTargetHeader},
|
||||
};
|
||||
use brass_db::models::{PasswordReset, User};
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct RequestResetPasswordForm {
|
||||
email: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct ResetPasswordForm {
|
||||
email: Option<String>,
|
||||
token: Option<String>,
|
||||
password: Option<String>,
|
||||
passwordretyped: Option<String>,
|
||||
dry: Option<bool>,
|
||||
token: String,
|
||||
password: String,
|
||||
passwordretyped: String,
|
||||
}
|
||||
|
||||
#[actix_web::post("/reset-password")]
|
||||
async fn post(
|
||||
form: web::Form<ResetPasswordForm>,
|
||||
form: Either<web::Form<RequestResetPasswordForm>, web::Form<ResetPasswordForm>>,
|
||||
header: web::Header<HtmxTargetHeader>,
|
||||
pool: web::Data<PgPool>,
|
||||
mailer: web::Data<Mailer>,
|
||||
) -> Result<impl Responder, ApplicationError> {
|
||||
if form.email.is_some()
|
||||
&& form.token.is_none()
|
||||
&& form.password.is_none()
|
||||
&& form.passwordretyped.is_none()
|
||||
{
|
||||
if let Ok(user) =
|
||||
User::read_for_login(pool.get_ref(), &form.email.as_ref().unwrap().to_lowercase()).await
|
||||
{
|
||||
let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?;
|
||||
mailer
|
||||
.send_forgot_password_mail(&user, &reset.token)
|
||||
.await?;
|
||||
match form {
|
||||
Either::Left(form) => {
|
||||
if let Some(user) =
|
||||
User::read_for_login(pool.get_ref(), &form.email.to_lowercase()).await?
|
||||
{
|
||||
let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?;
|
||||
mailer
|
||||
.send_forgot_password_mail(&user, &reset.token)
|
||||
.await?;
|
||||
}
|
||||
|
||||
return Ok(HttpResponse::Ok().body("E-Mail versandt!"));
|
||||
}
|
||||
Either::Right(form) => {
|
||||
let is_dry = header.is_some_and_equal("password-strength");
|
||||
|
||||
Ok(HttpResponse::Ok().body("E-Mail versandt!"))
|
||||
} else if form.email.is_none()
|
||||
&& form.token.is_some()
|
||||
&& form.password.is_some()
|
||||
&& form.passwordretyped.is_some()
|
||||
{
|
||||
// TODO: refactor into check if HX-TARGET = #password-strength exists
|
||||
let is_dry = form.dry.is_some_and(|b| b);
|
||||
let Some(token) = PasswordReset::does_token_exist(pool.get_ref(), &form.token).await?
|
||||
else {
|
||||
return Ok(HttpResponse::NoContent().finish());
|
||||
};
|
||||
|
||||
let token = if let Some(token) =
|
||||
PasswordReset::does_token_exist(pool.get_ref(), form.token.as_ref().unwrap()).await?
|
||||
{
|
||||
token
|
||||
} else {
|
||||
return Ok(HttpResponse::NoContent().finish());
|
||||
};
|
||||
|
||||
let mut builder = PasswordChangeBuilder::<PasswordReset>::new(
|
||||
pool.get_ref(),
|
||||
token.userid,
|
||||
&form.password.as_ref().unwrap(),
|
||||
&form.passwordretyped.as_ref().unwrap(),
|
||||
)
|
||||
.with_token(token);
|
||||
|
||||
let change = builder.build();
|
||||
|
||||
let response = if is_dry {
|
||||
change.validate_for_input().await.unwrap() // TODO:
|
||||
} else {
|
||||
change.validate().await.unwrap(); // TODO
|
||||
change.commit().await?;
|
||||
HttpResponse::Ok().body(
|
||||
html! {
|
||||
div class="block mb-3" {
|
||||
"Passwort erfolgreich geändert."
|
||||
}
|
||||
a class="block button is-primary" hx-boost="true" href="/login"{
|
||||
"Zum Login"
|
||||
}
|
||||
}
|
||||
.into_string(),
|
||||
let mut builder = PasswordChangeBuilder::<PasswordReset>::new(
|
||||
pool.get_ref(),
|
||||
token.userid,
|
||||
&form.password,
|
||||
&form.passwordretyped,
|
||||
)
|
||||
};
|
||||
.with_token(token);
|
||||
|
||||
return Ok(response);
|
||||
} else {
|
||||
return Ok(HttpResponse::BadRequest().finish());
|
||||
let change = builder.build();
|
||||
|
||||
let response = if is_dry {
|
||||
match change.validate_for_input().await {
|
||||
Ok(r) => r,
|
||||
Err(e) => HttpResponse::UnprocessableEntity().body(e.message),
|
||||
}
|
||||
} else {
|
||||
if let Err(e) = change.validate().await {
|
||||
return Ok(HttpResponse::UnprocessableEntity().body(e.message));
|
||||
}
|
||||
|
||||
change.commit().await?;
|
||||
HttpResponse::Ok().body(
|
||||
html! {
|
||||
div class="block mb-3" {
|
||||
"Passwort erfolgreich geändert."
|
||||
}
|
||||
a class="block button is-primary" hx-boost="true" href="/login"{
|
||||
"Zum Login"
|
||||
}
|
||||
}
|
||||
.into_string(),
|
||||
)
|
||||
};
|
||||
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user