From dba151915b772bb3172f31a6ee10537b4501aace Mon Sep 17 00:00:00 2001 From: Ben Halpern Date: Mon, 31 Aug 2020 12:54:22 -0400 Subject: [PATCH] [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 --- app/controllers/application_controller.rb | 3 +- .../concerns/verify_setup_completed.rb | 4 +- app/controllers/registrations_controller.rb | 13 ++- app/models/site_config.rb | 4 +- app/views/devise/registrations/new.html.erb | 8 +- .../_email_registration_form.html.erb | 110 +++++++++--------- .../_initial_account_wizard.html.erb | 21 ++++ db/seeds.rb | 15 +++ docs/getting-started/db.md | 8 ++ spec/rails_helper.rb | 2 + spec/requests/registrations_spec.rb | 36 ++++++ 11 files changed, 160 insertions(+), 64 deletions(-) create mode 100644 app/views/shared/authentication/_initial_account_wizard.html.erb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 135636ef7..7ea6b0665 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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? diff --git a/app/controllers/concerns/verify_setup_completed.rb b/app/controllers/concerns/verify_setup_completed.rb index 7af57783f..e4e2803d8 100644 --- a/app/controllers/concerns/verify_setup_completed.rb +++ b/app/controllers/concerns/verify_setup_completed.rb @@ -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 diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 1574c5105..a0c680589 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -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 diff --git a/app/models/site_config.rb b/app/models/site_config.rb index c15ad6c69..764627dd1 100644 --- a/app/models/site_config.rb +++ b/app/models/site_config.rb @@ -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" diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index c9728e060..91acae02b 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -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" %> +
+ Email authentication is not enabled for this Forem. +
+<% elsif SiteConfig.waiting_on_first_user %> + <%= render "shared/authentication/initial_account_wizard" %> <% else %> <%= render "devise/shared/authorization_error" %> <%= render "devise/registrations/registration_form" %> diff --git a/app/views/shared/authentication/_email_registration_form.html.erb b/app/views/shared/authentication/_email_registration_form.html.erb index 937072215..75fce7338 100644 --- a/app/views/shared/authentication/_email_registration_form.html.erb +++ b/app/views/shared/authentication/_email_registration_form.html.erb @@ -1,61 +1,55 @@ -<% if SiteConfig.allow_email_password_registration %> -
- <%= form_for(User.new, as: :user, url: registration_path(:user)) do |f| %> - <% if resource.errors.any? %> -
-
-

- Whoops, we found <%= pluralize(resource.errors.size, "problem") %> -

-
-
-
    - <% resource.errors.full_messages.each do |message| %> -
  • <%= message %>
  • - <% end %> -
-
+
+ <%= form_for(User.new, as: :user, url: registration_path(:user)) do |f| %> + <% if defined?(resource) && resource&.errors&.any? %> +
+
+

+ Whoops, we found <%= pluralize(resource.errors.size, "problem") %> +

+
+
+
    + <% resource.errors.full_messages.each do |message| %> +
  • <%= message %>
  • + <% end %> +
- <% else %> -

Create account from email... (beta)

- <% end %> -
- <%= 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" %> -
- -
- <%= f.label :name, class: "crayons-field__label" %> - <%= f.text_field :name, class: "crayons-textfield" %> -
- -
- <%= f.label :username, class: "crayons-field__label" %> - <%= f.text_field :username, class: "crayons-textfield" %> -
- -
- <%= f.label :email, class: "crayons-field__label" %> - <%= f.email_field :email, autocomplete: "email", class: "crayons-textfield" %> -
- -
- <%= f.label :password, class: "crayons-field__label" %> - <%= f.password_field :password, autocomplete: "current-password", class: "crayons-textfield" %> -
- -
- <%= f.label :password_confirmation, class: "crayons-field__label" %> - <%= f.password_field :password_confirmation, autocomplete: "current-password", class: "crayons-textfield" %> -
- -
- <%= f.submit "Sign up", class: "crayons-btn" %>
+ <% else %> +

Create your account

<% end %> -
-<% else %> -
- Email authentication is not enabled for this Forem. -
-<% end %> +
+ <%= 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 %> +
+ +
+ <%= f.label :name, class: "crayons-field__label" %> + <%= f.text_field :name, class: "crayons-textfield", required: true %> +
+ +
+ <%= f.label :username, class: "crayons-field__label" %> + <%= f.text_field :username, class: "crayons-textfield", required: true %> +
+ +
+ <%= f.label :email, class: "crayons-field__label" %> + <%= f.email_field :email, autocomplete: "email", class: "crayons-textfield", required: true %> +
+ +
+ <%= f.label :password, class: "crayons-field__label" %> + <%= f.password_field :password, autocomplete: "current-password", class: "crayons-textfield", required: true %> +
+ +
+ <%= f.label :password_confirmation, class: "crayons-field__label" %> + <%= f.password_field :password_confirmation, autocomplete: "current-password", class: "crayons-textfield", required: true %> +
+ +
+ <%= f.submit "Sign up", class: "crayons-btn" %> +
+ <% end %> +
diff --git a/app/views/shared/authentication/_initial_account_wizard.html.erb b/app/views/shared/authentication/_initial_account_wizard.html.erb new file mode 100644 index 000000000..2f5ea73d6 --- /dev/null +++ b/app/views/shared/authentication/_initial_account_wizard.html.erb @@ -0,0 +1,21 @@ +<% title "Let's get started with Forem" %> +
+

Let's start your Forem journey

+ +

+ Create an account. It will be the first super admin account. +

+

+ Once you sign up below, you can configure your community at admin/config. +

+

+ There is lots you can do as an admin, but there is a learning curve. +

+

+ Please reach out to the Forem team if you are unsure about anything! +

+

+ For Empowering Community 🌱 +

+ <%= render "shared/authentication/email_registration_form" %> +
diff --git a/db/seeds.rb b/db/seeds.rb index 7b0b66716..d59a96c82 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -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 diff --git a/docs/getting-started/db.md b/docs/getting-started/db.md index 04f40e620..def6818ac 100644 --- a/docs/getting-started/db.md +++ b/docs/getting-started/db.md @@ -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 +``` \ No newline at end of file diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 6b5b0324b..34b753d84 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -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 diff --git a/spec/requests/registrations_spec.rb b/spec/requests/registrations_spec.rb index 31796f500..41a38a3c7 100644 --- a/spec/requests/registrations_spec.rb +++ b/spec/requests/registrations_spec.rb @@ -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