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:
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?)
|
||||
|
||||
Reference in New Issue
Block a user