From 84c601460fe2be392689378ba6c3aa9de714c31b Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Tue, 11 May 2021 12:02:05 +0100 Subject: [PATCH] Add rate limiting to user friendships --- app/controllers/friendships_controller.rb | 2 ++ app/models/user.rb | 9 +++++++++ config/locales/en.yml | 1 + config/settings.yml | 2 ++ .../20210511104518_add_time_to_friendships.rb | 9 +++++++++ db/structure.sql | 18 ++++++++++-------- 6 files changed, 33 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20210511104518_add_time_to_friendships.rb diff --git a/app/controllers/friendships_controller.rb b/app/controllers/friendships_controller.rb index 0bff13df6..5cdb2a4e4 100644 --- a/app/controllers/friendships_controller.rb +++ b/app/controllers/friendships_controller.rb @@ -19,6 +19,8 @@ class FriendshipsController < ApplicationController friendship.befriendee = @new_friend if current_user.is_friends_with?(@new_friend) flash[:warning] = t "friendships.make_friend.already_a_friend", :name => @new_friend.display_name + elsif current_user.friendships.where("created_at >= ?", Time.now.getutc - 1.hour).count >= current_user.max_friends_per_hour + flash.now[:error] = t "friendships.make_friend.limit_exceeded" elsif friendship.save flash[:notice] = t "friendships.make_friend.success", :name => @new_friend.display_name UserMailer.friendship_notification(friendship).deliver_later diff --git a/app/models/user.rb b/app/models/user.rb index 26a9f33e1..a2c0619de 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -305,6 +305,15 @@ class User < ApplicationRecord max_messages.clamp(0, Settings.max_messages_per_hour) end + def max_friends_per_hour + account_age_in_seconds = Time.now.utc - creation_time + account_age_in_hours = account_age_in_seconds / 3600 + recent_friends = friendships.where("created_at >= ?", Time.now.utc - 3600).count + active_reports = issues.with_status(:open).sum(:reports_count) + max_messages = account_age_in_hours.ceil + recent_friends - active_reports * 10 + max_messages.clamp(0, Settings.max_friends_per_hour) + end + private def set_defaults diff --git a/config/locales/en.yml b/config/locales/en.yml index cfe717fe2..e04a6ac87 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -487,6 +487,7 @@ en: success: "%{name} is now your friend!" failed: "Sorry, failed to add %{name} as a friend." already_a_friend: "You are already friends with %{name}." + limit_exceeded: "You have friended a lot of users recently. Please wait a while before trying to friend any more." remove_friend: heading: "Unfriend %{user}?" button: "Unfriend" diff --git a/config/settings.yml b/config/settings.yml index 3dd732972..81ab37015 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -45,6 +45,8 @@ web_timeout: 30 user_block_periods: [0, 1, 3, 6, 12, 24, 48, 96, 168, 336, 731, 4383, 8766, 87660] # Rate limit for message sending max_messages_per_hour: 60 +# Rate limit for friending +max_friends_per_hour: 60 # Domain for handling message replies #messages_domain: "messages.openstreetmap.org" # Geonames authentication details diff --git a/db/migrate/20210511104518_add_time_to_friendships.rb b/db/migrate/20210511104518_add_time_to_friendships.rb new file mode 100644 index 000000000..4f426a63f --- /dev/null +++ b/db/migrate/20210511104518_add_time_to_friendships.rb @@ -0,0 +1,9 @@ +class AddTimeToFriendships < ActiveRecord::Migration[6.0] + disable_ddl_transaction! + + def change + add_column :friends, :created_at, :datetime + add_index :friends, [:user_id, :created_at], :algorithm => :concurrently + remove_index :friends, :column => :user_id, :name => "friends_user_id_idx" + end +end diff --git a/db/structure.sql b/db/structure.sql index 89a62626e..957dee817 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -720,7 +720,8 @@ CREATE TABLE public.diary_entry_subscriptions ( CREATE TABLE public.friends ( id bigint NOT NULL, user_id bigint NOT NULL, - friend_user_id bigint NOT NULL + friend_user_id bigint NOT NULL, + created_at timestamp without time zone ); @@ -2146,13 +2147,6 @@ CREATE INDEX diary_entry_language_code_created_at_index ON public.diary_entries CREATE INDEX diary_entry_user_id_created_at_index ON public.diary_entries USING btree (user_id, created_at); --- --- Name: friends_user_id_idx; Type: INDEX; Schema: public; Owner: - --- - -CREATE INDEX friends_user_id_idx ON public.friends USING btree (user_id); - - -- -- Name: gpx_file_tags_gpxid_idx; Type: INDEX; Schema: public; Owner: - -- @@ -2279,6 +2273,13 @@ CREATE INDEX index_client_applications_on_user_id ON public.client_applications CREATE INDEX index_diary_entry_subscriptions_on_diary_entry_id ON public.diary_entry_subscriptions USING btree (diary_entry_id); +-- +-- Name: index_friends_on_user_id_and_created_at; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_friends_on_user_id_and_created_at ON public.friends USING btree (user_id, created_at); + + -- -- Name: index_issue_comments_on_issue_id; Type: INDEX; Schema: public; Owner: - -- @@ -3123,6 +3124,7 @@ INSERT INTO "schema_migrations" (version) VALUES ('20201006213836'), ('20201006220807'), ('20201214144017'), +('20210511104518'), ('21'), ('22'), ('23'), -- 2.39.5