diff --git a/application/config/config.sample.php b/application/config/config.sample.php index 4c4ce6d5a..da602f15a 100644 --- a/application/config/config.sample.php +++ b/application/config/config.sample.php @@ -769,3 +769,11 @@ $config['disable_version_check'] = false; */ $config['enable_eqsl_massdownload'] = false; + +/* +|-------------------------------------------------------------------------- +| Lock Account after n failed login-attempts +|-------------------------------------------------------------------------- + */ + +$config['max_login_attempts'] = 3; diff --git a/application/config/migration.php b/application/config/migration.php index 5108d575b..bc1360558 100644 --- a/application/config/migration.php +++ b/application/config/migration.php @@ -22,7 +22,7 @@ $config['migration_enabled'] = TRUE; | */ -$config['migration_version'] = 233; +$config['migration_version'] = 234; /* |-------------------------------------------------------------------------- diff --git a/application/controllers/User.php b/application/controllers/User.php index b56d97c25..af67c9b24 100644 --- a/application/controllers/User.php +++ b/application/controllers/User.php @@ -25,6 +25,12 @@ class User extends CI_Controller { $data['disable_impersonate'] = false; } + if ($this->config->item('max_login_attempts')) { + $data['maxattempts'] = $this->config->item('max_login_attempts'); + } else { + $data['maxattempts'] = 3; + } + // Get Date format if($this->session->userdata('user_date_format')) { // If Logged in and session exists @@ -63,6 +69,12 @@ class User extends CI_Controller { $custom_date_format = $this->config->item('qso_date_format'); } + if ($this->config->item('max_login_attempts')) { + $maxattempts = $this->config->item('max_login_attempts'); + } else { + $maxattempts = 3; + } + if ($this->user_model->exists_by_id($data['user_id']) && $modal != '') { $user = $this->user_model->get_by_id($data['user_id'])->row(); $gettext = new Gettext; @@ -74,6 +86,7 @@ class User extends CI_Controller { $data['user_lastname'] = $user->user_lastname; $data['user_language'] = $gettext->find_by('folder', $user->user_language)['name_en']; $data['is_clubstation'] = $user->clubstation == 1 ? true : false; + $data['is_locked'] = $user->login_attempts > $maxattempts ? true : false; $data['last_seen'] = $user->last_seen; $data['custom_date_format'] = $custom_date_format; $data['has_flossie'] = ($this->config->item('encryption_key') == 'flossie1234555541') ? true : false; @@ -85,6 +98,24 @@ class User extends CI_Controller { } } + public function unlock($uid) { + $this->load->model('user_model'); + if(!$this->user_model->authorize(99)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } + + if ($this->user_model->exists_by_id($uid)) { + if ($this->user_model->unlock($uid)) { + $this->session->set_flashdata('success', __("User unlocked!")); + redirect('user'); + } else { + $this->session->set_flashdata('error', __("Failed to unlock user!")); + redirect('user'); + } + } else { + $this->session->set_flashdata('error', __("User not found!")); + redirect('dashboard'); + } + } + public function convert() { $this->load->model('user_model'); if(!$this->user_model->authorize(99)) { $this->session->set_flashdata('error', __("You're not allowed to do that!")); redirect('dashboard'); } @@ -1023,6 +1054,9 @@ class User extends CI_Controller { } else if ($login_attempt === 2) { $this->session->set_flashdata('warning', __("You can't login to a clubstation directly. Use your personal account instead.")); redirect('user/login'); + } else if ($login_attempt === 3) { + $this->session->set_flashdata('warning', __("Your account is locked, due to too many failed login-attempts. Please reset your password.")); + redirect('user/login'); } else { if(ENVIRONMENT == 'maintenance') { $this->session->set_flashdata('notice', __("Sorry. This instance is currently in maintenance mode. If this message appears unexpectedly or keeps showing up, please contact an administrator. Only administrators are currently allowed to log in.")); diff --git a/application/migrations/234_login_attempts.php b/application/migrations/234_login_attempts.php new file mode 100644 index 000000000..d465e3ab1 --- /dev/null +++ b/application/migrations/234_login_attempts.php @@ -0,0 +1,29 @@ +db->field_exists('login_attempts', 'users')) { + $fields = array( + 'login_attempts integer DEFAULT 0' + ); + + $this->dbforge->add_column('users', $fields); + } + } + + public function down() + { + if ($this->db->field_exists('login_attempts', 'users')) { + $this->dbforge->drop_column('users', 'login_attempts'); + } + } +} diff --git a/application/models/User_model.php b/application/models/User_model.php index a18e5632f..23aa6df9b 100644 --- a/application/models/User_model.php +++ b/application/models/User_model.php @@ -361,6 +361,7 @@ class User_Model extends CI_Model { if($data['user_password'] == EPASSWORDINVALID) { return EPASSWORDINVALID; } + $data['login_attempts'] = 0; } } } @@ -609,7 +610,19 @@ class User_Model extends CI_Model { return 2; } + if ($this->config->item('max_login_attempts')) { + $maxattempts = $this->config->item('max_login_attempts'); + } else { + $maxattempts = 3; + } + if ($u->row()->login_attempts > $maxattempts) { + $uid = $u->row()->user_id; + log_message('debug', "User ID: [$uid] Login rejected because of too many failed login attempts."); + return 3; + } + if($this->_auth($password, $u->row()->user_password)) { + $this->db->query("UPDATE users SET login_attempts = 0 WHERE user_id = ?", [$u->row()->user_id]); // Reset failurecount if (ENVIRONMENT != "maintenance") { return 1; } else { @@ -619,6 +632,8 @@ class User_Model extends CI_Model { return 1; } } + } else { // Update failurecount + $this->db->query("UPDATE users SET login_attempts = login_attempts+1 WHERE user_id = ?", [$u->row()->user_id]); } } return 0; @@ -657,6 +672,12 @@ class User_Model extends CI_Model { } } + // FUNCTION: bool unlock($user_id) + // Unlocks a user account after it was locked doe too many failed login attempts + function unlock($user_id) { + return $this->db->query("UPDATE users SET login_attempts = 0 WHERE user_id = ?", [$user_id]); + } + // FUNCTION: object users() // Returns a list of users with additional counts function users($club = '') { @@ -746,7 +767,8 @@ class User_Model extends CI_Model { $data = array( 'user_password' => $this->_hash($password), 'reset_password_code' => NULL, - 'reset_password_date' => NULL + 'reset_password_date' => NULL, + 'login_attempts' => 0 ); $this->db->where('reset_password_code', $reset_code); diff --git a/application/views/user/index.php b/application/views/user/index.php index cb56c254d..c14c8f9ff 100644 --- a/application/views/user/index.php +++ b/application/views/user/index.php @@ -70,17 +70,21 @@ } ?> - : stationcount; ?> -
- : logbookcount; ?> - qsocount > 0) { ?> -
lastqso; ?>"> - qsocount; ?> -
+ login_attempts > $maxattempts) { ?> + "> - "> - qsocount; ?> - + : stationcount; ?> +
+ : logbookcount; ?> + qsocount > 0) { ?> +
lastqso; ?>"> + qsocount; ?> +
+ + "> + qsocount; ?> + + diff --git a/application/views/user/modals/more_actions_modal.php b/application/views/user/modals/more_actions_modal.php index 1c79fa173..da5c44181 100644 --- a/application/views/user/modals/more_actions_modal.php +++ b/application/views/user/modals/more_actions_modal.php @@ -27,6 +27,12 @@
+ + + +
+ + +