diff --git a/app/models/user_auth_token.rb b/app/models/user_auth_token.rb index 30a38c68bc1..48079e351d7 100644 --- a/app/models/user_auth_token.rb +++ b/app/models/user_auth_token.rb @@ -8,6 +8,8 @@ class UserAuthToken < ActiveRecord::Base # used when token did not arrive at client URGENT_ROTATE_TIME = 1.minute + MAX_SESSION_COUNT = 60 + USER_ACTIONS = ['generate'] attr_accessor :unhashed_auth_token @@ -220,6 +222,14 @@ class UserAuthToken < ActiveRecord::Base end end + + def self.enforce_session_count_limit!(user_id) + tokens_to_destroy = where(user_id: user_id). + where('rotated_at > ?', SiteSetting.maximum_session_age.hours.ago). + order("rotated_at DESC").offset(MAX_SESSION_COUNT) + + tokens_to_destroy.delete_all # Returns the number of deleted rows + end end # == Schema Information diff --git a/lib/auth/default_current_user_provider.rb b/lib/auth/default_current_user_provider.rb index 3f405233ccd..86d6bb3d5ee 100644 --- a/lib/auth/default_current_user_provider.rb +++ b/lib/auth/default_current_user_provider.rb @@ -164,6 +164,9 @@ class Auth::DefaultCurrentUserProvider unstage_user(user) make_developer_admin(user) enable_bootstrap_mode(user) + + UserAuthToken.enforce_session_count_limit!(user.id) + @env[CURRENT_USER_KEY] = user end diff --git a/spec/components/auth/default_current_user_provider_spec.rb b/spec/components/auth/default_current_user_provider_spec.rb index 616de7b4c03..08be6290bc3 100644 --- a/spec/components/auth/default_current_user_provider_spec.rb +++ b/spec/components/auth/default_current_user_provider_spec.rb @@ -586,6 +586,33 @@ describe Auth::DefaultCurrentUserProvider do expect(UserAuthToken.where(user_id: user.id).count).to eq(2) end + it "cleans up old sessions when a user logs in" do + user = Fabricate(:user) + + yesterday = 1.day.ago + + UserAuthToken.insert_all((1..(UserAuthToken::MAX_SESSION_COUNT + 2)).to_a.map do |i| + { + user_id: user.id, + created_at: yesterday + i.seconds, + updated_at: yesterday + i.seconds, + rotated_at: yesterday + i.seconds, + prev_auth_token: "abc#{i}", + auth_token: "abc#{i}" + } + end) + + # Check the oldest 3 still exist + expect(UserAuthToken.where(auth_token: (1..3).map { |i| "abc#{i}" }).count).to eq(3) + + # On next login, gets fixed + provider('/').log_on_user(user, {}, {}) + expect(UserAuthToken.where(user_id: user.id).count).to eq(UserAuthToken::MAX_SESSION_COUNT) + + # Oldest sessions are 1, 2, 3. They should now be deleted + expect(UserAuthToken.where(auth_token: (1..3).map { |i| "abc#{i}" }).count).to eq(0) + end + it "sets secure, same site lax cookies" do SiteSetting.force_https = false SiteSetting.same_site_cookies = "Lax"