From 378bcf1f8f08d343974cb7828c715a6f1b01f565 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Sat, 31 Aug 2019 16:40:48 +1000 Subject: [PATCH 1/8] 1.5.1: User IP addresses are now logged. Introduce registration throttle. Throttle values are hard coded for the moment, still need to make them configurable --- components/Account.php | 28 +++++++++++++++++++++ lang/en/lang.php | 3 +++ models/User.php | 43 +++++++++++++++++++++++++++++++- models/user/columns.yaml | 10 ++++++++ models/user/fields.yaml | 14 +++++++++++ updates/users_add_ip_address.php | 28 +++++++++++++++++++++ updates/version.yaml | 3 +++ 7 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 updates/users_add_ip_address.php diff --git a/components/Account.php b/components/Account.php index 9ed31205..cd384572 100644 --- a/components/Account.php +++ b/components/Account.php @@ -14,6 +14,7 @@ use October\Rain\Auth\AuthException; use Cms\Classes\Page; use Cms\Classes\ComponentBase; +use RainLab\User\Models\User as UserModel; use RainLab\User\Models\Settings as UserSettings; use Exception; @@ -234,6 +235,13 @@ public function onSignin() throw new AuthException(/*Sorry, this user is currently not activated. Please contact us for further assistance.*/'rainlab.user::lang.account.banned'); } + /* + * Record IP address + */ + if ($ipAddress = Request::ip()) { + $user->touchIpAddress($ipAddress); + } + /* * Redirect */ @@ -257,6 +265,10 @@ public function onRegister() throw new ApplicationException(Lang::get(/*Registrations are currently disabled.*/'rainlab.user::lang.account.registration_disabled')); } + if ($this->isRegisterThrottled()) { + throw new ApplicationException(Lang::get(/*Registration is throttled. Please try again later.*/'rainlab.user::lang.account.registration_throttled')); + } + /* * Validate input */ @@ -280,6 +292,13 @@ public function onRegister() throw new ValidationException($validation); } + /* + * Record IP address + */ + if ($ipAddress = Request::ip()) { + $data['created_ip_address'] = $data['last_ip_address'] = $ipAddress; + } + /* * Register user */ @@ -563,4 +582,13 @@ protected function redirectForceSecure() return Redirect::secure(Request::path()); } + + /** + * Returns true if user is throttled. + * @return bool + */ + protected function isRegisterThrottled() + { + return UserModel::isRegisterThrottled(Request::ip()); + } } diff --git a/lang/en/lang.php b/lang/en/lang.php index ef4facc3..14297e81 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -132,6 +132,8 @@ 'status_guest' => 'Guest', 'status_activated' => 'Activated', 'status_registered' => 'Registered', + 'created_ip_address' => 'Created IP Address', + 'last_ip_address' => 'Last IP Address', ], 'group' => [ 'label' => 'Group', @@ -183,6 +185,7 @@ 'already_active' => 'Your account is already activated!', 'activation_email_sent' => 'An activation email has been sent to your email address.', 'registration_disabled' => 'Registrations are currently disabled.', + 'registration_throttled' => 'Registration is throttled. Please try again later.', 'sign_in' => 'Sign in', 'register' => 'Register', 'full_name' => 'Full Name', diff --git a/models/User.php b/models/User.php index 999ae36a..9f3c7b66 100644 --- a/models/User.php +++ b/models/User.php @@ -4,6 +4,7 @@ use Auth; use Mail; use Event; +use Carbon\Carbon; use October\Rain\Auth\Models\User as UserBase; use RainLab\User\Models\Settings as UserSettings; use October\Rain\Auth\AuthException; @@ -49,7 +50,9 @@ class User extends UserBase 'username', 'email', 'password', - 'password_confirmation' + 'password_confirmation', + 'created_ip_address', + 'last_ip_address' ]; /** @@ -354,6 +357,44 @@ public function isBanned() return $throttle ? $throttle->is_banned : false; } + // + // IP Recording and Throttle + // + + /** + * Records the last_ip_address to reflect the last known IP for this user. + * @return void + */ + public function touchIpAddress($ipAddress) + { + $this + ->newQuery() + ->where('id', $this->id) + ->update(['last_ip_address' => $ipAddress]) + ; + } + + /** + * Returns true if IP address is throttled and cannot register + * again. Maximum 3 registrations every 15 minutes. + * @return bool + */ + public static function isRegisterThrottled($ipAddress) + { + if (!$ipAddress) { + return false; + } + + $timeLimit = Carbon::now()->subMinutes(15); + $count = static::make() + ->where('created_ip_address', $ipAddress) + ->where('created_at', '>', $timeLimit) + ->count() + ; + + return $count > 2; + } + // // Last Seen // diff --git a/models/user/columns.yaml b/models/user/columns.yaml index 9b091b77..f3c27be9 100644 --- a/models/user/columns.yaml +++ b/models/user/columns.yaml @@ -38,3 +38,13 @@ columns: label: rainlab.user::lang.user.is_guest type: switch invisible: true + + created_ip_address: + label: rainlab.user::lang.user.created_ip_address + searchable: true + invisible: true + + last_ip_address: + label: rainlab.user::lang.user.last_ip_address + searchable: true + invisible: true diff --git a/models/user/fields.yaml b/models/user/fields.yaml index 78a0e078..7b9d394a 100644 --- a/models/user/fields.yaml +++ b/models/user/fields.yaml @@ -70,6 +70,20 @@ tabs: type: relation emptyOption: rainlab.user::lang.user.empty_groups + created_ip_address: + label: rainlab.user::lang.user.created_ip_address + span: auto + disabled: true + tab: rainlab.user::lang.user.account + context: preview + + last_ip_address: + label: rainlab.user::lang.user.last_ip_address + span: auto + disabled: true + tab: rainlab.user::lang.user.account + context: preview + secondaryTabs: fields: diff --git a/updates/users_add_ip_address.php b/updates/users_add_ip_address.php new file mode 100644 index 00000000..8b307cce --- /dev/null +++ b/updates/users_add_ip_address.php @@ -0,0 +1,28 @@ +string('created_ip_address')->nullable(); + $table->string('last_ip_address')->nullable(); + }); + } + + public function down() + { + if (Schema::hasColumn('users', 'created_ip_address')) { + Schema::table('users', function($table) + { + $table->dropColumn('created_ip_address'); + $table->dropColumn('last_ip_address'); + }); + } + } +} diff --git a/updates/version.yaml b/updates/version.yaml index 9a7ce8e1..4b03e6a2 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -68,3 +68,6 @@ 1.4.7: Fixes redirect bug in Account component / Update translations and separate user and group management. 1.4.8: Fixes a bug where calling MailBlocker::removeBlock could remove all mail blocks for the user. 1.5.0: !!! Required password length is now a minimum of 8 characters. Previous passwords will not be affected until the next password change. +1.5.1: + - User IP addresses are now logged. Introduce registration throttle. + - users_add_ip_address.php From 2c55fb614643acdfa7055aef3de946815d774479 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Sat, 31 Aug 2019 18:47:51 +1000 Subject: [PATCH 2/8] Implement setting to disable register throttle --- components/Account.php | 4 ++++ lang/en/lang.php | 2 ++ models/Settings.php | 1 + models/settings/fields.yaml | 10 +++++++++- 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/components/Account.php b/components/Account.php index cd384572..091b0650 100644 --- a/components/Account.php +++ b/components/Account.php @@ -589,6 +589,10 @@ protected function redirectForceSecure() */ protected function isRegisterThrottled() { + if (!UserSettings::get('use_register_throttle', true)) { + return false; + } + return UserModel::isRegisterThrottled(Request::ip()); } } diff --git a/lang/en/lang.php b/lang/en/lang.php index 14297e81..67a1072f 100644 --- a/lang/en/lang.php +++ b/lang/en/lang.php @@ -89,6 +89,8 @@ 'require_activation_comment' => 'Users must have an activated account to sign in.', 'use_throttle' => 'Throttle attempts', 'use_throttle_comment' => 'Repeat failed sign in attempts will temporarily suspend the user.', + 'use_register_throttle' => 'Throttle registration', + 'use_register_throttle_comment' => 'Prevent multiple registrations from the same IP in short succession.', 'block_persistence' => 'Prevent concurrent sessions', 'block_persistence_comment' => 'When enabled users cannot sign in to multiple devices at the same time.', 'login_attribute' => 'Login attribute', diff --git a/models/Settings.php b/models/Settings.php index 89274268..0feb1d59 100644 --- a/models/Settings.php +++ b/models/Settings.php @@ -38,6 +38,7 @@ public function initSettingsData() $this->update_requires_password = false; $this->remember_login = self::REMEMBER_ALWAYS; $this->min_password_length = self::MIN_PASSWORD_LENGTH_DEFAULT; + $this->use_register_throttle = true; } public function getActivateModeOptions() diff --git a/models/settings/fields.yaml b/models/settings/fields.yaml index 470ec100..ac6c9785 100644 --- a/models/settings/fields.yaml +++ b/models/settings/fields.yaml @@ -30,7 +30,7 @@ tabs: # Remeber Login Mode remember_login: - span: left + span: right label: rainlab.user::lang.settings.remember_login commentAbove: rainlab.user::lang.settings.remember_login_comment type: radio @@ -44,6 +44,14 @@ tabs: type: switch tab: rainlab.user::lang.settings.registration_tab + # Require Activation + use_register_throttle: + span: right + label: rainlab.user::lang.settings.use_register_throttle + comment: rainlab.user::lang.settings.use_register_throttle_comment + type: switch + tab: rainlab.user::lang.settings.registration_tab + # Minimum password length min_password_length: span: left From 3afc138a4673d360c94420ed06cd8c02c24acdf8 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Sat, 31 Aug 2019 18:50:37 +1000 Subject: [PATCH 3/8] Tighten limit to 3 per hour --- models/User.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/User.php b/models/User.php index 9f3c7b66..52718f94 100644 --- a/models/User.php +++ b/models/User.php @@ -376,7 +376,7 @@ public function touchIpAddress($ipAddress) /** * Returns true if IP address is throttled and cannot register - * again. Maximum 3 registrations every 15 minutes. + * again. Maximum 3 registrations every 60 minutes. * @return bool */ public static function isRegisterThrottled($ipAddress) @@ -385,7 +385,7 @@ public static function isRegisterThrottled($ipAddress) return false; } - $timeLimit = Carbon::now()->subMinutes(15); + $timeLimit = Carbon::now()->subMinutes(60); $count = static::make() ->where('created_ip_address', $ipAddress) ->where('created_at', '>', $timeLimit) From 19c5266df13b827887b6aea677a9afce70409cfb Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sat, 31 Aug 2019 21:07:59 +0800 Subject: [PATCH 4/8] Update comment to reflect setting --- models/settings/fields.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/settings/fields.yaml b/models/settings/fields.yaml index ac6c9785..735a6e54 100644 --- a/models/settings/fields.yaml +++ b/models/settings/fields.yaml @@ -44,7 +44,7 @@ tabs: type: switch tab: rainlab.user::lang.settings.registration_tab - # Require Activation + # Enable registration throttling use_register_throttle: span: right label: rainlab.user::lang.settings.use_register_throttle From 800b663fe3c6e87639a823d37b0e7374efde1295 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sat, 31 Aug 2019 21:08:20 +0800 Subject: [PATCH 5/8] Add parameter to the docblock --- models/User.php | 1 + 1 file changed, 1 insertion(+) diff --git a/models/User.php b/models/User.php index 52718f94..91d311d8 100644 --- a/models/User.php +++ b/models/User.php @@ -363,6 +363,7 @@ public function isBanned() /** * Records the last_ip_address to reflect the last known IP for this user. + * @param string|null $ipAddress * @return void */ public function touchIpAddress($ipAddress) From 2a944bb63dd7f9e10933f2353098f3b03533e769 Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sat, 31 Aug 2019 21:08:34 +0800 Subject: [PATCH 6/8] Add parameter to the docblock --- models/User.php | 1 + 1 file changed, 1 insertion(+) diff --git a/models/User.php b/models/User.php index 91d311d8..38760a9d 100644 --- a/models/User.php +++ b/models/User.php @@ -378,6 +378,7 @@ public function touchIpAddress($ipAddress) /** * Returns true if IP address is throttled and cannot register * again. Maximum 3 registrations every 60 minutes. + * @param string|null $ipAddress * @return bool */ public static function isRegisterThrottled($ipAddress) From c784b13bc08ff687128a248ac6101bf32744f0e0 Mon Sep 17 00:00:00 2001 From: Samuel Georges Date: Sun, 1 Sep 2019 11:04:45 +1000 Subject: [PATCH 7/8] Don't enable by default --- components/Account.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Account.php b/components/Account.php index 091b0650..adfd7f37 100644 --- a/components/Account.php +++ b/components/Account.php @@ -589,7 +589,7 @@ protected function redirectForceSecure() */ protected function isRegisterThrottled() { - if (!UserSettings::get('use_register_throttle', true)) { + if (!UserSettings::get('use_register_throttle', false)) { return false; } From 9b0bce2a8add7811853fb29010e7e753f12ba81e Mon Sep 17 00:00:00 2001 From: Ben Thomson Date: Sun, 1 Sep 2019 09:32:36 +0800 Subject: [PATCH 8/8] Update notes --- updates/version.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updates/version.yaml b/updates/version.yaml index 4b03e6a2..980d2185 100644 --- a/updates/version.yaml +++ b/updates/version.yaml @@ -69,5 +69,5 @@ 1.4.8: Fixes a bug where calling MailBlocker::removeBlock could remove all mail blocks for the user. 1.5.0: !!! Required password length is now a minimum of 8 characters. Previous passwords will not be affected until the next password change. 1.5.1: - - User IP addresses are now logged. Introduce registration throttle. + - User IP addresses are now logged. Introduce registration throttle and "remember login" functionality. - users_add_ip_address.php