Allow Forems to specify which domains are allowed for registration (#11442)

* Allow authors to restrict which emails can sign up

* Add form and tests

* Check for email presence in allowed email flow

* Fix test domains

* Fix codeclimate issue
This commit is contained in:
Ben Halpern 2020-11-17 08:43:46 -05:00 committed by GitHub
parent cc63725875
commit f3a69bbd84
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 132 additions and 1 deletions

View file

@ -123,6 +123,8 @@ module Admin
video_encoder_key
tag_feed_minimum_score
home_feed_minimum_score
allowed_registration_email_domains
display_email_domain_allow_list_publicly
].freeze
IMAGE_FIELDS =
@ -211,6 +213,7 @@ module Admin
errors = []
errors << "Brand color must be darker for accessibility." if brand_contrast_too_low
errors << "Brand color must be be a 6 character hex (starting with #)." if brand_color_not_hex
errors << "Allowed emails must be list of domains." if allowed_domains_include_improper_format
redirect_to admin_config_path, alert: "😭 #{errors.to_sentence}" if errors.any?
end
@ -260,6 +263,16 @@ module Admin
hex.present? && !hex.match?(/\A#(\h{6}|\h{3})\z/)
end
def allowed_domains_include_improper_format
domains = params.dig(:site_config, :allowed_registration_email_domains)
return unless domains
domains_array = domains.delete(" ").split(",")
valid_domains = domains_array
.select { |d| d.match?(/^[a-zA-Z0-9][a-zA-Z0-9-]{1,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+$/) }
valid_domains.size != domains_array.size
end
def valid_image_url(url)
url.match?(VALID_URL)
end

View file

@ -23,6 +23,7 @@ class RegistrationsController < Devise::RegistrationsController
resource.registered = true
resource.registered_at = Time.current
resource.editor_version = "v2"
check_allowed_email(resource) if resource.email.present?
resource.save if resource.email.present?
yield resource if block_given?
if resource.persisted?
@ -57,4 +58,13 @@ class RegistrationsController < Devise::RegistrationsController
recaptcha_params = { secret_key: SiteConfig.recaptcha_secret_key }
params["g-recaptcha-response"] && verify_recaptcha(recaptcha_params)
end
def check_allowed_email(resource)
domain = resource.email.split("@").last
allow_list = SiteConfig.allowed_registration_email_domains
return if allow_list.empty? || allow_list.include?(domain)
resource.email = nil
resource.errors.add(:email, "is not included in allowed domains.")
end
end

View file

@ -12,7 +12,7 @@ const emailAuthModalTitle = 'Disable Email address registration';
// TODO: Remove the sentence "You must update site config to save this action!"
// once we build more robust flow for Admin/Config
const emailAuthModalBody =
'<p>If you disable Email address as a registration option, people cannot create an account with their email address.</p><p>However, people who have already created an account using their email address can continue to login.</p><p><strong>Please update site config to save this action.</strong></p>';
'<p>If you disable Email address as a registration option, people cannot create an account with their email address.</p><p>However, people who have already created an account using their email address can continue to login.</p><p><strong>You must confirm and update site config to save below this action.</strong></p>';
export default class ConfigController extends Controller {
static targets = [

View file

@ -9,6 +9,10 @@ module Constants
they're creating a new account in your community",
placeholder: ""
},
allowed_registration_email_domains: {
description: "Restrict registration to only certain emails? (comma-separated list)",
placeholder: "dev.to, forem.com, codenewbie.org"
},
authentication_providers: {
description: "How can users sign in?",
placeholder: ""
@ -82,6 +86,9 @@ module Constants
default_font: {
description: "Determines the default Base Reading Font (registered users can change this in their UX settings)"
},
display_email_domain_allow_list_publicly: {
description: "Do you want to display the list of allowed domains, or keep it private?"
},
display_jobs_banner: {
description: "Display a jobs banner that points users to the jobs page when they type 'job'" \
"or 'jobs' in the search box",

View file

@ -27,6 +27,8 @@ class SiteConfig < RailsSettings::Base
# Authentication
field :allow_email_password_registration, type: :boolean, default: false
field :allow_email_password_login, type: :boolean, default: true
field :allowed_registration_email_domains, type: :array, default: %w[]
field :display_email_domain_allow_list_publicly, type: :boolean, default: false
field :require_captcha_for_email_password_registration, type: :boolean, default: false
field :authentication_providers, type: :array, default: proc { Authentication::Providers.available }
field :invite_only_mode, type: :boolean, default: false

View file

@ -196,6 +196,24 @@
id: "email-registration-checkbox",
class: "crayons-checkbox" %>
</div>
<div class="crayons-field">
<%= admin_config_label :allowed_registration_email_domains, "Allowed email domains" %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:allowed_registration_email_domains][:description] %>
<%= f.text_field :allowed_registration_email_domains,
class: "crayons-textfield",
value: SiteConfig.allowed_registration_email_domains.join(","),
placeholder: Constants::SiteConfig::DETAILS[:allowed_registration_email_domains][:placeholder] %>
</div>
<div class="crayons-field--checkbox">
<%= f.check_box :display_email_domain_allow_list_publicly,
checked: SiteConfig.display_email_domain_allow_list_publicly,
id: "email-display-emails-publicly-checkbox",
class: "crayons-checkbox mt-2" %>
<div>
<%= admin_config_label :display_email_domain_allow_list_publicly %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:display_email_domain_allow_list_publicly][:description] %>
</div>
</div>
<div class="crayons-field--checkbox">
<%= f.check_box :require_captcha_for_email_password_registration,
checked: SiteConfig.require_captcha_for_email_password_registration,

View file

@ -29,6 +29,23 @@
<% else %>
<p class="pb-4 fw-bold">Create your account</p>
<% end %>
<% if SiteConfig.display_email_domain_allow_list_publicly &&
SiteConfig.allowed_registration_email_domains.any? %>
<div class="crayons-notice crayons-notice--info">
<% if SiteConfig.allowed_registration_email_domains.one? %>
Registration restricted to <strong>@<%= SiteConfig.allowed_registration_email_domains.first %></strong> emails.
<% else %>
Registration restricted to the following emails
<ul>
<% SiteConfig.allowed_registration_email_domains.each do |domain| %>
<li>
<strong>@<%= domain %></strong></strong>
</li>
<% end %>
</ul>
<% end %>
</div>
<% end %>
<div class="crayons-field mt-3">
<%= f.label :profile_image, class: "crayons-field__label" %>
<%= f.file_field :profile_image, accept: "image/*", class: "crayons-card crayons-card--secondary p-3 flex items-center flex-1 w-100", required: true %>

View file

@ -88,6 +88,26 @@ RSpec.describe "/admin/config", type: :request do
expect(SiteConfig.authentication_providers).to eq([provider])
end
it "enables proper domains to allow list" do
proper_list = "dev.to, forem.com, forem.dev"
post "/admin/config", params: { site_config: { allowed_registration_email_domains: proper_list },
confirmation: confirmation_message }
expect(SiteConfig.allowed_registration_email_domains).to eq(%w[dev.to forem.com forem.dev])
end
it "does not allow improper domain list" do
impproper_list = "dev.to, foremcom, forem.dev"
post "/admin/config", params: { site_config: { allowed_registration_email_domains: impproper_list },
confirmation: confirmation_message }
expect(SiteConfig.allowed_registration_email_domains).not_to eq(%w[dev.to foremcom forem.dev])
end
it "enables display_email_domain_allow_list_publicly" do
post "/admin/config", params: { site_config: { display_email_domain_allow_list_publicly: true },
confirmation: confirmation_message }
expect(SiteConfig.display_email_domain_allow_list_publicly).to be(true)
end
it "enables email authentication" do
post "/admin/config", params: { site_config: { allow_email_password_registration: true },
confirmation: confirmation_message }

View file

@ -222,6 +222,50 @@ RSpec.describe "Registrations", type: :request do
end
end
context "when email registration allowed and email allow list empty" do
before do
allow(SiteConfig).to receive(:allow_email_password_registration).and_return(true)
allow(SiteConfig).to receive(:allowed_registration_email_domains).and_return([])
end
it "creates user when email in allow list" do
post "/users", params:
{ user: { name: "royal #{rand(10)}",
username: "magoo_#{rand(10)}",
email: "queenelizabeth@dev.to",
password: "PaSSw0rd_yo000",
password_confirmation: "PaSSw0rd_yo000" } }
expect(User.all.size).to be 1
end
end
context "when email registration allowed and email allow list present" do
before do
allow(SiteConfig).to receive(:allow_email_password_registration).and_return(true)
allow(SiteConfig).to receive(:allowed_registration_email_domains).and_return(["dev.to", "forem.com"])
end
it "does not create user when email not in allow list" do
post "/users", params:
{ user: { name: "ronald #{rand(10)}",
username: "mcdonald_#{rand(10)}",
email: "ronald@mcdonald.com",
password: "PaSSw0rd_yo000",
password_confirmation: "PaSSw0rd_yo000" } }
expect(User.all.size).to be 0
end
it "creates user when email in allow list" do
post "/users", params:
{ user: { name: "royal #{rand(10)}",
username: "magoo_#{rand(10)}",
email: "queenelizabeth@dev.to",
password: "PaSSw0rd_yo000",
password_confirmation: "PaSSw0rd_yo000" } }
expect(User.all.size).to be 1
end
end
context "when site configured to accept email registration AND require captcha" do
before do
allow(SiteConfig).to receive(:allow_email_password_registration).and_return(true)