Files
BasicOIDC/src/controllers/settings_controller.rs

155 lines
4.4 KiB
Rust

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<String>,
pub success_message: Option<String>,
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<String>,
success_message: Option<String>,
) -> 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<Addr<UsersActor>>,
req: Option<web::Form<PassChangeRequest>>,
bruteforce: web::Data<Addr<BruteForceActor>>,
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(),
)
}