[Team Email Login] Admin enable/disable recaptcha during email signup (#10846)
* Initial Implementation * Complete hookup of recaptcha guard * Write tests for recaptcha during email signup * Addressing PR changes 1 * Create helper method and write specs * fix bug
This commit is contained in:
parent
f87ff8e2ae
commit
8e57cf17ad
10 changed files with 181 additions and 49 deletions
|
|
@ -116,6 +116,7 @@ module Admin
|
|||
invite_only_mode
|
||||
allow_email_password_registration
|
||||
allow_email_password_login
|
||||
require_captcha_for_email_password_registration
|
||||
primary_brand_color_hex
|
||||
spam_trigger_terms
|
||||
recaptcha_site_key
|
||||
|
|
|
|||
|
|
@ -19,16 +19,21 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
not_authorized if SiteConfig.waiting_on_first_user && ENV["FOREM_OWNER_SECRET"].present? &&
|
||||
ENV["FOREM_OWNER_SECRET"] != params[:user][:forem_owner_secret]
|
||||
|
||||
build_resource(sign_up_params)
|
||||
resource.saw_onboarding = false
|
||||
resource.editor_version = "v2"
|
||||
resource.save if resource.email.present?
|
||||
yield resource if block_given?
|
||||
if resource.persisted?
|
||||
update_first_user_permissions(resource)
|
||||
redirect_to "/confirm-email?email=#{resource.email}"
|
||||
if recaptcha_disabled? || recaptcha_verified?
|
||||
build_resource(sign_up_params)
|
||||
resource.saw_onboarding = false
|
||||
resource.editor_version = "v2"
|
||||
resource.save if resource.email.present?
|
||||
yield resource if block_given?
|
||||
if resource.persisted?
|
||||
update_first_user_permissions(resource)
|
||||
redirect_to "/confirm-email?email=#{resource.email}"
|
||||
else
|
||||
render action: "by_email"
|
||||
end
|
||||
else
|
||||
render action: "by_email"
|
||||
redirect_to new_user_registration_path(state: "email_signup")
|
||||
flash[:notice] = "You must complete the recaptcha ✅"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -41,4 +46,14 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
resource.add_role(:single_resource_admin, Config)
|
||||
SiteConfig.waiting_on_first_user = false
|
||||
end
|
||||
|
||||
def recaptcha_disabled?
|
||||
(SiteConfig.recaptcha_site_key.blank? && SiteConfig.recaptcha_secret_key.blank?) ||
|
||||
!SiteConfig.require_captcha_for_email_password_registration
|
||||
end
|
||||
|
||||
def recaptcha_verified?
|
||||
recaptcha_params = { secret_key: SiteConfig.recaptcha_secret_key }
|
||||
params["g-recaptcha-response"] && verify_recaptcha(recaptcha_params)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,4 +18,10 @@ module AuthenticationHelper
|
|||
def authentication_enabled_providers_for_user(user = current_user)
|
||||
Authentication::Providers.enabled_for_user(user)
|
||||
end
|
||||
|
||||
def recaptcha_configured_and_enabled?
|
||||
SiteConfig.recaptcha_secret_key.present? &&
|
||||
SiteConfig.recaptcha_site_key.present? &&
|
||||
SiteConfig.require_captcha_for_email_password_registration
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
import { Controller } from 'stimulus';
|
||||
|
||||
const recaptchaFields = document.querySelector('#recaptchaContainer');
|
||||
|
||||
export default class ConfigController extends Controller {
|
||||
static targets = ['inviteOnlyMode', 'authenticationProviders'];
|
||||
static targets = ['inviteOnlyMode', 'authenticationProviders', 'requireCaptchaForEmailPasswordRegistration'];
|
||||
|
||||
disableAuthenticationOptions() {
|
||||
if (this.inviteOnlyModeTarget.checked) {
|
||||
|
|
@ -16,4 +18,12 @@ export default class ConfigController extends Controller {
|
|||
).disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
toggleGoogleRecaptchaFields() {
|
||||
if (this.requireCaptchaForEmailPasswordRegistrationTarget.checked) {
|
||||
recaptchaFields.classList.remove('collapse');
|
||||
} else {
|
||||
recaptchaFields.classList.add('collapse');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,15 @@ module Constants
|
|||
module SiteConfig
|
||||
DETAILS = {
|
||||
allow_email_password_registration: {
|
||||
description: "Can users sign up with only email and password?",
|
||||
description: "People can sign up using their email and password",
|
||||
placeholder: ""
|
||||
},
|
||||
allow_email_password_login: {
|
||||
description: "Can users login using email and password?",
|
||||
description: "People can login using their email and password",
|
||||
placeholder: ""
|
||||
},
|
||||
require_captcha_for_email_password_registration: {
|
||||
description: "People will be required to fill out a captcha when they're creating a new account in your community",
|
||||
placeholder: ""
|
||||
},
|
||||
authentication_providers: {
|
||||
|
|
@ -232,12 +236,12 @@ module Constants
|
|||
placeholder: 2
|
||||
},
|
||||
recaptcha_site_key: {
|
||||
description: "Site key for Google reCAPTCHA, used for reporting abuse.",
|
||||
placeholder: "..."
|
||||
description: "Add the site key for Google reCAPTCHA, which is used for reporting abuse",
|
||||
placeholder: "What is the Google reCAPTCHA site key?"
|
||||
},
|
||||
recaptcha_secret_key: {
|
||||
description: "Secret key for Google reCAPTCHA, used for reporting abuse.",
|
||||
placeholder: "..."
|
||||
description: "Add the secret key for Google reCAPTCHA, which is used for reporting abuse",
|
||||
placeholder: "What is the Google reCAPTCHA secret key?"
|
||||
},
|
||||
right_navbar_svg_icon: {
|
||||
description: "The SVG icon used to expand the right navbar navigation menu. Should be a max of 24x24px.",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class SiteConfig < RailsSettings::Base
|
|||
# Authentication
|
||||
field :allow_email_password_registration, type: :boolean, default: false
|
||||
field :allow_email_password_login, type: :boolean, default: true
|
||||
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
|
||||
field :twitter_key, type: :string, default: ApplicationConfig["TWITTER_KEY"]
|
||||
|
|
|
|||
|
|
@ -176,6 +176,15 @@
|
|||
</div>
|
||||
<div class="config-authentication__item--body">
|
||||
<fieldset class="config-authentication-form">
|
||||
<div class="crayons-field--checkbox">
|
||||
<%= f.check_box :allow_email_password_registration, checked: SiteConfig.allow_email_password_registration, class: "crayons-checkbox" %>
|
||||
<div>
|
||||
<%= admin_config_label :allow_email_password_registration %>
|
||||
<p class="crayons-field__description">
|
||||
<%= Constants::SiteConfig::DETAILS[:allow_email_password_registration][:description] %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="crayons-field--checkbox">
|
||||
<%= f.check_box :allow_email_password_login, checked: SiteConfig.allow_email_password_login, class: "crayons-checkbox" %>
|
||||
<div>
|
||||
|
|
@ -186,14 +195,39 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="crayons-field--checkbox">
|
||||
<%= f.check_box :allow_email_password_registration, checked: SiteConfig.allow_email_password_registration, class: "crayons-checkbox" %>
|
||||
<%= f.check_box :require_captcha_for_email_password_registration,
|
||||
checked: SiteConfig.require_captcha_for_email_password_registration,
|
||||
data: { action: "config#toggleGoogleRecaptchaFields", target: "config.requireCaptchaForEmailPasswordRegistration" },
|
||||
class: "crayons-checkbox" %>
|
||||
<div>
|
||||
<%= admin_config_label :allow_email_password_registration %>
|
||||
<%= admin_config_label :require_captcha_for_email_password_registration, "Enable Google reCAPTCHA for email password registration" %>
|
||||
<p class="crayons-field__description">
|
||||
<%= Constants::SiteConfig::DETAILS[:allow_email_password_registration][:description] %>
|
||||
<%= Constants::SiteConfig::DETAILS[:require_captcha_for_email_password_registration][:description] %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div id="recaptchaContainer" class="<%= SiteConfig.require_captcha_for_email_password_registration ? "" : "collapse" %> ml-7" aria-labelledby="recaptchaContainer">
|
||||
<div class="form-group">
|
||||
<%= admin_config_label :recaptcha_site_key, "Google reCAPTCHA site key" %>
|
||||
<p class="crayons-field__description mb-1">
|
||||
<%= Constants::SiteConfig::DETAILS[:recaptcha_site_key][:description] %>
|
||||
</p>
|
||||
<%= f.text_field :recaptcha_site_key,
|
||||
class: "form-control",
|
||||
value: SiteConfig.recaptcha_site_key,
|
||||
placeholder: Constants::SiteConfig::DETAILS[:recaptcha_site_key][:placeholder] %>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= admin_config_label :recaptcha_secret_key, "Google reCAPTCHA secret key" %>
|
||||
<p class="crayons-field__description mb-1">
|
||||
<%= Constants::SiteConfig::DETAILS[:recaptcha_secret_key][:description] %>
|
||||
</p>
|
||||
<%= f.text_field :recaptcha_secret_key,
|
||||
class: "form-control",
|
||||
value: SiteConfig.recaptcha_secret_key,
|
||||
placeholder: Constants::SiteConfig::DETAILS[:recaptcha_secret_key][:placeholder] %>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -643,36 +677,6 @@
|
|||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_for(SiteConfig.new, url: admin_config_path) do |f| %>
|
||||
<div class="card mt-3">
|
||||
<%= render partial: "card_header",
|
||||
locals: {
|
||||
header: "Google reCAPTCHA Keys",
|
||||
state: "collapse",
|
||||
target: "recaptchaContainer",
|
||||
expanded: "false"
|
||||
} %>
|
||||
<div id="recaptchaContainer" class="card-body collapse hide" aria-labelledby="recaptchaContainer">
|
||||
<div class="form-group">
|
||||
<%= admin_config_label :recaptcha_site_key, "reCAPTCHA Site Key" %>
|
||||
<%= f.text_field :recaptcha_site_key,
|
||||
class: "form-control",
|
||||
value: SiteConfig.recaptcha_site_key %>
|
||||
<div class="alert alert-info"><%= Constants::SiteConfig::DETAILS[:recaptcha_site_key][:description] %></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<%= admin_config_label :recaptcha_secret_key, "reCAPTCHA Secret Key" %>
|
||||
<%= f.text_field :recaptcha_secret_key,
|
||||
class: "form-control",
|
||||
value: SiteConfig.recaptcha_secret_key %>
|
||||
<div class="alert alert-info"><%= Constants::SiteConfig::DETAILS[:recaptcha_secret_key][:description] %></div>
|
||||
</div>
|
||||
|
||||
<%= render "form_submission", f: f %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_for(SiteConfig.new, url: admin_config_path) do |f| %>
|
||||
<div class="card mt-3">
|
||||
<%= render partial: "card_header",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
<div id="sign-in-password-form" class="mt-8 mb-6 crayons-card p-7 align-left mx-auto" style="max-width:580px;">
|
||||
<% if flash[:notice] %>
|
||||
<div class="crayons-notice crayons-notice--danger mb-6">
|
||||
<%= flash[:notice] %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= form_for(User.new, as: :user, url: registration_path(:user)) do |f| %>
|
||||
<% if defined?(resource) && resource&.errors&.any? %>
|
||||
<div class="crayons-card crayons-card--secondary crayons-notice crayons-notice--danger">
|
||||
|
|
@ -54,6 +59,12 @@
|
|||
<%= f.password_field :forem_owner_secret, autocomplete: "current-password", class: "crayons-textfield", required: true, placeholder: "As provided by your Forem host" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if recaptcha_configured_and_enabled? %>
|
||||
<div class="recaptcha-tag-container mt-2">
|
||||
<%= recaptcha_tags site_key: SiteConfig.recaptcha_site_key %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="actions pt-3">
|
||||
<%= f.submit "Sign up", class: "crayons-btn" %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -27,4 +27,31 @@ RSpec.describe AuthenticationHelper, type: :helper do
|
|||
expect(provider_names).not_to include(disabled_provider)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#recaptcha_configured_and_enabled?" do
|
||||
context "when recaptcha is enabled" do
|
||||
before do
|
||||
allow(SiteConfig).to receive(:require_captcha_for_email_password_registration).and_return(true)
|
||||
end
|
||||
|
||||
it "returns true if both site & secret keys present" do
|
||||
allow(SiteConfig).to receive(:recaptcha_secret_key).and_return("someSecretKey")
|
||||
allow(SiteConfig).to receive(:recaptcha_site_key).and_return("someSiteKey")
|
||||
|
||||
expect(recaptcha_configured_and_enabled?).to be(true)
|
||||
end
|
||||
|
||||
it "returns false if site or secret key missing" do
|
||||
allow(SiteConfig).to receive(:recaptcha_site_key).and_return("")
|
||||
|
||||
expect(recaptcha_configured_and_enabled?).to be(false)
|
||||
end
|
||||
end
|
||||
|
||||
it "returns false if recaptcha disabled for email signup" do
|
||||
allow(SiteConfig).to receive(:require_captcha_for_email_password_registration).and_return(false)
|
||||
|
||||
expect(recaptcha_configured_and_enabled?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -88,6 +88,19 @@ RSpec.describe "Registrations", type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
context "when email registration allowed and captcha required" do
|
||||
before do
|
||||
allow(SiteConfig).to receive(:allow_email_password_registration).and_return(true)
|
||||
allow(SiteConfig).to receive(:require_captcha_for_email_password_registration).and_return(true)
|
||||
end
|
||||
|
||||
it "displays the captcha box on email signup page" do
|
||||
get sign_up_path, params: { state: "email_signup" }
|
||||
|
||||
expect(response.body).to include("recaptcha-tag-container")
|
||||
end
|
||||
end
|
||||
|
||||
context "when user logged in" do
|
||||
it "redirects to main feed" do
|
||||
sign_in user
|
||||
|
|
@ -99,6 +112,14 @@ RSpec.describe "Registrations", type: :request do
|
|||
end
|
||||
|
||||
describe "POST /users" do
|
||||
def mock_recaptcha_verification
|
||||
# rubocop:disable RSpec/AnyInstance
|
||||
allow_any_instance_of(RegistrationsController).to(
|
||||
receive(:recaptcha_verified?).and_return(true),
|
||||
)
|
||||
# rubocop:enable RSpec/AnyInstance
|
||||
end
|
||||
|
||||
context "when site is not configured to accept email registration" do
|
||||
before do
|
||||
SiteConfig.allow_email_password_registration = false
|
||||
|
|
@ -154,6 +175,38 @@ RSpec.describe "Registrations", type: :request do
|
|||
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)
|
||||
allow(SiteConfig).to receive(:require_captcha_for_email_password_registration).and_return(true)
|
||||
end
|
||||
|
||||
it "creates user when valid params passed and recaptcha completed" do
|
||||
mock_recaptcha_verification
|
||||
post "/users", params:
|
||||
{ user: { name: "test #{rand(10)}",
|
||||
username: "haha_#{rand(10)}",
|
||||
email: "yoooo#{rand(100)}@yo.co",
|
||||
password: "PaSSw0rd_yo000",
|
||||
password_confirmation: "PaSSw0rd_yo000" } }
|
||||
expect(User.all.size).to be 1
|
||||
end
|
||||
|
||||
it "does not create user when valid params passed BUT recaptcha incomplete" do
|
||||
post "/users", params:
|
||||
{ user: { name: "test #{rand(10)}",
|
||||
username: "haha_#{rand(10)}",
|
||||
email: "yoooo#{rand(100)}@yo.co",
|
||||
password: "PaSSw0rd_yo000",
|
||||
password_confirmation: "PaSSw0rd_yo000" } }
|
||||
expect(User.all.size).to be 0
|
||||
expect(response).to redirect_to("/users/sign_up?state=email_signup")
|
||||
|
||||
follow_redirect!
|
||||
expect(response.body).to include("You must complete the recaptcha")
|
||||
end
|
||||
end
|
||||
|
||||
context "when site is in waiting_on_first_user state" do
|
||||
before do
|
||||
SiteConfig.waiting_on_first_user = true
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue