diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index d61623db2..bdd99f2ac 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -61,6 +61,7 @@ class UsersController < ApplicationController def create @user = authorize User.new( last_ip_addr: CurrentUser.ip_addr, + requires_verification: IpLookup.new(CurrentUser.ip_addr).is_proxy?, name: params[:user][:name], password: params[:user][:password], password_confirmation: params[:user][:password_confirmation] diff --git a/app/logical/email_validator.rb b/app/logical/email_validator.rb index ada3d638e..7af1bbda3 100644 --- a/app/logical/email_validator.rb +++ b/app/logical/email_validator.rb @@ -76,6 +76,11 @@ module EmailValidator "#{name}@#{domain}" end + def nondisposable?(address) + domain = Mail::Address.new(address).domain + domain.in?(Danbooru.config.email_domain_verification_list) + end + def undeliverable?(to_address, from_address: Danbooru.config.contact_email, timeout: 3) mail_server = mx_domain(to_address, timeout: timeout) mail_server.nil? || rcpt_to_failed?(to_address, from_address, mail_server, timeout: timeout) diff --git a/app/logical/ip_lookup.rb b/app/logical/ip_lookup.rb index bc78e83b4..dc9f5bfce 100644 --- a/app/logical/ip_lookup.rb +++ b/app/logical/ip_lookup.rb @@ -5,6 +5,10 @@ class IpLookup attr_reader :ip_addr, :api_key, :cache_duration + def self.enabled? + Danbooru.config.ip_registry_api_key.present? + end + def initialize(ip_addr, api_key: Danbooru.config.ip_registry_api_key, cache_duration: 1.day) @ip_addr = ip_addr @api_key = api_key diff --git a/app/models/email_address.rb b/app/models/email_address.rb index d96470eff..c0f7779be 100644 --- a/app/models/email_address.rb +++ b/app/models/email_address.rb @@ -2,24 +2,33 @@ class EmailAddress < ApplicationRecord # https://www.regular-expressions.info/email.html EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/ - belongs_to :user + belongs_to :user, inverse_of: :email_address validates :address, presence: true, confirmation: true, format: { with: EMAIL_REGEX } validates :normalized_address, uniqueness: true validates :user_id, uniqueness: true validate :validate_deliverable, on: :deliverable + after_save :update_user def address=(value) self.normalized_address = EmailValidator.normalize(value) || address super end + def nondisposable? + EmailValidator.nondisposable?(address) + end + def validate_deliverable if EmailValidator.undeliverable?(address) errors[:address] << "is invalid or does not exist" end end + def update_user + user.update!(is_verified: is_verified? && nondisposable?) + end + concerning :VerificationMethods do def verifier @verifier ||= Danbooru::MessageVerifier.new(:email_verification_key) diff --git a/app/models/user.rb b/app/models/user.rb index 106392c7b..c3f2d2500 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -63,6 +63,8 @@ class User < ApplicationRecord opt_out_tracking no_flagging no_feedback + requires_verification + is_verified ) has_bit_flags BOOLEAN_ATTRIBUTES, :field => "bit_prefs" diff --git a/app/policies/application_policy.rb b/app/policies/application_policy.rb index a7d52ba37..b834eec02 100644 --- a/app/policies/application_policy.rb +++ b/app/policies/application_policy.rb @@ -39,7 +39,11 @@ class ApplicationPolicy end def unbanned? - user.is_member? && !user.is_banned? + user.is_member? && !user.is_banned? && verified? + end + + def verified? + user.is_verified? || user.is_gold? || !user.requires_verification? end def policy(object) diff --git a/config/danbooru_default_config.rb b/config/danbooru_default_config.rb index a9283db02..09edcebe2 100644 --- a/config/danbooru_default_config.rb +++ b/config/danbooru_default_config.rb @@ -519,6 +519,13 @@ module Danbooru nil end + # A list of email domains that are used for account verification purposes. + # If a user signs up from a proxy they will need to verify their account + # using an email address from one of the domains on this list. + def email_domain_verification_list + # ["gmail.com", "outlook.com", "yahoo.com"] + end + # API key for Google Maps. Used for embedding maps on IP address lookup pages. # Generate at https://console.developers.google.com/apis/credentials def google_maps_api_key diff --git a/test/functional/emails_controller_test.rb b/test/functional/emails_controller_test.rb index 058025ed8..378b071fb 100644 --- a/test/functional/emails_controller_test.rb +++ b/test/functional/emails_controller_test.rb @@ -85,6 +85,30 @@ class EmailsControllerTest < ActionDispatch::IntegrationTest assert_equal(false, @user.reload.email_address.is_verified) end end + + context "with a nondisposable email address" do + should "mark the user as verified" do + Danbooru.config.stubs(:email_domain_verification_list).returns(["gmail.com"]) + @user.email_address.update!(address: "test@gmail.com") + get email_verification_url(@user) + + assert_redirected_to @user + assert_equal(true, @user.reload.email_address.is_verified) + assert_equal(true, @user.is_verified) + end + end + + context "with a disposable email address" do + should "not mark the user as verified" do + Danbooru.config.stubs(:email_domain_verification_list).returns([]) + @user.email_address.update!(address: "test@mailinator.com") + get email_verification_url(@user) + + assert_redirected_to @user + assert_equal(true, @user.reload.email_address.is_verified) + assert_equal(false, @user.is_verified) + end + end end end end diff --git a/test/functional/posts_controller_test.rb b/test/functional/posts_controller_test.rb index 2a3a6b499..5ff04d3e6 100644 --- a/test/functional/posts_controller_test.rb +++ b/test/functional/posts_controller_test.rb @@ -311,6 +311,13 @@ class PostsControllerTest < ActionDispatch::IntegrationTest assert_response 403 assert_not_equal("blah", @post.reload.tag_string) end + + should "not allow unverified users to update posts" do + @user.update!(requires_verification: true, is_verified: false) + put_auth post_path(@post), @user, params: { post: { tag_string: "blah" }} + assert_response 403 + assert_not_equal("blah", @post.reload.tag_string) + end end context "revert action" do diff --git a/test/functional/users_controller_test.rb b/test/functional/users_controller_test.rb index c002f7b6c..67a782d7e 100644 --- a/test/functional/users_controller_test.rb +++ b/test/functional/users_controller_test.rb @@ -151,6 +151,24 @@ class UsersControllerTest < ActionDispatch::IntegrationTest end end + should "mark users signing up from proxies as requiring verification" do + skip unless IpLookup.enabled? + self.remote_addr = "1.1.1.1" + post users_path, params: { user: { name: "xxx", password: "xxxxx1", password_confirmation: "xxxxx1" }} + + assert_redirected_to User.last + assert_equal(true, User.last.requires_verification) + end + + should "not mark users signing up from non-proxies as requiring verification" do + skip unless IpLookup.enabled? + self.remote_addr = "187.37.226.17" + post users_path, params: { user: { name: "xxx", password: "xxxxx1", password_confirmation: "xxxxx1" }} + + assert_redirected_to User.last + assert_equal(false, User.last.requires_verification) + end + context "with sockpuppet validation enabled" do setup do Danbooru.config.unstub(:enable_sock_puppet_validation?)