diff --git a/app/controllers/admin/settings/rate_limits_controller.rb b/app/controllers/admin/settings/rate_limits_controller.rb new file mode 100644 index 000000000..646d6632c --- /dev/null +++ b/app/controllers/admin/settings/rate_limits_controller.rb @@ -0,0 +1,22 @@ +module Admin + module Settings + class RateLimitsController < Admin::ApplicationController + def create + result = ::RateLimits::SettingsUpsert.call(settings_params) + + if result.success? + Audit::Logger.log(:internal, current_user, params.dup) + redirect_to admin_config_path, notice: "Site configuration was successfully updated." + else + redirect_to admin_config_path, alert: "😭 #{result.errors.to_sentence}" + end + end + + def settings_params + params + .require(:settings_rate_limit) + .permit(*::Settings::RateLimit.keys) + end + end + end +end diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index 6981e4311..a39545fec 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -108,7 +108,7 @@ class UserDecorator < ApplicationDecorator end def considered_new? - min_days = SiteConfig.user_considered_new_days + min_days = Settings::RateLimit.user_considered_new_days return false unless min_days.positive? created_at.after?(min_days.days.ago) diff --git a/app/helpers/rate_limit_checker_helper.rb b/app/helpers/rate_limit_checker_helper.rb index f027877f0..57bbb43d0 100644 --- a/app/helpers/rate_limit_checker_helper.rb +++ b/app/helpers/rate_limit_checker_helper.rb @@ -8,91 +8,91 @@ module RateLimitCheckerHelper end CONFIGURABLE_RATES = { - rate_limit_published_article_creation: { + published_article_creation: { min: 0, placeholder: 9, title: "Limit number of posts created", description: "How many posts can someone create within any 30 second period?" }, - rate_limit_published_article_antispam_creation: { + published_article_antispam_creation: { min: 0, placeholder: 1, title: "Limit number of posts created by a new member", description: new_user_message("posts") }, - rate_limit_article_update: { + article_update: { min: 1, placeholder: 30, title: "Limit number of updates to a post", description: "How many updates can someone make within any 30 second period? Update API docs when changed." }, - rate_limit_image_upload: { + image_upload: { min: 0, placeholder: 9, title: "Limit number of images uploaded", description: "How many images can someone upload within any 30 second period?" }, - rate_limit_user_update: { + user_update: { min: 1, placeholder: 5, title: "Limit number of changes someone can make to their account", description: "How many changes can someone make to their user account within any 30 second period?" }, - rate_limit_follow_count_daily: { + follow_count_daily: { min: 0, placeholder: 500, title: "Limit number of followers someone can follow daily", description: "How many people can someone follow in a day?" }, - rate_limit_reaction_creation: { + reaction_creation: { min: 1, placeholder: 10, title: "Limit number of reactions to a post or comment", description: "How many times can someone react to a post or comment within any 30 second period?" }, - rate_limit_feedback_message_creation: { + feedback_message_creation: { min: 1, placeholder: 5, title: "Limit number of times someone can report abuse", description: "How many times can someone report abuse within any 5 minute period?" }, - rate_limit_comment_creation: { + comment_creation: { min: 0, placeholder: 9, title: "Limit number of comments created", description: "How many comments can someone create within any 30 second period?" }, - rate_limit_comment_antispam_creation: { + comment_antispam_creation: { min: 0, placeholder: 1, title: "Limit number of comments created by a new member", description: new_user_message("comments") }, - rate_limit_listing_creation: { + listing_creation: { min: 1, placeholder: 1, title: "Limit number of listings created", description: "How many listings can someone create within any 1 minute period?" }, - rate_limit_organization_creation: { + organization_creation: { min: 1, placeholder: 1, title: "Limit number of organizations created", description: "How many organizations can someone create within any 5 minute period?" }, - rate_limit_user_subscription_creation: { + user_subscription_creation: { min: 0, placeholder: 3, title: "Limit number of times someone can subscribe to mailing list liquid tag", description: "How many times can someone subscribe to a mailing list within any 30 second period?" }, - rate_limit_email_recipient: { + email_recipient: { min: 0, placeholder: 5, title: "Limit number of general emails we send", description: "How many emails can we send to someone within any 2 minute period?" }, - rate_limit_send_email_confirmation: { + send_email_confirmation: { min: 1, placeholder: 2, title: "Limit number of confirmation emails we send", diff --git a/app/lib/constants/settings/rate_limit.rb b/app/lib/constants/settings/rate_limit.rb new file mode 100644 index 000000000..241423d6d --- /dev/null +++ b/app/lib/constants/settings/rate_limit.rb @@ -0,0 +1,17 @@ +module Constants + module Settings + module RateLimit + DETAILS = { + spam_trigger_terms: { + description: "Individual (case insensitive) phrases that trigger spam alerts, comma separated.", + placeholder: "used cars near you, pokemon go hack" + }, + user_considered_new_days: { + description: "The number of days a user is considered new. The default is 3 days, but you "\ + "can disable this entirely by inputting 0.", + placeholder: ::SiteConfig.user_considered_new_days + } + }.freeze + end + end +end diff --git a/app/lib/constants/site_config.rb b/app/lib/constants/site_config.rb index 0a54d8855..3d80ea19f 100644 --- a/app/lib/constants/site_config.rb +++ b/app/lib/constants/site_config.rb @@ -172,10 +172,6 @@ module Constants description: "A place for an alternate logo, if you have one. Used throughout member onboarding and in some sign in forms.", placeholder: IMAGE_PLACEHOLDER }, - spam_trigger_terms: { - description: "Individual (case insensitive) phrases that trigger spam alerts, comma separated.", - placeholder: "used cars near you, pokemon go hack" - }, shop_url: { description: "Used as the shop url of the community", placeholder: "https://shop.url" @@ -229,10 +225,6 @@ module Constants description: "Used as the twitter hashtag of the community", placeholder: "#DEVCommunity" }, - user_considered_new_days: { - description: "The number of days a user is considered new. The default is 3 days, but you can disable this entirely by inputting 0.", - placeholder: ::SiteConfig.user_considered_new_days - }, video_encoder_key: { description: "Secret key used to allow AWS video encoding through the VideoStatesController", placeholder: "" diff --git a/app/models/settings/rate_limit.rb b/app/models/settings/rate_limit.rb new file mode 100644 index 000000000..2d8a1eff6 --- /dev/null +++ b/app/models/settings/rate_limit.rb @@ -0,0 +1,31 @@ +module Settings + class RateLimit < RailsSettings::Base + self.table_name = :settings_rate_limits + + # The configuration is cached, change this if you want to force update + # the cache, or call Settings::RateLimit.clear_cache + cache_prefix { "v1" } + + field :article_update, type: :integer, default: 30 + field :comment_antispam_creation, type: :integer, default: 1 + field :comment_creation, type: :integer, default: 9 + field :email_recipient, type: :integer, default: 5 + field :feedback_message_creation, type: :integer, default: 5 + field :follow_count_daily, type: :integer, default: 500 + field :image_upload, type: :integer, default: 9 + field :listing_creation, type: :integer, default: 1 + field :organization_creation, type: :integer, default: 1 + field :published_article_antispam_creation, type: :integer, default: 1 + field :published_article_creation, type: :integer, default: 9 + field :reaction_creation, type: :integer, default: 10 + field :send_email_confirmation, type: :integer, default: 2 + field :spam_trigger_terms, type: :array, default: [] + field :user_considered_new_days, type: :integer, default: 3 + field :user_subscription_creation, type: :integer, default: 3 + field :user_update, type: :integer, default: 15 + + def self.get_default(field) + get_field(field)[:default] + end + end +end diff --git a/app/models/site_config.rb b/app/models/site_config.rb index efeeb1aac..5dba14b11 100644 --- a/app/models/site_config.rb +++ b/app/models/site_config.rb @@ -142,6 +142,8 @@ class SiteConfig < RailsSettings::Base field :prefer_manual_suggested_users, type: :boolean, default: false # Rate limits and spam prevention + # NOTE: @citizen428 These will be removed once we migrated to the new settings + # model across the fleet. field :rate_limit_follow_count_daily, type: :integer, default: 500 field :rate_limit_comment_creation, type: :integer, default: 9 field :rate_limit_comment_antispam_creation, type: :integer, default: 1 diff --git a/app/services/rate_limit_checker.rb b/app/services/rate_limit_checker.rb index 187bb124a..77b39fba1 100644 --- a/app/services/rate_limit_checker.rb +++ b/app/services/rate_limit_checker.rb @@ -59,7 +59,7 @@ class RateLimitChecker def limit_by_email_recipient_address(address) # This is related to the recipient, not the "user" initiator, like in action. EmailMessage.where(to: address).where("sent_at > ?", 2.minutes.ago).size > - SiteConfig.rate_limit_email_recipient + Settings::RateLimit.email_recipient end private @@ -78,39 +78,39 @@ class RateLimitChecker end def action_rate_limit(action) - SiteConfig.public_send("rate_limit_#{action}") + Settings::RateLimit.public_send(action) end def check_comment_creation_limit user.comments.where("created_at > ?", 30.seconds.ago).size > - SiteConfig.rate_limit_comment_creation + Settings::RateLimit.comment_creation end def check_published_article_creation_limit # TODO: Vaidehi Joshi - We should make this time frame configurable. user.articles.published.where("created_at > ?", 30.seconds.ago).size > - SiteConfig.rate_limit_published_article_creation + Settings::RateLimit.published_article_creation end def check_published_article_antispam_creation_limit # TODO: Vaidehi Joshi - We should make this time frame configurable. user.articles.published.where("created_at > ?", 5.minutes.ago).size > - SiteConfig.rate_limit_published_article_antispam_creation + Settings::RateLimit.published_article_antispam_creation end def check_comment_antispam_creation_limit # TODO: We should make this time frame configurable. user.comments.where(created_at: 5.minutes.ago...).size > - SiteConfig.rate_limit_comment_antispam_creation + Settings::RateLimit.comment_antispam_creation end def check_follow_account_limit - user_today_follow_count > SiteConfig.rate_limit_follow_count_daily + user_today_follow_count > Settings::RateLimit.follow_count_daily end def user_today_follow_count following_users_count = user.following_users_count - return following_users_count if following_users_count < SiteConfig.rate_limit_follow_count_daily + return following_users_count if following_users_count < Settings::RateLimit.follow_count_daily now = Time.zone.now user.follows.where(created_at: (now.beginning_of_day..now)).size diff --git a/app/services/rate_limits/settings_upsert.rb b/app/services/rate_limits/settings_upsert.rb new file mode 100644 index 000000000..27f86c41d --- /dev/null +++ b/app/services/rate_limits/settings_upsert.rb @@ -0,0 +1,41 @@ +module RateLimits + class SettingsUpsert + attr_reader :errors + + def self.call(configs) + new(configs).call + end + + def initialize(configs) + @configs = configs + @errors = [] + end + + def call + upsert_configs + self + end + + def success? + @errors.none? + end + + private + + # NOTE: @citizen428 - This was adapted from Settings::Upsert. I'll see if + # a pattern for refactoring emerges in the future, but for now I'll leave + # this as-is. + def upsert_configs + @configs.each do |key, value| + if value.is_a?(Array) && value.any? + Settings::RateLimit.public_send("#{key}=", value.reject(&:blank?)) + elsif value.present? + Settings::RateLimit.public_send("#{key}=", value.strip) + end + rescue ActiveRecord::RecordInvalid => e + @errors << e.message + next + end + end + end +end diff --git a/app/views/admin/configs/show.html.erb b/app/views/admin/configs/show.html.erb index 091a18619..055670896 100644 --- a/app/views/admin/configs/show.html.erb +++ b/app/views/admin/configs/show.html.erb @@ -1071,7 +1071,7 @@ <% end %> - <%= form_for(SiteConfig.new, url: admin_config_path) do |f| %> + <%= form_for(Settings::RateLimit.new, url: admin_settings_rate_limits_path) do |f| %>
<%= render partial: "admin/shared/card_header", locals: { @@ -1083,35 +1083,35 @@
- <%= admin_config_label :user_considered_new_days %> - <%= admin_config_description Constants::SiteConfig::DETAILS[:user_considered_new_days][:description] %> + <%= admin_config_label :user_considered_new_days, model: Settings::RateLimit %> + <%= admin_config_description Constants::Settings::RateLimit::DETAILS[:user_considered_new_days][:description] %> <%= f.number_field :user_considered_new_days, class: "crayons-textfield", - value: SiteConfig.user_considered_new_days, - placeholder: Constants::SiteConfig::DETAILS[:user_considered_new_days][:placeholder] %> + value: Settings::RateLimit.user_considered_new_days, + placeholder: Constants::Settings::RateLimit::DETAILS[:user_considered_new_days][:placeholder] %>
<% configurable_rate_limits.each do |key, field_hash| %>
- <%= admin_config_label field_hash[:title] %> + <%= admin_config_label field_hash[:title], model: Settings::RateLimit %>

<%= field_hash[:description] %>

<%= f.number_field key, class: "crayons-textfield", - value: SiteConfig.public_send(key), + value: Settings::RateLimit.public_send(key), min: field_hash[:min], placeholder: field_hash[:placeholder] %>
<% end %>
- <%= admin_config_label :spam_trigger_terms %> - <%= admin_config_description Constants::SiteConfig::DETAILS[:spam_trigger_terms][:description] %> + <%= admin_config_label :spam_trigger_terms, model: Settings::RateLimit %> + <%= admin_config_description Constants::Settings::RateLimit::DETAILS[:spam_trigger_terms][:description] %> <%= f.text_area :spam_trigger_terms, class: "crayons-textfield", - value: SiteConfig.spam_trigger_terms.join(","), - placeholder: Constants::SiteConfig::DETAILS[:spam_trigger_terms][:placeholder] %> + value: Settings::RateLimit.spam_trigger_terms.join(","), + placeholder: Constants::Settings::RateLimit::DETAILS[:spam_trigger_terms][:placeholder] %>
<%= render "form_submission", f: f %>
diff --git a/app/views/dashboards/following_tags.html.erb b/app/views/dashboards/following_tags.html.erb index e370d1664..72f20e0d2 100644 --- a/app/views/dashboards/following_tags.html.erb +++ b/app/views/dashboards/following_tags.html.erb @@ -18,7 +18,7 @@ <% if @followed_tags.any? %> <%= javascript_packs_with_chunks_tag "dashboardTagsDisableUnchangedButtons", defer: true %> -
+
Adjust tag weight to modify your home feed. Higher values mean more appearances for that tag. Default 1.0
diff --git a/app/views/shared/authentication/_email_registration_form.html.erb b/app/views/shared/authentication/_email_registration_form.html.erb index 69f959e76..9f6a8b830 100644 --- a/app/views/shared/authentication/_email_registration_form.html.erb +++ b/app/views/shared/authentication/_email_registration_form.html.erb @@ -1,6 +1,6 @@
<% if flash[:notice] %> -