use crate::{to_account_page, Authenticated, Error, State}; use actix_web::{error::BlockingError, web, HttpResponse}; pub type UpdatePasswordPageArgs = (Authenticated, web::Data); pub type UpdatePasswordArgs = ( Authenticated, web::Data, web::Form, ); #[derive(Debug, serde::Deserialize)] pub struct UpdatePasswordForm { new_password: String, new_password_confirmation: String, password: String, } #[derive(Debug)] pub struct UpdatePasswordState { state: State, form: Option, error: Option, } pub fn update_password_page((_, state): UpdatePasswordPageArgs) -> UpdatePasswordState { UpdatePasswordState::empty(&state) } pub async fn update_password( (authenticated, state, form): UpdatePasswordArgs, ) -> Result, Error> { let form = form.into_inner(); match try_update_password(authenticated, &form, &state).await? { Ok(res) => Ok(Err(res)), Err(e) => Ok(Ok(UpdatePasswordState::new_from_request(&state, form, e))), } } impl UpdatePasswordState { fn empty(state: &State) -> Self { UpdatePasswordState { state: state.clone(), form: None, error: None, } } fn new_from_request( state: &State, form: UpdatePasswordForm, error: UpdatePasswordError, ) -> Self { UpdatePasswordState { state: state.clone(), form: Some(form), error: Some(error), } } pub fn update_password_path(&self) -> String { self.state.pages.update_password_path() } pub fn new_password(&self) -> Option { self.form.as_ref().map(|form| form.new_password.clone()) } pub fn new_password_confirmation(&self) -> Option { self.form .as_ref() .map(|form| form.new_password_confirmation.clone()) } pub fn new_password_confirmation_error(&self) -> Option { self.error .as_ref() .and_then(|e| e.new_password_confirmation()) } pub fn password(&self) -> Option { self.form.as_ref().map(|form| form.password.clone()) } pub fn password_error(&self) -> Option { self.error.as_ref().and_then(|e| e.password()) } } #[derive(Debug)] enum UpdatePasswordError { Match, Invalid, } impl UpdatePasswordError { fn new_password_confirmation(&self) -> Option { match self { UpdatePasswordError::Match => { Some("New password confirmation does not match".to_owned()) } _ => None, } } fn password(&self) -> Option { match self { UpdatePasswordError::Invalid => Some("Incorrect password supplied".to_owned()), _ => None, } } } async fn try_update_password( authenticated: Authenticated, form: &UpdatePasswordForm, state: &State, ) -> Result, Error> { if form.new_password != form.new_password_confirmation { return Ok(Err(UpdatePasswordError::Match)); } let res = state .user_store .exec(crate::store::UpdatePassword { user: authenticated.user(), new_password: form.new_password.clone(), password: form.password.clone(), }) .await; match res { Ok(_) => (), Err(BlockingError::Error(crate::store::StoreError::AuthenticationFailed)) => { return Ok(Err(UpdatePasswordError::Invalid)) } Err(BlockingError::Error(crate::store::StoreError::DoubleChange)) => { return Ok(Err(UpdatePasswordError::Invalid)) } Err(e) => return Err(e.into()), }; Ok(Ok(to_account_page(&state))) }