Enable bruteforce protection on login endpoint
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use actix_web::HttpResponse;
|
||||
use askama::Template;
|
||||
|
||||
use crate::constants::LOGIN_ROUTE;
|
||||
|
||||
@ -24,3 +25,10 @@ pub fn redirect_user_for_login<P: Display>(redirect_uri: P) -> HttpResponse {
|
||||
))
|
||||
.finish()
|
||||
}
|
||||
|
||||
/// Fatal error page message
|
||||
#[derive(Template)]
|
||||
#[template(path = "fatal_error.html")]
|
||||
pub struct FatalErrorPage {
|
||||
pub message: &'static str,
|
||||
}
|
@ -1,13 +1,16 @@
|
||||
use actix::Addr;
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
use actix_web::{HttpRequest, HttpResponse, Responder, web};
|
||||
use askama::Template;
|
||||
|
||||
use crate::actors::users_actor;
|
||||
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, MIN_PASS_LEN};
|
||||
use crate::controllers::base_controller::redirect_user;
|
||||
use crate::constants::{APP_NAME, MAX_FAILED_LOGIN_ATTEMPTS, MIN_PASS_LEN};
|
||||
use crate::controllers::base_controller::{FatalErrorPage, redirect_user};
|
||||
use crate::data::app_config::AppConfig;
|
||||
use crate::data::session_identity::{SessionIdentity, SessionStatus};
|
||||
use crate::utils::network_utils::{get_remote_ip, parse_ip};
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "base_login_page.html")]
|
||||
@ -47,15 +50,38 @@ pub struct LoginRequestQuery {
|
||||
|
||||
/// Authenticate user
|
||||
pub async fn login_route(
|
||||
http_req: HttpRequest,
|
||||
users: web::Data<Addr<UsersActor>>,
|
||||
bruteforce: web::Data<Addr<BruteForceActor>>,
|
||||
query: web::Query<LoginRequestQuery>,
|
||||
req: Option<web::Form<LoginRequestBody>>,
|
||||
config: web::Data<AppConfig>,
|
||||
id: Identity,
|
||||
) -> impl Responder {
|
||||
let mut danger = String::new();
|
||||
let mut success = String::new();
|
||||
let mut login = String::new();
|
||||
|
||||
let remote_ip = match parse_ip(&get_remote_ip(&http_req, config.proxy_ip.as_deref())) {
|
||||
None => return HttpResponse::InternalServerError().body(
|
||||
FatalErrorPage {
|
||||
message: "Failed to determine remote ip address!"
|
||||
}.render().unwrap()
|
||||
),
|
||||
Some(i) => i,
|
||||
};
|
||||
|
||||
let failed_attempts = bruteforce.send(bruteforce_actor::CountFailedAttempt { ip: remote_ip })
|
||||
.await.unwrap();
|
||||
|
||||
if failed_attempts > MAX_FAILED_LOGIN_ATTEMPTS {
|
||||
return HttpResponse::InternalServerError().body(
|
||||
FatalErrorPage {
|
||||
message: "Too many failed login attempts, please try again later!"
|
||||
}.render().unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
let redirect_uri = match query.redirect.as_deref() {
|
||||
None => "/",
|
||||
Some(s) => match s.starts_with('/') && !s.starts_with("//") {
|
||||
@ -125,8 +151,10 @@ pub async fn login_route(
|
||||
}
|
||||
|
||||
c => {
|
||||
log::warn!("Failed login for username {} : {:?}", login, c);
|
||||
log::warn!("Failed login for ip {:?} / username {}: {:?}", remote_ip, login, c);
|
||||
danger = "Login failed.".to_string();
|
||||
|
||||
bruteforce.send(bruteforce_actor::RecordFailedAttempt { ip: remote_ip }).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,8 +172,8 @@ pub async fn login_route(
|
||||
},
|
||||
min_pass_len: MIN_PASS_LEN,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
.render()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -160,8 +188,8 @@ pub async fn login_route(
|
||||
},
|
||||
login,
|
||||
}
|
||||
.render()
|
||||
.unwrap(),
|
||||
.render()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user