[Team Email Login] Refactor the Email Authentication flow (#11090)

* Hooked "Enable" button to hidden checkbox

* Hooked "Close" button to close email settings and show "Enable/Edit" button

* Additional hookups

* Start building generalized Modal blocks

* Everything hooked up except styling and a few Qs

* last of the hookups; ensure logic flow

* clean up

* specs to cover email auth refactor

* Fix bug surfaced by Vaidehi

* Incorporate PR feedback

* prevent email auth disable if invite-only-mode

* adjust emailAuthModal body text

* Sundry improvements

* Last-mile tweaks

* Trying to get 3rd party auth deselect to work

* delete unnecssary function

* remove superfluous comment

* Move inline styling into CSS file

* Incorporate PR feedback

* Incorporate more PR feedback

* Make Confirm btn intent clearer

* Add TODO comment
This commit is contained in:
Arit Amana 2020-10-28 13:37:11 -04:00 committed by GitHub
parent fc22e2c4ae
commit 1912ffdc46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 248 additions and 58 deletions

View file

@ -125,3 +125,15 @@ label {
flex-direction: column;
gap: var(--su-4);
}
.admin-config-checkmark {
width: var(--su-4);
height: var(--su-4);
background-color: var(--accent-success);
border-radius: 100%;
color: var(--base-inverted);
}
.cursor-disabled {
cursor: not-allowed;
}

View file

@ -118,8 +118,7 @@ module Admin
facebook_key
facebook_secret
invite_only_mode
allow_email_password_registration
allow_email_password_login
allow_both_email_signup_and_login
require_captcha_for_email_password_registration
primary_brand_color_hex
spam_trigger_terms
@ -153,6 +152,8 @@ module Admin
end
end
toggle_email_password_authentication
redirect_to admin_config_path, notice: "Site configuration was successfully updated."
end
@ -211,6 +212,17 @@ module Admin
config[:credit_prices_in_cents]&.transform_values!(&:to_i)
end
def toggle_email_password_authentication
if SiteConfig.allow_both_email_signup_and_login
SiteConfig.allow_email_password_registration = true
SiteConfig.allow_email_password_login = true
else
SiteConfig.allow_email_password_registration = false
SiteConfig.allow_email_password_login = false
SiteConfig.invite_only_mode = false
end
end
# Validations
def brand_contrast_too_low
hex = params.dig(:site_config, :primary_brand_color_hex)

View file

@ -32,4 +32,24 @@ module AuthenticationHelper
def waiting_on_first_user?
SiteConfig.waiting_on_first_user
end
def disable_email_tooltip_class
SiteConfig.invite_only_mode || authentication_enabled_providers.none? ? "crayons-tooltip" : ""
end
def disable_email_tooltip_content
SiteConfig.invite_only_mode || authentication_enabled_providers.none? ? disable_email_auth_tooltip_text : ""
end
def disable_button_class
SiteConfig.invite_only_mode || authentication_enabled_providers.none? ? "disabled" : ""
end
def disable_email_auth_tooltip_text
if SiteConfig.invite_only_mode
"You cannot do this until you disable Invite Only Mode"
elsif authentication_enabled_providers.none?
"You cannot do this until you enable at least one other registration option"
end
end
end

View file

@ -1,11 +1,63 @@
import { Controller } from 'stimulus';
const recaptchaFields = document.querySelector('#recaptchaContainer');
const emailSigninAndLoginCheckbox = document.querySelector(
'#email-signup-and-login-checkbox',
);
const emailAuthSettingsSection = document.querySelector(
'#email-auth-settings-section',
);
const modalAnchor = document.querySelector('.admin-config-modal-anchor');
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><br /><p>However, people who have already created an account using their email address can continue to login.</p><br /><p><strong>You must update site config to save this action!</strong></p>';
const adminConfigModal = (
title,
body,
confirmBtnText,
confirmBtnAction,
cancelBtnText,
cancelBtnAction,
) => `
<div class="crayons-modal crayons-modal--s absolute">
<div class="crayons-modal__box">
<header class="crayons-modal__box__header">
<p class="fw-bold fs-l">${title}</p>
<button type="button" class="crayons-btn crayons-btn--icon crayons-btn--ghost" data-action="click->config#closeAdminConfigModal">
<svg width="24" height="24" viewBox="0 0 24 24" class="crayons-icon" xmlns="http://www.w3.org/2000/svg">
<path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636l4.95 4.95z" />
</svg>
</button>
</header>
<div class="crayons-modal__box__body">
${body}
<div class="mt-6">
<button
class="crayons-btn crayons-btn--danger"
data-action="click->config#${confirmBtnAction}">
${confirmBtnText}
</button>
<button
class="crayons-btn crayons-btn--secondary"
data-action="click->config#${cancelBtnAction}">
${cancelBtnText}
</button>
</div>
</div>
</div>
<div class="crayons-modal__overlay"></div>
</div>
`;
export default class ConfigController extends Controller {
static targets = [
'authenticationProviders',
'collectiveNoun',
'emailAuthSettingsBtn',
'inviteOnlyMode',
'requireCaptchaForEmailPasswordRegistration',
];
@ -29,9 +81,59 @@ export default class ConfigController extends Controller {
toggleGoogleRecaptchaFields() {
if (this.requireCaptchaForEmailPasswordRegistrationTarget.checked) {
recaptchaFields.classList.remove('collapse');
recaptchaFields.classList.remove('hidden');
} else {
recaptchaFields.classList.add('collapse');
recaptchaFields.classList.add('hidden');
}
}
enableOrEditEmailAuthSettings() {
event.preventDefault();
if (this.emailAuthSettingsBtnTarget.dataset.buttonText === 'enable') {
emailSigninAndLoginCheckbox.checked = true;
}
this.emailAuthSettingsBtnTarget.classList.add('hidden');
emailAuthSettingsSection.classList.remove('hidden');
}
hideEmailAuthSettings() {
event.preventDefault();
this.emailAuthSettingsBtnTarget.classList.remove('hidden');
emailAuthSettingsSection.classList.add('hidden');
}
activateEmailAuthModal() {
event.preventDefault();
modalAnchor.innerHTML = adminConfigModal(
emailAuthModalTitle,
emailAuthModalBody,
'Confirm',
'disableEmailAuthFromModal',
'Cancel',
'closeAdminConfigModal',
);
if (document.querySelector('.crayons-modal')) {
window.scrollTo(0, 0);
document.body.style.height = '100vh';
document.body.style.overflowY = 'hidden';
}
}
closeAdminConfigModal() {
modalAnchor.innerHTML = '';
document.body.style.height = 'inherit';
document.body.style.overflowY = 'inherit';
}
disableEmailAuthFromModal() {
event.preventDefault();
emailSigninAndLoginCheckbox.checked = false;
this.closeAdminConfigModal();
}
disableEmailAuth() {
event.preventDefault();
emailSigninAndLoginCheckbox.checked = false;
this.hideEmailAuthSettings();
}
}

View file

@ -4,14 +4,6 @@ module Constants
SVG_PLACEHOLDER = "<svg ...></svg>".freeze
DETAILS = {
allow_email_password_registration: {
description: "People can sign up using their email and password",
placeholder: ""
},
allow_email_password_login: {
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",
@ -155,7 +147,7 @@ module Constants
placeholder: "0"
},
invite_only_mode: {
description: "Only users invited by email can join this community.",
description: "Only users invited by email can join this community. To use this, you must enable email address registration.",
placeholder: ""
},
jobs_url: {

View file

@ -25,8 +25,9 @@ class SiteConfig < RailsSettings::Base
field :video_encoder_key, type: :string
# Authentication
field :allow_email_password_registration, type: :boolean, default: false
field :allow_email_password_registration, type: :boolean, default: true
field :allow_email_password_login, type: :boolean, default: true
field :allow_both_email_signup_and_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

View file

@ -141,7 +141,11 @@
</h3>
<div class="form-group">
<div class="crayons-field--checkbox">
<%= f.check_box :invite_only_mode, checked: SiteConfig.invite_only_mode, data: { action: "config#disableTargetField", disable_target: "authenticationProviders" }, class: "crayons-checkbox" %>
<%= f.check_box :invite_only_mode,
checked: SiteConfig.invite_only_mode,
disabled: !SiteConfig.allow_both_email_signup_and_login,
data: { action: "config#disableAuthenticationOptions", target: "config.inviteOnlyMode" },
class: "crayons-checkbox #{!SiteConfig.allow_both_email_signup_and_login ? 'cursor-disabled' : ''}" %>
<div class="mt-0">
<%= admin_config_label :invite_only_mode %>
<%= admin_config_description Constants::SiteConfig::DETAILS[:invite_only_mode][:description] %>
@ -150,55 +154,45 @@
</div>
</section>
<section>
<h3 class="crayons-subtitle-3 mb-3">
Authentication methods
<h3 class="crayons-subtitle-3 mb-1">
Registration options
</h3>
<div class="form-group">
<%= admin_config_label :authentication_providers %>
<p class="crayons-field__description">
<%= Constants::SiteConfig::DETAILS[:authentication_providers][:description] %>
</p>
<%= select_tag "site_config[authentication_providers]",
options_from_collection_for_select(
authentication_available_providers,
:provider_name,
:official_name,
authentication_enabled_providers.map(&:provider_name),
),
multiple: true,
class: "form-control selectpicker",
data: { target: "config.authenticationProviders" } %>
</div>
<p class="crayons-field__description mb-6">
How can people create an account on your community?
</p>
<section class="config-authentication__item">
<figure class="config-authentication__item--icon">
<%= inline_svg_tag("email.svg", class: "crayons-icon", aria: true, title: "Twitter logo") %>
<%= inline_svg_tag("email.svg", class: "crayons-icon", aria: true, title: "Email icon") %>
</figure>
<div class="config-authentication__item--container">
<div class="config-authentication__item--label">
<p class="fw-medium">
Email address and password
</p>
<div class="config-authentication__item flex items-baseline justify-between">
<div>
<p class="config-authentication__item--label fw-medium <%= SiteConfig.allow_both_email_signup_and_login ? "pb-0" : "" %>">
Email address
</p>
<% if SiteConfig.allow_both_email_signup_and_login %>
<div class="mb-5 flex items-center">
<%= inline_svg_tag("checkmark.svg", aria: true, class: "crayons-icon admin-config-checkmark", title: "Checkmark") %>
<small class="crayons-field__description ml-1">Enabled</small>
</div>
<% end %>
</div>
<button
class="crayons-btn crayons-btn--secondary"
data-button-text="<%= SiteConfig.allow_both_email_signup_and_login ? "edit" : "enable" %>"
data-target="config.emailAuthSettingsBtn"
data-action="click->config#enableOrEditEmailAuthSettings">
<%= SiteConfig.allow_both_email_signup_and_login ? "Edit" : "Enable" %>
</button>
</div>
<div class="config-authentication__item--body">
<div id="email-auth-settings-section" class="config-authentication__item--body hidden">
<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>
<%= admin_config_label :allow_email_password_login %>
<p class="crayons-field__description">
<%= Constants::SiteConfig::DETAILS[:allow_email_password_login][:description] %>
</p>
</div>
<div class="crayons-field--checkbox hidden">
<%= f.check_box :allow_both_email_signup_and_login,
checked: SiteConfig.allow_both_email_signup_and_login,
id: "email-signup-and-login-checkbox",
class: "crayons-checkbox" %>
</div>
<div class="crayons-field--checkbox">
<%= f.check_box :require_captcha_for_email_password_registration,
@ -212,7 +206,7 @@
</p>
</div>
</div>
<div id="recaptchaContainer" class="<%= SiteConfig.require_captcha_for_email_password_registration ? "" : "collapse" %> ml-7" aria-labelledby="recaptchaContainer">
<div id="recaptchaContainer" class="<%= SiteConfig.require_captcha_for_email_password_registration ? "" : "hidden" %> 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">
@ -234,11 +228,52 @@
placeholder: Constants::SiteConfig::DETAILS[:recaptcha_secret_key][:placeholder] %>
</div>
</div>
<div class="mt-6">
<% if SiteConfig.allow_both_email_signup_and_login %>
<button
class="crayons-btn crayons-btn--danger <%= disable_email_tooltip_class %>"
data-action="click->config#activateEmailAuthModal"
data-tooltip="<%= disable_email_tooltip_content %>"
<%= disable_button_class %>>
Disable
</button>
<% end %>
<% if SiteConfig.allow_both_email_signup_and_login %>
<button
class="crayons-btn crayons-btn--secondary"
data-action="click->config#hideEmailAuthSettings">
Close
</button>
<% else %>
<button
class="crayons-btn crayons-btn--secondary"
data-action="click->config#disableEmailAuth">
Undo
</button>
<% end %>
</div>
</fieldset>
</div>
</div>
</section>
<div class="form-group">
<%= admin_config_label :authentication_providers %>
<p class="crayons-field__description">
<%= Constants::SiteConfig::DETAILS[:authentication_providers][:description] %>
</p>
<%= select_tag "site_config[authentication_providers]",
options_from_collection_for_select(
authentication_available_providers,
:provider_name,
:official_name,
authentication_enabled_providers.map(&:provider_name),
),
multiple: true,
class: "form-control selectpicker",
data: { target: "config.authenticationProviders" } %>
</div>
<section class="config-authentication__item">
<figure class="config-authentication__item--icon">
<%= inline_svg_tag("twitter.svg", class: "crayons-icon", aria: true, title: "Twitter logo") %>
@ -1338,4 +1373,5 @@
</div>
</div>
</div>
<div class="admin-config-modal-anchor"></div>
</div>

View file

@ -8,7 +8,7 @@
<%= params[:state] == "new-user" ? "Sign up" : "Continue" %> with <%= provider.official_name %>
</a>
<% end %>
<% if params[:state] == "new-user" && SiteConfig.allow_email_password_registration %>
<% if params[:state] == "new-user" && SiteConfig.allow_email_password_registration && !SiteConfig.invite_only_mode %>
<%= link_to "#{inline_svg_tag('email.svg', aria: true, class: 'crayons-icon', title: 'email')}Sign up with Email".html_safe,
request.params.merge(state: "email_signup").except("i"),
class: "crayons-btn crayons-btn--l crayons-btn--brand-email crayons-btn--icon-left whitespace-nowrap",

View file

@ -87,6 +87,21 @@ RSpec.describe "/admin/config", type: :request do
confirmation: confirmation_message }
expect(SiteConfig.authentication_providers).to eq([provider])
end
it "enables email authentication" do
post "/admin/config", params: { site_config: { allow_both_email_signup_and_login: true },
confirmation: confirmation_message }
expect(SiteConfig.allow_email_password_registration).to be(true)
expect(SiteConfig.allow_email_password_login).to be(true)
end
it "disables email authentication and invite-only mode" do
post "/admin/config", params: { site_config: { allow_both_email_signup_and_login: false },
confirmation: confirmation_message }
expect(SiteConfig.allow_email_password_registration).to be(false)
expect(SiteConfig.allow_email_password_login).to be(false)
expect(SiteConfig.invite_only_mode).to be(false)
end
end
describe "Community Content" do