users: require email verification for signups from proxies.

Require users who signup using proxies to verify their email addresses
before they can perform any edits. For verification purposes, the email
must be a nondisposable address from a whitelist of trusted email
providers.
This commit is contained in:
evazion
2020-03-24 02:18:37 -05:00
parent 5faa323729
commit b7bd6c8fdd
10 changed files with 83 additions and 2 deletions

View File

@@ -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]

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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"

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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?)