use actix::Addr; use actix_identity::Identity; use actix_web::{HttpResponse, Responder, web}; use askama::Template; use crate::actors::{bruteforce_actor, users_actor}; use crate::actors::bruteforce_actor::BruteForceActor; use crate::actors::users_actor::{ChangePasswordResult, LoginResult, UsersActor}; use crate::constants::{APP_NAME, MAX_FAILED_LOGIN_ATTEMPTS, MIN_PASS_LEN}; use crate::controllers::base_controller::{FatalErrorPage, redirect_user, redirect_user_for_login}; use crate::data::login_redirect_query::LoginRedirectQuery; use crate::data::remote_ip::RemoteIP; use crate::data::session_identity::{SessionIdentity, SessionStatus}; use crate::data::user::{TwoFactor, User}; struct BaseLoginPage { danger: Option, success: Option, page_title: &'static str, app_name: &'static str, redirect_uri: String, } #[derive(Template)] #[template(path = "login/login.html")] struct LoginTemplate { _p: BaseLoginPage, login: String, } #[derive(Template)] #[template(path = "login/password_reset.html")] struct PasswordResetTemplate { _p: BaseLoginPage, min_pass_len: usize, } #[derive(Template)] #[template(path = "login/choose_second_factor.html")] struct ChooseSecondFactorTemplate<'a> { _p: BaseLoginPage, factors: &'a [TwoFactor], } #[derive(serde::Deserialize)] pub struct LoginRequestBody { login: String, password: String, } #[derive(serde::Deserialize)] pub struct LoginRequestQuery { logout: Option, #[serde(default)] redirect: LoginRedirectQuery, } /// Authenticate user pub async fn login_route( remote_ip: RemoteIP, users: web::Data>, bruteforce: web::Data>, query: web::Query, req: Option>, id: Identity, ) -> impl Responder { let mut danger = None; let mut success = None; let mut login = String::new(); let failed_attempts = bruteforce.send(bruteforce_actor::CountFailedAttempt { ip: remote_ip.into() }) .await.unwrap(); if failed_attempts > MAX_FAILED_LOGIN_ATTEMPTS { return HttpResponse::TooManyRequests().body( FatalErrorPage { message: "Too many failed login attempts, please try again later!" }.render().unwrap() ); } // Check if user session must be closed if let Some(true) = query.logout { id.forget(); success = Some("Goodbye!".to_string()); } // Check if user is already authenticated if SessionIdentity(&id).is_authenticated() { return redirect_user(query.redirect.get()); } // Check if the password of the user has to be changed if SessionIdentity(&id).need_new_password() { return redirect_user(&format!("/reset_password?redirect={}", query.redirect.get_encoded())); } // Check if the user has to valide a second factor if SessionIdentity(&id).need_2fa_auth() { return redirect_user(&format!("/2fa_auth?redirect={}", query.redirect.get_encoded())); } // Try to authenticate user if let Some(req) = &req { login = req.login.clone(); let response: LoginResult = users .send(users_actor::LoginRequest { login: login.clone(), password: req.password.clone(), }) .await .unwrap(); match response { LoginResult::Success(user) => { SessionIdentity(&id).set_user(&user); return if user.need_reset_password { SessionIdentity(&id).set_status(SessionStatus::NeedNewPassword); redirect_user(&format!("/reset_password?redirect={}", query.redirect.get_encoded())) } else if user.has_two_factor() { SessionIdentity(&id).set_status(SessionStatus::Need2FA); redirect_user(&format!("/2fa_auth?redirect={}", query.redirect.get_encoded())) } else { redirect_user(query.redirect.get()) }; } LoginResult::AccountDisabled => { log::warn!("Failed login for username {} : account is disabled", login); danger = Some("Your account is disabled!".to_string()); } c => { log::warn!("Failed login for ip {:?} / username {}: {:?}", remote_ip, login, c); danger = Some("Login failed.".to_string()); bruteforce.send(bruteforce_actor::RecordFailedAttempt { ip: remote_ip.into() }).await.unwrap(); } } } HttpResponse::Ok().content_type("text/html").body( LoginTemplate { _p: BaseLoginPage { page_title: "Login", danger, success, app_name: APP_NAME, redirect_uri: query.redirect.get_encoded(), }, login, } .render() .unwrap(), ) } /// Sign out user pub async fn logout_route() -> impl Responder { redirect_user("/login?logout=true") } #[derive(serde::Deserialize)] pub struct ChangePasswordRequestBody { password: String, } #[derive(serde::Deserialize)] pub struct PasswordResetQuery { #[serde(default)] redirect: LoginRedirectQuery, } /// Reset user password route pub async fn reset_password_route(id: Identity, query: web::Query, req: Option>, users: web::Data>) -> impl Responder { let mut danger = None; if !SessionIdentity(&id).need_new_password() { return redirect_user_for_login(query.redirect.get()); } // Check if user is setting a new password if let Some(req) = &req { if req.password.len() < MIN_PASS_LEN { danger = Some("Password is too short!".to_string()); } else { let res: ChangePasswordResult = users .send(users_actor::ChangePasswordRequest { user_id: SessionIdentity(&id).user_id(), new_password: req.password.clone(), temporary: false, }) .await .unwrap(); if !res.0 { danger = Some("Failed to change password!".to_string()); } else { SessionIdentity(&id).set_status(SessionStatus::SignedIn); return redirect_user(query.redirect.get()); } } } HttpResponse::Ok().content_type("text/html").body( PasswordResetTemplate { _p: BaseLoginPage { page_title: "Password reset", danger, success: None, app_name: APP_NAME, redirect_uri: query.redirect.get_encoded(), }, min_pass_len: MIN_PASS_LEN, } .render() .unwrap(), ) } #[derive(serde::Deserialize)] pub struct ChooseSecondFactorQuery { #[serde(default)] redirect: LoginRedirectQuery, } /// Let the user select the factor to use to authenticate pub async fn choose_2fa_method(id: Identity, query: web::Query, users: web::Data>) -> impl Responder { if !SessionIdentity(&id).need_2fa_auth() { return redirect_user_for_login(query.redirect.get()); } let user: User = users.send(users_actor::GetUserRequest(SessionIdentity(&id).user_id())) .await.unwrap().0.expect("Could not find user!"); HttpResponse::Ok().content_type("text/html").body( ChooseSecondFactorTemplate { _p: BaseLoginPage { page_title: "Two factor authentication", danger: None, success: None, app_name: APP_NAME, redirect_uri: query.redirect.get_encoded(), }, factors: &user.two_factor, } .render() .unwrap(), ) }