refactor: password change validation
This commit is contained in:
parent
d038331a4b
commit
a09a35ece2
@ -1,16 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
filters,
|
filters,
|
||||||
models::{Area, Role, Token, User},
|
models::{Area, Role, User},
|
||||||
utils::{
|
|
||||||
auth::{generate_salt_and_hash_plain_password, hash_plain_password_with_salt},
|
|
||||||
password_help, ApplicationError,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
use actix_web::HttpResponse;
|
|
||||||
use rinja::Template;
|
use rinja::Template;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
|
||||||
use zxcvbn::{zxcvbn, Score};
|
|
||||||
|
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
pub mod get_changepassword;
|
pub mod get_changepassword;
|
||||||
@ -71,82 +64,3 @@ struct ResetPasswordTemplate<'a> {
|
|||||||
retype_label: &'a str,
|
retype_label: &'a str,
|
||||||
submit_button_label: &'a str,
|
submit_button_label: &'a str,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_password_change_request(
|
|
||||||
pool: &PgPool,
|
|
||||||
token: Option<&impl Token>,
|
|
||||||
user_id: i32,
|
|
||||||
password: &str,
|
|
||||||
password_retyped: &str,
|
|
||||||
current_password: Option<&str>,
|
|
||||||
generate_message: bool,
|
|
||||||
) -> Result<HttpResponse, ApplicationError> {
|
|
||||||
let no_message = HttpResponse::NoContent().finish();
|
|
||||||
|
|
||||||
if password.chars().count() > 256 {
|
|
||||||
if generate_message {
|
|
||||||
return Ok(
|
|
||||||
HttpResponse::BadRequest().body(password_help::format_message(
|
|
||||||
"danger",
|
|
||||||
"Password darf nicht länger als 256 Zeichen sein.",
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(no_message);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make sure this unwrap is safe
|
|
||||||
let user = User::read_by_id(pool, user_id).await?.unwrap();
|
|
||||||
let mut split_names: Vec<&str> = user.name.as_str().split_whitespace().collect();
|
|
||||||
let mut user_inputs = vec![user.email.as_str()];
|
|
||||||
user_inputs.append(&mut split_names);
|
|
||||||
let entropy = zxcvbn(password, &user_inputs);
|
|
||||||
|
|
||||||
if entropy.score() < Score::Three {
|
|
||||||
if generate_message {
|
|
||||||
let message = password_help::generate_for_entropy(&entropy);
|
|
||||||
return Ok(HttpResponse::BadRequest().body(message));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(no_message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if generate_message {
|
|
||||||
if entropy.score() == Score::Three {
|
|
||||||
return Ok(HttpResponse::Ok().body(password_help::format_message(
|
|
||||||
"success",
|
|
||||||
"Sicheres Passwort.",
|
|
||||||
)));
|
|
||||||
} else {
|
|
||||||
return Ok(HttpResponse::Ok().body(password_help::format_message(
|
|
||||||
"success",
|
|
||||||
"Sehr sicheres Passwort.",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(current_password) = current_password {
|
|
||||||
// unwraps are safe, as login only works with password and salt and this call site requires login
|
|
||||||
let hash = user.password.as_ref().unwrap();
|
|
||||||
let salt = user.salt.as_ref().unwrap();
|
|
||||||
|
|
||||||
if hash != &hash_plain_password_with_salt(current_password, salt)? {
|
|
||||||
return Ok(HttpResponse::BadRequest().body("Aktuelles Passwort ist falsch."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if password != password_retyped {
|
|
||||||
return Ok(HttpResponse::BadRequest().body("Passwörter stimmen nicht überein."));
|
|
||||||
}
|
|
||||||
|
|
||||||
let (hash, salt) = generate_salt_and_hash_plain_password(password).unwrap();
|
|
||||||
|
|
||||||
User::update_password(pool, user_id, &hash, &salt).await?;
|
|
||||||
|
|
||||||
if let Some(token) = token {
|
|
||||||
token.delete(pool).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().body(r#"<div class="block">Registrierung abgeschlossen.</div><a class="block button is-primary" hx-boost="true" href="/login">Zum Login</a>"#))
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
use actix_web::{web, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use maud::html;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::user::handle_password_change_request,
|
|
||||||
models::{NoneToken, User},
|
models::{NoneToken, User},
|
||||||
utils::ApplicationError,
|
utils::{password_change::PasswordChangeBuilder, ApplicationError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -25,16 +25,30 @@ async fn post(
|
|||||||
// TODO: refactor into check if HX-TARGET = #password-strength exists
|
// TODO: refactor into check if HX-TARGET = #password-strength exists
|
||||||
let is_dry = form.dry.unwrap_or(false);
|
let is_dry = form.dry.unwrap_or(false);
|
||||||
|
|
||||||
let response = handle_password_change_request(
|
let mut builder = PasswordChangeBuilder::<NoneToken>::new(
|
||||||
pool.get_ref(),
|
pool.get_ref(),
|
||||||
None::<&NoneToken>,
|
|
||||||
user.id,
|
user.id,
|
||||||
&form.password,
|
&form.password,
|
||||||
&form.passwordretyped,
|
&form.passwordretyped,
|
||||||
Some(&form.currentpassword),
|
|
||||||
is_dry,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.with_current_password(&form.currentpassword);
|
||||||
|
|
||||||
|
let change = builder.build();
|
||||||
|
|
||||||
|
let response = if is_dry {
|
||||||
|
change.validate_for_input().await?
|
||||||
|
} else {
|
||||||
|
change.validate().await?;
|
||||||
|
change.commit().await?;
|
||||||
|
HttpResponse::Ok().body(
|
||||||
|
html! {
|
||||||
|
div class="block" {
|
||||||
|
"Passwort erfolgreich geändert."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_string(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
use actix_web::{post, web, HttpResponse, Responder};
|
use actix_web::{post, web, HttpResponse, Responder};
|
||||||
|
use maud::html;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::user::handle_password_change_request, models::Registration, utils::ApplicationError,
|
models::Registration,
|
||||||
|
utils::{password_change::PasswordChangeBuilder, ApplicationError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -28,16 +30,33 @@ async fn post(
|
|||||||
return Ok(HttpResponse::NoContent().finish());
|
return Ok(HttpResponse::NoContent().finish());
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = handle_password_change_request(
|
let mut builder = PasswordChangeBuilder::<Registration>::new(
|
||||||
pool.get_ref(),
|
pool.get_ref(),
|
||||||
Some(&token),
|
|
||||||
token.userid,
|
token.userid,
|
||||||
&form.password,
|
&form.password,
|
||||||
&form.passwordretyped,
|
&form.passwordretyped,
|
||||||
None,
|
|
||||||
is_dry,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.with_token(token);
|
||||||
|
|
||||||
|
let change = builder.build();
|
||||||
|
|
||||||
|
let response = if is_dry {
|
||||||
|
change.validate_for_input().await?
|
||||||
|
} else {
|
||||||
|
change.validate().await?;
|
||||||
|
change.commit().await?;
|
||||||
|
HttpResponse::Ok().body(
|
||||||
|
html! {
|
||||||
|
div class="block mb-3" {
|
||||||
|
"Registrierung abgeschlossen."
|
||||||
|
}
|
||||||
|
a class="block button is-primary" hx-boost="true" href="/login"{
|
||||||
|
"Zum Login"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_string(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
use maud::html;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
endpoints::user::handle_password_change_request,
|
|
||||||
mail::Mailer,
|
mail::Mailer,
|
||||||
models::{PasswordReset, User},
|
models::{PasswordReset, User},
|
||||||
utils::ApplicationError,
|
utils::{password_change::PasswordChangeBuilder, ApplicationError},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
@ -31,7 +31,9 @@ async fn post(
|
|||||||
{
|
{
|
||||||
if let Ok(user) = User::read_for_login(pool.get_ref(), form.email.as_ref().unwrap()).await {
|
if let Ok(user) = User::read_for_login(pool.get_ref(), form.email.as_ref().unwrap()).await {
|
||||||
let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?;
|
let reset = PasswordReset::insert_new_for_user(pool.get_ref(), user.id).await?;
|
||||||
mailer.send_forgot_password_mail(&user, &reset.token).await?;
|
mailer
|
||||||
|
.send_forgot_password_mail(&user, &reset.token)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().body("E-Mail versandt!"))
|
Ok(HttpResponse::Ok().body("E-Mail versandt!"))
|
||||||
@ -51,16 +53,33 @@ async fn post(
|
|||||||
return Ok(HttpResponse::NoContent().finish());
|
return Ok(HttpResponse::NoContent().finish());
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = handle_password_change_request(
|
let mut builder = PasswordChangeBuilder::<PasswordReset>::new(
|
||||||
pool.get_ref(),
|
pool.get_ref(),
|
||||||
Some(&token),
|
|
||||||
token.userid,
|
token.userid,
|
||||||
form.password.as_ref().unwrap(),
|
&form.password.as_ref().unwrap(),
|
||||||
form.passwordretyped.as_ref().unwrap(),
|
&form.passwordretyped.as_ref().unwrap(),
|
||||||
None,
|
|
||||||
is_dry,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.with_token(token);
|
||||||
|
|
||||||
|
let change = builder.build();
|
||||||
|
|
||||||
|
let response = if is_dry {
|
||||||
|
change.validate_for_input().await?
|
||||||
|
} else {
|
||||||
|
change.validate().await?;
|
||||||
|
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);
|
return Ok(response);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
use actix_web::{http::StatusCode, HttpResponse};
|
use actix_web::{http::StatusCode, HttpResponse};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use super::password_change::PasswordChangeError;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum ApplicationError {
|
pub enum ApplicationError {
|
||||||
#[error("unsupported value '{value}' for enum '{enum_name}'")]
|
#[error("unsupported value '{value}' for enum '{enum_name}'")]
|
||||||
@ -23,6 +26,11 @@ pub enum ApplicationError {
|
|||||||
Hash(#[from] argon2::password_hash::Error),
|
Hash(#[from] argon2::password_hash::Error),
|
||||||
#[error("templating failed")]
|
#[error("templating failed")]
|
||||||
Template(#[from] rinja::Error),
|
Template(#[from] rinja::Error),
|
||||||
|
#[error("{}", inner.message)]
|
||||||
|
PasswordChange {
|
||||||
|
#[from]
|
||||||
|
inner: PasswordChangeError,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl actix_web::error::ResponseError for ApplicationError {
|
impl actix_web::error::ResponseError for ApplicationError {
|
||||||
@ -38,6 +46,7 @@ impl actix_web::error::ResponseError for ApplicationError {
|
|||||||
ApplicationError::Hash(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
ApplicationError::Hash(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
ApplicationError::Template(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
ApplicationError::Template(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
ApplicationError::EmailStubTransport(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
ApplicationError::EmailStubTransport(_) => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
ApplicationError::PasswordChange { .. } => StatusCode::BAD_REQUEST,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,9 +2,9 @@ mod application_error;
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod event_planning_template;
|
pub mod event_planning_template;
|
||||||
pub mod manage_commands;
|
pub mod manage_commands;
|
||||||
pub mod password_help;
|
|
||||||
pub mod token_generation;
|
pub mod token_generation;
|
||||||
mod template_response_trait;
|
mod template_response_trait;
|
||||||
|
pub mod password_change;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test_helper;
|
pub mod test_helper;
|
||||||
|
@ -1,9 +1,23 @@
|
|||||||
|
use maud::html;
|
||||||
|
use thiserror::Error;
|
||||||
use zxcvbn::{
|
use zxcvbn::{
|
||||||
feedback::{Suggestion, Warning},
|
feedback::{Suggestion, Warning},
|
||||||
Entropy,
|
Entropy,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn generate_for_entropy(entropy: &Entropy) -> String {
|
mod password_change;
|
||||||
|
mod password_change_builder;
|
||||||
|
|
||||||
|
pub use password_change::PasswordChange;
|
||||||
|
pub use password_change_builder::PasswordChangeBuilder;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error("{message}")]
|
||||||
|
pub struct PasswordChangeError {
|
||||||
|
pub message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_help_message_for_entropy(entropy: &Entropy) -> String {
|
||||||
let feedback = entropy.feedback().unwrap();
|
let feedback = entropy.feedback().unwrap();
|
||||||
|
|
||||||
let warning = match feedback.warning() {
|
let warning = match feedback.warning() {
|
||||||
@ -73,12 +87,17 @@ pub fn generate_for_entropy(entropy: &Entropy) -> String {
|
|||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
format!("<div id=\"password-strength\" class=\"mb-3 help content is-danger\"><p>{warning}</p>{vorschlag_text}:<ul>{suggestion}</ul></div>")
|
html!(
|
||||||
|
{
|
||||||
|
div id="password-strength" class="mb-3 help content is-danger" {
|
||||||
|
p { ( warning )}
|
||||||
|
(vorschlag_text) ":"
|
||||||
|
ul {
|
||||||
|
(suggestion)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format_message(level: &str, message: &str) -> String {
|
}
|
||||||
format!(
|
}
|
||||||
r#"<div id="password-strength" class="mb-3 help content is-{}">{}</div>"#,
|
)
|
||||||
level, message
|
.into_string()
|
||||||
)
|
|
||||||
}
|
}
|
112
web/src/utils/password_change/password_change.rs
Normal file
112
web/src/utils/password_change/password_change.rs
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
use actix_web::HttpResponse;
|
||||||
|
use maud::html;
|
||||||
|
use sqlx::PgPool;
|
||||||
|
use zxcvbn::{zxcvbn, Score};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
models::{Token, User},
|
||||||
|
utils::{
|
||||||
|
auth::{generate_salt_and_hash_plain_password, hash_plain_password_with_salt},
|
||||||
|
ApplicationError,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::{generate_help_message_for_entropy, PasswordChangeError};
|
||||||
|
|
||||||
|
pub struct PasswordChange<'a, T>
|
||||||
|
where
|
||||||
|
T: Token,
|
||||||
|
{
|
||||||
|
pub(super) pool: &'a PgPool,
|
||||||
|
pub(super) user_id: i32,
|
||||||
|
pub(super) password: &'a str,
|
||||||
|
pub(super) password_retyped: &'a str,
|
||||||
|
pub(super) token: Option<T>,
|
||||||
|
pub(super) current_password: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PasswordChange<'_, T>
|
||||||
|
where
|
||||||
|
T: Token,
|
||||||
|
{
|
||||||
|
/// should be called after password input has changed to hint the user of any input related problems
|
||||||
|
pub async fn validate_for_input(&self) -> Result<HttpResponse, ApplicationError> {
|
||||||
|
if self.password.chars().count() > 256 {
|
||||||
|
return Err(PasswordChangeError {
|
||||||
|
message: html! {
|
||||||
|
div id="password-strength" class="mb-3 help content is-danger"{
|
||||||
|
"Password darf nicht länger als 256 Zeichen sein."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_string(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// unwrap is safe, as either a token is present (tokens always require a userid present) or the current password is set (which requires a existing user)
|
||||||
|
let user = User::read_by_id(self.pool, self.user_id).await?.unwrap();
|
||||||
|
let mut split_names: Vec<&str> = user.name.as_str().split_whitespace().collect();
|
||||||
|
let mut user_inputs = vec![user.email.as_str()];
|
||||||
|
user_inputs.append(&mut split_names);
|
||||||
|
let entropy = zxcvbn(self.password, &user_inputs);
|
||||||
|
|
||||||
|
if entropy.score() < Score::Three {
|
||||||
|
let message = generate_help_message_for_entropy(&entropy);
|
||||||
|
return Err(PasswordChangeError { message }.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(HttpResponse::Ok().body(
|
||||||
|
html! {
|
||||||
|
div id="password-strength" class="mb-3 help content is-success" {
|
||||||
|
@if entropy.score() == Score::Three {
|
||||||
|
"Sicheres Passwort."
|
||||||
|
} @else {
|
||||||
|
"Sehr sicheres Passwort."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.into_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// should be called after the form is fully filled and submit is clicked
|
||||||
|
pub async fn validate(&self) -> Result<(), ApplicationError> {
|
||||||
|
self.validate_for_input().await?;
|
||||||
|
|
||||||
|
if let Some(current_password) = self.current_password {
|
||||||
|
// unwraps are safe, as login only works with password and salt and this call site requires login
|
||||||
|
let user = User::read_by_id(self.pool, self.user_id).await?.unwrap();
|
||||||
|
let hash = user.password.as_ref().unwrap();
|
||||||
|
let salt = user.salt.as_ref().unwrap();
|
||||||
|
|
||||||
|
if hash != &hash_plain_password_with_salt(current_password, salt)? {
|
||||||
|
return Err(PasswordChangeError {
|
||||||
|
message: "Aktuelles Passwort ist falsch.".to_string(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.password != self.password_retyped {
|
||||||
|
return Err(PasswordChangeError {
|
||||||
|
message: "Passwörter stimmen nicht überein.".to_string(),
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// commits the password change to the database
|
||||||
|
pub async fn commit(&self) -> Result<(), ApplicationError> {
|
||||||
|
let (hash, salt) = generate_salt_and_hash_plain_password(self.password).unwrap();
|
||||||
|
|
||||||
|
User::update_password(self.pool, self.user_id, &hash, &salt).await?;
|
||||||
|
|
||||||
|
if let Some(token) = &self.token {
|
||||||
|
token.delete(self.pool).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
62
web/src/utils/password_change/password_change_builder.rs
Normal file
62
web/src/utils/password_change/password_change_builder.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
use sqlx::PgPool;
|
||||||
|
|
||||||
|
use crate::models::Token;
|
||||||
|
|
||||||
|
use super::PasswordChange;
|
||||||
|
|
||||||
|
pub struct PasswordChangeBuilder<'a, T>
|
||||||
|
where
|
||||||
|
T: Token,
|
||||||
|
{
|
||||||
|
pool: &'a PgPool,
|
||||||
|
user_id: i32,
|
||||||
|
password: &'a str,
|
||||||
|
password_retyped: &'a str,
|
||||||
|
token: Option<T>,
|
||||||
|
current_password: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> PasswordChangeBuilder<'_, T>
|
||||||
|
where
|
||||||
|
T: Token,
|
||||||
|
{
|
||||||
|
pub fn new<'a>(
|
||||||
|
pool: &'a PgPool,
|
||||||
|
user_id: i32,
|
||||||
|
password: &'a str,
|
||||||
|
password_retyped: &'a str,
|
||||||
|
) -> PasswordChangeBuilder<'a, T>
|
||||||
|
where
|
||||||
|
T: Token,
|
||||||
|
{
|
||||||
|
PasswordChangeBuilder {
|
||||||
|
pool,
|
||||||
|
user_id,
|
||||||
|
password,
|
||||||
|
password_retyped,
|
||||||
|
token: None,
|
||||||
|
current_password: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_token(mut self, token: T) -> Self {
|
||||||
|
self.token = Some(token);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_current_password(mut self, current_password: &str) -> Self {
|
||||||
|
self.current_password = Some(current_password.to_string());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build<'a>(&'a mut self) -> PasswordChange<'a, T> {
|
||||||
|
PasswordChange {
|
||||||
|
pool: self.pool,
|
||||||
|
user_id: self.user_id,
|
||||||
|
password: self.password,
|
||||||
|
password_retyped: self.password_retyped,
|
||||||
|
token: self.token.take(),
|
||||||
|
current_password: self.current_password.as_deref(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user