use actix::Addr; use actix_remote_ip::RemoteIP; use actix_web::{web, HttpResponse, Responder}; use askama::Template; use crate::actors::bruteforce_actor::BruteForceActor; use crate::actors::users_actor::UsersActor; use crate::actors::{bruteforce_actor, users_actor}; use crate::constants::{APP_NAME, MAX_FAILED_LOGIN_ATTEMPTS, MIN_PASS_LEN}; use crate::data::action_logger::{Action, ActionLogger}; use crate::data::app_config::AppConfig; use crate::data::current_user::CurrentUser; use crate::data::user::User; pub(crate) struct BaseSettingsPage<'a> { pub danger_message: Option, pub success_message: Option, pub page_title: &'static str, pub app_name: &'static str, pub user: &'a User, pub version: &'static str, pub ip_location_api: Option<&'static str>, } impl<'a> BaseSettingsPage<'a> { pub fn get( page_title: &'static str, user: &'a User, danger_message: Option, success_message: Option, ) -> BaseSettingsPage<'a> { Self { danger_message, success_message, page_title, app_name: APP_NAME, user, version: env!("CARGO_PKG_VERSION"), ip_location_api: AppConfig::get().ip_location_service.as_deref(), } } } #[derive(Template)] #[template(path = "settings/account_details.html")] struct AccountDetailsPage<'a> { p: BaseSettingsPage<'a>, remote_ip: String, } #[derive(Template)] #[template(path = "settings/change_password.html")] struct ChangePasswordPage<'a> { p: BaseSettingsPage<'a>, min_pwd_len: usize, } /// Account details page pub async fn account_settings_details_route(user: CurrentUser, ip: RemoteIP) -> impl Responder { let user = user.into(); HttpResponse::Ok().body( AccountDetailsPage { p: BaseSettingsPage::get("Account details", &user, None, None), remote_ip: ip.0.to_string(), } .render() .unwrap(), ) } #[derive(serde::Deserialize)] pub struct PassChangeRequest { pub old_pass: String, pub new_pass: String, } /// Change password route pub async fn change_password_route( user: CurrentUser, users: web::Data>, req: Option>, bruteforce: web::Data>, remote_ip: RemoteIP, logger: ActionLogger, ) -> impl Responder { let mut danger = None; let mut success = None; let user: User = user.into(); let failed_attempts = bruteforce .send(bruteforce_actor::CountFailedAttempt { ip: remote_ip.into(), }) .await .unwrap(); if failed_attempts > MAX_FAILED_LOGIN_ATTEMPTS { danger = Some( "Too many invalid password attempts. Please try to change your password later." .to_string(), ); } else if let Some(req) = req { // Invalid password if !users .send(users_actor::VerifyUserPasswordRequest( user.uid.clone(), req.old_pass.clone(), )) .await .unwrap() { danger = Some("Old password is invalid!".to_string()); bruteforce .send(bruteforce_actor::RecordFailedAttempt { ip: remote_ip.into(), }) .await .unwrap(); } // Password too short else if req.new_pass.len() < MIN_PASS_LEN { danger = Some("New password is too short!".to_string()); } // Change password else { let res = users .send(users_actor::ChangePasswordRequest { user_id: user.uid.clone(), new_password: req.new_pass.to_string(), temporary: false, }) .await .unwrap(); if !res { danger = Some("An error occurred while trying to change your password!".to_string()); } else { logger.log(Action::ChangedHisPassword); success = Some("Your password was successfully changed!".to_string()); } } } HttpResponse::Ok().body( ChangePasswordPage { p: BaseSettingsPage::get("Change password", &user, danger, success), min_pwd_len: MIN_PASS_LEN, } .render() .unwrap(), ) }