Browse Source

Implement max login attempts

pull/2747/head
Guillaume Villena 3 years ago
parent
commit
485e21d95f
  1. 0
      migrations/mysql/2022-09-15-002500_add_login_attempts/down.sql
  2. 1
      migrations/mysql/2022-09-15-002500_add_login_attempts/up.sql
  3. 0
      migrations/postgresql/2022-09-15-002500_add_login_attempts/down.sql
  4. 1
      migrations/postgresql/2022-09-15-002500_add_login_attempts/up.sql
  5. 0
      migrations/sqlite/2022-09-15-002500_add_login_attempts/down.sql
  6. 1
      migrations/sqlite/2022-09-15-002500_add_login_attempts/up.sql
  7. 34
      src/api/identity.rs
  8. 3
      src/config.rs
  9. 3
      src/db/models/user.rs
  10. 1
      src/db/schemas/mysql/schema.rs
  11. 1
      src/db/schemas/postgresql/schema.rs
  12. 1
      src/db/schemas/sqlite/schema.rs

0
migrations/mysql/2022-09-15-002500_add_login_attempts/down.sql

1
migrations/mysql/2022-09-15-002500_add_login_attempts/up.sql

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN invalid_login_count INTEGER NOT NULL DEFAULT 0;

0
migrations/postgresql/2022-09-15-002500_add_login_attempts/down.sql

1
migrations/postgresql/2022-09-15-002500_add_login_attempts/up.sql

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN invalid_login_count INTEGER NOT NULL DEFAULT 0;

0
migrations/sqlite/2022-09-15-002500_add_login_attempts/down.sql

1
migrations/sqlite/2022-09-15-002500_add_login_attempts/up.sql

@ -0,0 +1 @@
ALTER TABLE users ADD COLUMN invalid_login_count INTEGER NOT NULL DEFAULT 0;

34
src/api/identity.rs

@ -105,15 +105,36 @@ async fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> Json
None => err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username)), None => err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username)),
}; };
// Check if the user is disabled
if !user.enabled {
err!("This user has been disabled", format!("IP: {}. Username: {}.", ip.ip, username))
}
// Check password // Check password
let password = data.password.as_ref().unwrap(); let password = data.password.as_ref().unwrap();
if !user.check_valid_password(password) { if !user.check_valid_password(password) {
err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username)) // When the configuration limits the number of attempts
if CONFIG.login_max_retry() > 0 {
let mut user = user;
let invalid_count = user.invalid_login_count;
//It is already the nth attempts, disable the user !
if user.invalid_login_count >= CONFIG.login_max_retry() {
user.enabled = false;
user.invalid_login_count = 0;
} else {
user.invalid_login_count += 1;
}
if let Err(e) = user.save(&conn).await {
error!("Error updating user: {:#?}", e);
} }
// Check if the user is disabled
if !user.enabled { if !user.enabled {
err!("This user has been disabled", format!("IP: {}. Username: {}.", ip.ip, username)) err!("Too many failed login attempts. User has been disabled", format!("IP: {}. Username: {}. Invalid logins: {}.", ip.ip, username, invalid_count))
}
}
err!("Username or password is incorrect. Try again", format!("IP: {}. Username: {}.", ip.ip, username))
} }
let now = Utc::now().naive_utc(); let now = Utc::now().naive_utc();
@ -184,6 +205,13 @@ async fn _password_login(data: ConnectData, conn: DbConn, ip: &ClientIp) -> Json
result["TwoFactorToken"] = Value::String(token); result["TwoFactorToken"] = Value::String(token);
} }
// Reset the number of attempts on success logins
let mut user = user;
user.invalid_login_count = 0;
if let Err(e) = user.save(&conn).await {
error!("Error updating user: {:#?}", e);
}
info!("User {} logged in successfully. IP: {}", username, ip.ip); info!("User {} logged in successfully. IP: {}", username, ip.ip);
Ok(Json(result)) Ok(Json(result))
} }

3
src/config.rs

@ -543,6 +543,9 @@ make_config! {
admin_ratelimit_seconds: u64, false, def, 300; admin_ratelimit_seconds: u64, false, def, 300;
/// Max burst size for login requests |> Allow a burst of requests of up to this size, while maintaining the average indicated by `admin_ratelimit_seconds` /// Max burst size for login requests |> Allow a burst of requests of up to this size, while maintaining the average indicated by `admin_ratelimit_seconds`
admin_ratelimit_max_burst: u32, false, def, 3; admin_ratelimit_max_burst: u32, false, def, 3;
/// Max number of login retries before user being disabled |> Limit the number of login attempts before the user is disabled automatically. 0 means no login attempts limits. Greater equal 1 means that user can retry 1 or more time before the account being locked.
login_max_retry: i32, false, def, 0;
}, },
/// Yubikey settings /// Yubikey settings

3
src/db/models/user.rs

@ -17,6 +17,7 @@ db_object! {
pub verified_at: Option<NaiveDateTime>, pub verified_at: Option<NaiveDateTime>,
pub last_verifying_at: Option<NaiveDateTime>, pub last_verifying_at: Option<NaiveDateTime>,
pub login_verify_count: i32, pub login_verify_count: i32,
pub invalid_login_count: i32,
pub email: String, pub email: String,
pub email_new: Option<String>, pub email_new: Option<String>,
@ -86,6 +87,8 @@ impl User {
verified_at: None, verified_at: None,
last_verifying_at: None, last_verifying_at: None,
login_verify_count: 0, login_verify_count: 0,
invalid_login_count: 0,
name: email.clone(), name: email.clone(),
email, email,
akey: String::new(), akey: String::new(),

1
src/db/schemas/mysql/schema.rs

@ -159,6 +159,7 @@ table! {
verified_at -> Nullable<Datetime>, verified_at -> Nullable<Datetime>,
last_verifying_at -> Nullable<Datetime>, last_verifying_at -> Nullable<Datetime>,
login_verify_count -> Integer, login_verify_count -> Integer,
invalid_login_count -> Integer,
email -> Text, email -> Text,
email_new -> Nullable<Text>, email_new -> Nullable<Text>,
email_new_token -> Nullable<Text>, email_new_token -> Nullable<Text>,

1
src/db/schemas/postgresql/schema.rs

@ -159,6 +159,7 @@ table! {
verified_at -> Nullable<Timestamp>, verified_at -> Nullable<Timestamp>,
last_verifying_at -> Nullable<Timestamp>, last_verifying_at -> Nullable<Timestamp>,
login_verify_count -> Integer, login_verify_count -> Integer,
invalid_login_count -> Integer,
email -> Text, email -> Text,
email_new -> Nullable<Text>, email_new -> Nullable<Text>,
email_new_token -> Nullable<Text>, email_new_token -> Nullable<Text>,

1
src/db/schemas/sqlite/schema.rs

@ -159,6 +159,7 @@ table! {
verified_at -> Nullable<Timestamp>, verified_at -> Nullable<Timestamp>,
last_verifying_at -> Nullable<Timestamp>, last_verifying_at -> Nullable<Timestamp>,
login_verify_count -> Integer, login_verify_count -> Integer,
invalid_login_count -> Integer,
email -> Text, email -> Text,
email_new -> Nullable<Text>, email_new -> Nullable<Text>,
email_new_token -> Nullable<Text>, email_new_token -> Nullable<Text>,

Loading…
Cancel
Save