From da649df37e0fe6b09e8fd1660ac8df23fb209818 Mon Sep 17 00:00:00 2001 From: Gareth Rees Date: Fri, 25 Feb 2022 21:57:46 +0000 Subject: [PATCH] Record User::SignIn#country Record the geolocation country code of the IP at the time of sign in so that it can be used for misuse detection and filtering. --- app/controllers/application_controller.rb | 2 +- app/models/user/sign_in.rb | 4 +++- app/views/admin/users/_sign_in_table.html.erb | 12 ++++++++++++ .../20220225214524_add_country_to_user_sign_ins.rb | 5 +++++ spec/factories/user/sign_ins.rb | 4 +++- spec/models/user/sign_in_spec.rb | 10 ++++++++-- 6 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 db/migrate/20220225214524_add_country_to_user_sign_ins.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 86164fa6a52..e4eaccced12 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -152,7 +152,7 @@ def sign_in(user, remember_me: nil) session[:remember_me] = remember_me # Intentionally allow to fail silently so that we don't have to care whether # sign in recording is enabled. - user.sign_ins.create(ip: user_ip) + user.sign_ins.create(ip: user_ip, country: country_from_ip) end # Logout form diff --git a/app/models/user/sign_in.rb b/app/models/user/sign_in.rb index 35ccf273a78..d97cb56bbb1 100644 --- a/app/models/user/sign_in.rb +++ b/app/models/user/sign_in.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20220225094330 +# Schema version: 20220225214524 # # Table name: user_sign_ins # @@ -8,6 +8,7 @@ # ip :inet # created_at :datetime not null # updated_at :datetime not null +# country :string # # Record medadata about User sign in activity @@ -25,6 +26,7 @@ def self.purge def self.search(query) joins(:user).references(:users).where(<<~SQL, query: query) lower(user_sign_ins.ip::text) LIKE lower('%'||:query||'%') OR + lower(user_sign_ins.country) LIKE lower('%'||:query||'%') OR lower(users.name) LIKE lower('%'||:query||'%') OR lower(users.email) LIKE lower('%'||:query||'%') SQL diff --git a/app/views/admin/users/_sign_in_table.html.erb b/app/views/admin/users/_sign_in_table.html.erb index f8afd6b9afc..027ddac13fa 100644 --- a/app/views/admin/users/_sign_in_table.html.erb +++ b/app/views/admin/users/_sign_in_table.html.erb @@ -9,6 +9,18 @@ + + + <% if sign_in.country %> + <%= link_to admin_sign_ins_path(query: sign_in.country) do %> + <%= sign_in.country %> + <% end %> + <% else %> + ?? + <% end %> + + + <%= admin_date(sign_in.created_at, ago_only: true) %> diff --git a/db/migrate/20220225214524_add_country_to_user_sign_ins.rb b/db/migrate/20220225214524_add_country_to_user_sign_ins.rb new file mode 100644 index 00000000000..c4f50047d60 --- /dev/null +++ b/db/migrate/20220225214524_add_country_to_user_sign_ins.rb @@ -0,0 +1,5 @@ +class AddCountryToUserSignIns < ActiveRecord::Migration[6.1] + def change + add_column :user_sign_ins, :country, :string + end +end diff --git a/spec/factories/user/sign_ins.rb b/spec/factories/user/sign_ins.rb index eb95f99c60a..76827aab9d2 100644 --- a/spec/factories/user/sign_ins.rb +++ b/spec/factories/user/sign_ins.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20220225094330 +# Schema version: 20220225214524 # # Table name: user_sign_ins # @@ -8,11 +8,13 @@ # ip :inet # created_at :datetime not null # updated_at :datetime not null +# country :string # FactoryBot.define do factory :user_sign_in, class: 'User::SignIn' do user ip { '0.0.0.0' } + country { 'XX' } trait :ipv4 do ip { '0.0.0.0' } diff --git a/spec/models/user/sign_in_spec.rb b/spec/models/user/sign_in_spec.rb index e8913a4c452..0998098ad53 100644 --- a/spec/models/user/sign_in_spec.rb +++ b/spec/models/user/sign_in_spec.rb @@ -1,5 +1,5 @@ # == Schema Information -# Schema version: 20220225094330 +# Schema version: 20220225214524 # # Table name: user_sign_ins # @@ -8,6 +8,7 @@ # ip :inet # created_at :datetime not null # updated_at :datetime not null +# country :string # require 'spec_helper' @@ -68,7 +69,7 @@ let(:sign_in_2) do user = FactoryBot.create(:user, name: 'James', email: 'james@example.com') - FactoryBot.create(:user_sign_in, ip: '2.2.2.2', user: user) + FactoryBot.create(:user_sign_in, ip: '2.2.2.2', country: 'XY', user: user) end let(:sign_in_3) do @@ -111,6 +112,11 @@ let(:query) { 'example.com' } it { is_expected.to match_array([sign_in_2, sign_in_1]) } end + + context 'when given a country' do + let(:query) { 'XY' } + it { is_expected.to match_array([sign_in_2]) } + end end describe '#other_users' do