Skip to content

Instantly share code, notes, and snippets.

@muffinista
Created April 4, 2019 02:15
Show Gist options
  • Save muffinista/73fa9d4ca4fd2a9493ea4e3d68e3d8d0 to your computer and use it in GitHub Desktop.
Save muffinista/73fa9d4ca4fd2a9493ea4e3d68e3d8d0 to your computer and use it in GitHub Desktop.
anti-spam measures for mastodon
diff --git a/Gemfile b/Gemfile
index 8266488f8..ce47aa6e2 100644
--- a/Gemfile
+++ b/Gemfile
@@ -147,3 +147,6 @@ group :production do
end
gem 'concurrent-ruby', require: false
+
+gem 'ipcat'
+gem 'sentry-raven'
diff --git a/Gemfile.lock b/Gemfile.lock
index 7b8ffec9a..82d701486 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -283,6 +283,7 @@ GEM
terminal-table (>= 1.5.1)
idn-ruby (0.1.0)
ipaddress (0.8.3)
+ ipcat (2.0.21)
iso-639 (0.2.8)
jaro_winkler (1.5.2)
jmespath (1.4.0)
@@ -551,6 +552,8 @@ GEM
scss_lint (0.57.1)
rake (>= 0.9, < 13)
sass (~> 3.5, >= 3.5.5)
+ sentry-raven (2.9.0)
+ faraday (>= 0.7.6, < 1.0)
sidekiq (5.2.5)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
@@ -698,6 +701,7 @@ DEPENDENCIES
httplog (~> 1.2)
i18n-tasks (~> 0.9)
idn-ruby
+ ipcat
iso-639
json-ld (~> 3.0)
json-ld-preloaded (~> 3.0)
@@ -749,6 +753,7 @@ DEPENDENCIES
rubocop (~> 0.63)
sanitize (~> 5.0)
scss_lint (~> 0.57)
+ sentry-raven
sidekiq (~> 5.2)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0)
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index ad7b1859f..3b4029132 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -10,6 +10,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :set_instance_presenter, only: [:new, :create, :update]
before_action :set_body_classes, only: [:new, :create, :edit, :update]
+ include DatacenterConcern
+
+
def destroy
not_found
end
diff --git a/app/controllers/concerns/datacenter_concern.rb b/app/controllers/concerns/datacenter_concern.rb
new file mode 100644
index 000000000..351f3147a
--- /dev/null
+++ b/app/controllers/concerns/datacenter_concern.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module DatacenterConcern
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :prevent_datacenters, only: [:new, :create]
+ end
+
+ def prevent_datacenters
+ begin
+ require 'ipcat'
+
+ if IPCat.datacenter?(request.remote_ip) != nil
+ redirect_to '/datacenters.html'
+ elsif File.exist?(Rails.root.join('blocks.txt'))
+ require 'ipaddr'
+
+ has_block = File.read(Rails.root.join('blocks.txt')).split(/\n/).find { |ip|
+ net = IPAddr.new(ip)
+ net.include?(request.remote_ip)
+ }
+
+ if has_block
+ redirect_to '/datacenters.html'
+ end
+ end
+
+ rescue
+
+ end
+ end
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index 035423b40..43b45e542 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -66,6 +66,7 @@ class Status < ApplicationRecord
validates :text, presence: true, unless: -> { with_media? || reblog? }
validates_with StatusLengthValidator
validates_with DisallowedHashtagsValidator
+ validates_with AllowedFollowingStatusValidator
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
default_scope { recent }
diff --git a/app/validators/allowed_following_status_validator.rb b/app/validators/allowed_following_status_validator.rb
new file mode 100644
index 000000000..b963590cd
--- /dev/null
+++ b/app/validators/allowed_following_status_validator.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+class AllowedFollowingStatusValidator < ActiveModel::Validator
+ def validate(status)
+ @status = status
+ return unless !accounts.empty?
+
+ # allow messages from local admin/moderators
+ return if status.account.user_moderator? || status.account.user_admin?
+
+ # allow messages from accounts that seem to have legit traffic
+ return if status.account.account_stat.statuses_count > 25
+ return if status.account.account_stat.followers_count > 5
+
+ not_following_accounts = []
+
+ begin
+ not_following_accounts = accounts.select { |a|
+ a && !a.following?(status.account)
+ }.reject { |a|
+ # allow messages to local admin/moderators
+ (a.respond_to?(:user_admin?) && a.user_admin?) ||
+ (a.respond_to?(:user_moderator?) && a.user_moderator?)
+ }
+ rescue StandardError => ex
+ Raven.capture_exception(ex) if defined?(Raven)
+ end
+
+
+ if status.thread.present?
+ mentioned_accounts = []
+ begin
+ #mentioned_accounts = status.thread.include(:account).map(&:account)
+ mentioned_accounts = status.mentions.includes(:account).map(&:account)
+ rescue StandardError => ex
+ Raven.capture_exception(ex) if defined?(Raven)
+ end
+
+ # if the thread includes this account
+ if mentioned_accounts.include?(status.account)
+ # allow messages to accounts in the thread, if this account
+ # was mentioned in the thread
+ not_following_accounts = not_following_accounts.reject { |a|
+ mentioned_accounts.include?(a)
+ }
+ end
+ end
+
+
+ status.errors.add(:text, I18n.t('statuses.cannot_send')) unless not_following_accounts.empty?
+ end
+
+ private
+
+ def accounts
+ @status.text.scan(Account::MENTION_RE).collect do |match|
+ mentioned_account = nil
+ begin
+ username, domain = Regexp.last_match(1).split('@')
+ mentioned_account = Account.find_remote(username, domain)
+ mentioned_account = Account.find_local(username) if mentioned_account.nil?
+ rescue StandardError => ex
+ Raven.capture_exception(ex) if defined?(Raven)
+ end
+
+ mentioned_account
+ end
+ end
+end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment