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
|
def create
|
||||||
@user = authorize User.new(
|
@user = authorize User.new(
|
||||||
last_ip_addr: CurrentUser.ip_addr,
|
last_ip_addr: CurrentUser.ip_addr,
|
||||||
|
requires_verification: IpLookup.new(CurrentUser.ip_addr).is_proxy?,
|
||||||
name: params[:user][:name],
|
name: params[:user][:name],
|
||||||
password: params[:user][:password],
|
password: params[:user][:password],
|
||||||
password_confirmation: params[:user][:password_confirmation]
|
password_confirmation: params[:user][:password_confirmation]
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ module EmailValidator
|
|||||||
"#{name}@#{domain}"
|
"#{name}@#{domain}"
|
||||||
end
|
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)
|
def undeliverable?(to_address, from_address: Danbooru.config.contact_email, timeout: 3)
|
||||||
mail_server = mx_domain(to_address, timeout: timeout)
|
mail_server = mx_domain(to_address, timeout: timeout)
|
||||||
mail_server.nil? || rcpt_to_failed?(to_address, from_address, mail_server, 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
|
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)
|
def initialize(ip_addr, api_key: Danbooru.config.ip_registry_api_key, cache_duration: 1.day)
|
||||||
@ip_addr = ip_addr
|
@ip_addr = ip_addr
|
||||||
@api_key = api_key
|
@api_key = api_key
|
||||||
|
|||||||
@@ -2,24 +2,33 @@ class EmailAddress < ApplicationRecord
|
|||||||
# https://www.regular-expressions.info/email.html
|
# https://www.regular-expressions.info/email.html
|
||||||
EMAIL_REGEX = /\A[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}\z/
|
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 :address, presence: true, confirmation: true, format: { with: EMAIL_REGEX }
|
||||||
validates :normalized_address, uniqueness: true
|
validates :normalized_address, uniqueness: true
|
||||||
validates :user_id, uniqueness: true
|
validates :user_id, uniqueness: true
|
||||||
validate :validate_deliverable, on: :deliverable
|
validate :validate_deliverable, on: :deliverable
|
||||||
|
after_save :update_user
|
||||||
|
|
||||||
def address=(value)
|
def address=(value)
|
||||||
self.normalized_address = EmailValidator.normalize(value) || address
|
self.normalized_address = EmailValidator.normalize(value) || address
|
||||||
super
|
super
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def nondisposable?
|
||||||
|
EmailValidator.nondisposable?(address)
|
||||||
|
end
|
||||||
|
|
||||||
def validate_deliverable
|
def validate_deliverable
|
||||||
if EmailValidator.undeliverable?(address)
|
if EmailValidator.undeliverable?(address)
|
||||||
errors[:address] << "is invalid or does not exist"
|
errors[:address] << "is invalid or does not exist"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update_user
|
||||||
|
user.update!(is_verified: is_verified? && nondisposable?)
|
||||||
|
end
|
||||||
|
|
||||||
concerning :VerificationMethods do
|
concerning :VerificationMethods do
|
||||||
def verifier
|
def verifier
|
||||||
@verifier ||= Danbooru::MessageVerifier.new(:email_verification_key)
|
@verifier ||= Danbooru::MessageVerifier.new(:email_verification_key)
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ class User < ApplicationRecord
|
|||||||
opt_out_tracking
|
opt_out_tracking
|
||||||
no_flagging
|
no_flagging
|
||||||
no_feedback
|
no_feedback
|
||||||
|
requires_verification
|
||||||
|
is_verified
|
||||||
)
|
)
|
||||||
|
|
||||||
has_bit_flags BOOLEAN_ATTRIBUTES, :field => "bit_prefs"
|
has_bit_flags BOOLEAN_ATTRIBUTES, :field => "bit_prefs"
|
||||||
|
|||||||
@@ -39,7 +39,11 @@ class ApplicationPolicy
|
|||||||
end
|
end
|
||||||
|
|
||||||
def unbanned?
|
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
|
end
|
||||||
|
|
||||||
def policy(object)
|
def policy(object)
|
||||||
|
|||||||
@@ -519,6 +519,13 @@ module Danbooru
|
|||||||
nil
|
nil
|
||||||
end
|
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.
|
# API key for Google Maps. Used for embedding maps on IP address lookup pages.
|
||||||
# Generate at https://console.developers.google.com/apis/credentials
|
# Generate at https://console.developers.google.com/apis/credentials
|
||||||
def google_maps_api_key
|
def google_maps_api_key
|
||||||
|
|||||||
@@ -85,6 +85,30 @@ class EmailsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_equal(false, @user.reload.email_address.is_verified)
|
assert_equal(false, @user.reload.email_address.is_verified)
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -311,6 +311,13 @@ class PostsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_response 403
|
assert_response 403
|
||||||
assert_not_equal("blah", @post.reload.tag_string)
|
assert_not_equal("blah", @post.reload.tag_string)
|
||||||
end
|
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
|
end
|
||||||
|
|
||||||
context "revert action" do
|
context "revert action" do
|
||||||
|
|||||||
@@ -151,6 +151,24 @@ class UsersControllerTest < ActionDispatch::IntegrationTest
|
|||||||
end
|
end
|
||||||
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
|
context "with sockpuppet validation enabled" do
|
||||||
setup do
|
setup do
|
||||||
Danbooru.config.unstub(:enable_sock_puppet_validation?)
|
Danbooru.config.unstub(:enable_sock_puppet_validation?)
|
||||||
|
|||||||
Reference in New Issue
Block a user