[deploy] Allow initial user to sign up and configure app (#10073)

* Allow initial user to sign up and configure app

* Fix added value unneeded

* Make siteconfig public in tests

* Remove deprecated method and adjust seed default

* Add new controllers not to redirect for public

* Modify tests

* Edit test defaults

* Fix linting

* Add starter mode to seed file
This commit is contained in:
Ben Halpern 2020-08-31 12:54:22 -04:00 committed by GitHub
parent 25e20bdc1c
commit dba151915b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 160 additions and 64 deletions

View file

@ -17,11 +17,12 @@ class ApplicationController < ActionController::Base
error_too_many_requests(exc)
end
PUBLIC_CONTROLLERS = %w[shell async_info ga_events].freeze
PUBLIC_CONTROLLERS = %w[shell async_info ga_events service_worker omniauth_callbacks registrations].freeze
private_constant :PUBLIC_CONTROLLERS
def verify_private_forem
return if controller_name.in?(PUBLIC_CONTROLLERS)
return if self.class.module_parent.to_s == "Admin"
return if user_signed_in? || SiteConfig.public
if api_action?

View file

@ -34,9 +34,9 @@ module VerifySetupCompleted
private
def verify_setup_completed
return if config_path? || setup_completed?
return if config_path? || setup_completed? || SiteConfig.waiting_on_first_user
link = helpers.link_to("the configuration page", admin_config_path)
link = helpers.link_to("the configuration page", admin_config_path, "data-no-instant" => true)
# rubocop:disable Rails/OutputSafety
flash[:global_notice] = "Setup not completed yet, please visit #{link}.".html_safe
# rubocop:enable Rails/OutputSafety

View file

@ -10,7 +10,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def create
not_authorized unless SiteConfig.allow_email_password_registration
not_authorized unless SiteConfig.allow_email_password_registration || SiteConfig.waiting_on_first_user
build_resource(sign_up_params)
resource.saw_onboarding = false
@ -18,9 +18,20 @@ class RegistrationsController < Devise::RegistrationsController
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
end
private
def update_first_user_permissions(resource)
return unless SiteConfig.waiting_on_first_user
resource.add_role(:super_admin)
resource.add_role(:single_resource_admin, Config)
SiteConfig.waiting_on_first_user = false
end
end

View file

@ -11,6 +11,8 @@ class SiteConfig < RailsSettings::Base
STACK_ICON = File.read(Rails.root.join("app/assets/images/stack.svg")).freeze
LIGHTNING_ICON = File.read(Rails.root.join("app/assets/images/lightning.svg")).freeze
field :waiting_on_first_user, type: :boolean, default: !User.exists?
# API Tokens
field :health_check_token, type: :string
@ -148,7 +150,7 @@ class SiteConfig < RailsSettings::Base
field :feed_style, type: :string, default: "basic"
# a non-public forem will redirect all unauthenticated pages to the registration page.
# a public forem could have more fine-grained authentication (listings ar private etc.) in future
field :public, type: :boolean, default: 1
field :public, type: :boolean, default: 0
# The default font for all users that have not chosen a custom font yet
field :default_font, type: :string, default: "sans_serif"

View file

@ -1,5 +1,11 @@
<% if params[:state] == "beta_email_signup" %>
<% if params[:state] == "beta_email_signup" && SiteConfig.allow_email_password_registration %>
<%= render "shared/authentication/email_registration_form" %>
<% elsif params[:state] == "beta_email_signup" %>
<div class="crayons-notice crayons-notice--danger mb-10 mt-8 mx-auto" style="width: 680px;max-width:94%;">
Email authentication is not enabled for this Forem.
</div>
<% elsif SiteConfig.waiting_on_first_user %>
<%= render "shared/authentication/initial_account_wizard" %>
<% else %>
<%= render "devise/shared/authorization_error" %>
<%= render "devise/registrations/registration_form" %>

View file

@ -1,61 +1,55 @@
<% if SiteConfig.allow_email_password_registration %>
<div id="sign-in-password-form" class="mt-8 mb-6 crayons-card p-7 align-left mx-auto" style="max-width:580px;">
<%= form_for(User.new, as: :user, url: registration_path(:user)) do |f| %>
<% if resource.errors.any? %>
<div class="crayons-card crayons-card--secondary crayons-notice crayons-notice--danger">
<div class="crayons-card__header">
<h3 class="crayons-card__headline">
Whoops, we found <%= pluralize(resource.errors.size, "problem") %>
</h3>
</div>
<div class="crayons-card__body">
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<div id="sign-in-password-form" class="mt-8 mb-6 crayons-card p-7 align-left mx-auto" style="max-width:580px;">
<%= 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">
<div class="crayons-card__header">
<h3 class="crayons-card__headline">
Whoops, we found <%= pluralize(resource.errors.size, "problem") %>
</h3>
</div>
<div class="crayons-card__body">
<ul>
<% resource.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% else %>
<p class="pb-4 fw-bold">Create account from email... (beta)</p>
<% 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" %>
</div>
<div class="crayons-field mt-2">
<%= f.label :name, class: "crayons-field__label" %>
<%= f.text_field :name, class: "crayons-textfield" %>
</div>
<div class="crayons-field mt-2">
<%= f.label :username, class: "crayons-field__label" %>
<%= f.text_field :username, class: "crayons-textfield" %>
</div>
<div class="crayons-field mt-2">
<%= f.label :email, class: "crayons-field__label" %>
<%= f.email_field :email, autocomplete: "email", class: "crayons-textfield" %>
</div>
<div class="crayons-field mt-2">
<%= f.label :password, class: "crayons-field__label" %>
<%= f.password_field :password, autocomplete: "current-password", class: "crayons-textfield" %>
</div>
<div class="crayons-field mt-2">
<%= f.label :password_confirmation, class: "crayons-field__label" %>
<%= f.password_field :password_confirmation, autocomplete: "current-password", class: "crayons-textfield" %>
</div>
<div class="actions pt-3">
<%= f.submit "Sign up", class: "crayons-btn" %>
</div>
<% else %>
<p class="pb-4 fw-bold">Create your account</p>
<% end %>
</div>
<% else %>
<div class="crayons-notice crayons-notice--danger mb-10 mt-8 mx-auto" style="width: 680px;max-width:94%;">
Email authentication is not enabled for this Forem.
</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 %>
</div>
<div class="crayons-field mt-2">
<%= f.label :name, class: "crayons-field__label" %>
<%= f.text_field :name, class: "crayons-textfield", required: true %>
</div>
<div class="crayons-field mt-2">
<%= f.label :username, class: "crayons-field__label" %>
<%= f.text_field :username, class: "crayons-textfield", required: true %>
</div>
<div class="crayons-field mt-2">
<%= f.label :email, class: "crayons-field__label" %>
<%= f.email_field :email, autocomplete: "email", class: "crayons-textfield", required: true %>
</div>
<div class="crayons-field mt-2">
<%= f.label :password, class: "crayons-field__label" %>
<%= f.password_field :password, autocomplete: "current-password", class: "crayons-textfield", required: true %>
</div>
<div class="crayons-field mt-2">
<%= f.label :password_confirmation, class: "crayons-field__label" %>
<%= f.password_field :password_confirmation, autocomplete: "current-password", class: "crayons-textfield", required: true %>
</div>
<div class="actions pt-3">
<%= f.submit "Sign up", class: "crayons-btn" %>
</div>
<% end %>
</div>

View file

@ -0,0 +1,21 @@
<% title "Let's get started with Forem" %>
<div class="crayons-card crayons-card--secondary crayons-notice crayons-notice--success mx-auto mt-8 mb-10 p-4 fs-2xl lh-base align-center" style="width:96%;max-width:1300px;">
<h1 class="m-7 mb-3">Let's start your Forem journey</h1>
<img class="m-7 mb-2 radius-default" src="https://media.giphy.com/media/F9hQLAVhWnL56/giphy.gif" style="height:220px" />
<p class="mb-4 mt-6">
Create an account. It will be the first super admin account.
</p>
<p class="mb-4">
Once you sign up below, you can configure your community at <a href="/admin/config">admin/config</a>.
</p>
<p class="mb-4">
There is lots you can do as an admin, but there is a learning curve.
</p>
<p class="mb-8">
Please reach out to the Forem team if you are unsure about anything!
</p>
<p class="mb-7 fw-bold">
For Empowering Community 🌱
</p>
<%= render "shared/authentication/email_registration_form" %>
</div>

View file

@ -30,6 +30,21 @@ SEEDS_MULTIPLIER = [1, ENV["SEEDS_MULTIPLIER"].to_i].max
puts "Seeding with multiplication factor: #{SEEDS_MULTIPLIER}\n\n"
##############################################################################
# Default development site config if different from production scenario
SiteConfig.public = true
SiteConfig.waiting_on_first_user = false
##############################################################################
# Put forem into "starter mode"
if ENV["MODE"] == "STARTER"
SiteConfig.public = false
SiteConfig.waiting_on_first_user = true
puts "Seeding forem in starter mode to replicate new creator experience"
exit # We don't need any models if we're launching things from startup.
end
seeder.create_if_none(Organization) do
3.times do

View file

@ -45,3 +45,11 @@ will result in creating double the default amount of items in the database.
It's currently used only for `articles` and `users`.
It can also be used for `rails db:seed` and `rails db:reset`.
### Other seed modes
To put your local forem into "starter mode", as it would be for a new creator, use `MODE=STARTER` i.e...
```shell
MODE=STARTER rails db:setup
```

View file

@ -167,6 +167,8 @@ RSpec.configure do |config|
}).to_return(status: 200, body: "", headers: {})
allow(SiteConfig).to receive(:community_description).and_return("Some description")
SiteConfig.public = true
SiteConfig.waiting_on_first_user = false
end
config.after do

View file

@ -38,6 +38,7 @@ RSpec.describe "Registrations", type: :request do
before do
SiteConfig.allow_email_password_registration = false
end
it "disallows communities where email registration is not allowed" do
expect { post "/users" }.to raise_error Pundit::NotAuthorizedError
end
@ -87,5 +88,40 @@ RSpec.describe "Registrations", type: :request do
expect(User.all.size).to be 0
end
end
context "when site is in waiting_on_first_user state" do
before do
SiteConfig.waiting_on_first_user = true
end
after do
SiteConfig.waiting_on_first_user = false
end
it "does not raise disallowed if community is set to allow email" do
expect { post "/users" }.not_to raise_error Pundit::NotAuthorizedError
end
it "creates user with valid params passed" 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 1
end
it "makes user super admin and config admin" 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.first.has_role?(:super_admin)).to be true
expect(User.first.has_role?(:single_resource_admin, Config)).to be true
end
end
end
end