diff --git a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs b/app/assets/javascripts/wizard/templates/components/wizard-step.hbs index 18ffac5463e..9c945cf60a2 100644 --- a/app/assets/javascripts/wizard/templates/components/wizard-step.hbs +++ b/app/assets/javascripts/wizard/templates/components/wizard-step.hbs @@ -11,7 +11,6 @@

{{{step.description}}}

{{/if}} - {{#wizard-step-form step=step}} {{#each step.fields as |field|}} {{wizard-field field=field step=step wizard=wizard}} diff --git a/app/assets/stylesheets/wizard.scss b/app/assets/stylesheets/wizard.scss index 52668acf7cd..69df6317b33 100644 --- a/app/assets/stylesheets/wizard.scss +++ b/app/assets/stylesheets/wizard.scss @@ -14,6 +14,21 @@ body.wizard { line-height: 1.4em; } +.finish-installation { + .tada { + width: 300px; + } + + .row { + text-align: center; + margin-bottom: 1em; + } + + .help-text { + color: #999; + } +} + .discourse-logo { background-image: asset-url('/images/wizard/discourse.png'); height: 30px; @@ -182,6 +197,7 @@ body.wizard { padding: 0.5em; transition: background-color .3s; margin-right: 0.5em; + text-decoration: none; background-color: #fff; color: #333; @@ -480,4 +496,4 @@ body.wizard { .invite-list .new-user { flex-direction: column !important; align-items: inherit !important; } .invite-list .new-user .invite-email { width: 100% !important; margin-bottom: 5px !important; } .invite-list .add-user { margin-top: 5px !important; } -} \ No newline at end of file +} diff --git a/app/controllers/finish_installation_controller.rb b/app/controllers/finish_installation_controller.rb new file mode 100644 index 00000000000..7e6b8c4d3b3 --- /dev/null +++ b/app/controllers/finish_installation_controller.rb @@ -0,0 +1,53 @@ +class FinishInstallationController < ApplicationController + skip_before_filter :check_xhr, :preload_json, :redirect_to_login_if_required + layout 'finish_installation' + + before_filter :ensure_no_admins, except: ['confirm_email'] + + def index + end + + def register + @allowed_emails = find_allowed_emails + + @user = User.new + if request.post? + email = params[:email].strip + raise Discourse::InvalidParameters.new unless @allowed_emails.include?(email) + + return redirect_confirm(email) if User.where(email: email).exists? + + @user.email = email + @user.username = params[:username] + @user.password = params[:password] + @user.password_required! + + if @user.save + @email_token = @user.email_tokens.unconfirmed.active.first + Jobs.enqueue(:critical_user_email, type: :signup, user_id: @user.id, email_token: @email_token.token) + return redirect_confirm(@user.email) + end + + end + end + + def confirm_email + @email = session[:registered_email] + end + + protected + + def redirect_confirm(email) + session[:registered_email] = email + redirect_to(finish_installation_confirm_email_path) + end + + def find_allowed_emails + return [] unless GlobalSetting.respond_to?(:developer_emails) && GlobalSetting.developer_emails.present? + GlobalSetting.developer_emails.split(",").map(&:strip) + end + + def ensure_no_admins + raise Discourse::InvalidAccess.new unless SiteSetting.has_login_hint? + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 36be2b16c1c..4f85726b510 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,6 +1,8 @@ require_dependency 'discourse_hub' require_dependency 'user_name_suggester' require_dependency 'rate_limiter' +require_dependency 'wizard' +require_dependency 'wizard/builder' class UsersController < ApplicationController @@ -411,6 +413,8 @@ class UsersController < ApplicationController Invite.invalidate_for_email(@user.email) # invite link can't be used to log in anymore session["password-#{params[:token]}"] = nil logon_after_password_reset + + return redirect_to(wizard_path) if Wizard.user_requires_completion?(@user) end end end @@ -507,6 +511,7 @@ class UsersController < ApplicationController if Guardian.new(@user).can_access_forum? @user.enqueue_welcome_message('welcome_user') if @user.send_welcome_message log_on_user(@user) + return redirect_to(wizard_path) if Wizard.user_requires_completion?(@user) else @needs_approval = true end diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb index 217f7885438..718cb1221aa 100644 --- a/app/serializers/site_serializer.rb +++ b/app/serializers/site_serializer.rb @@ -119,6 +119,6 @@ class SiteSerializer < ApplicationSerializer end def include_wizard_required? - Wizard::Builder.new(scope.user).build.requires_completion? + Wizard.user_requires_completion?(scope.user) end end diff --git a/app/views/finish_installation/confirm_email.html.erb b/app/views/finish_installation/confirm_email.html.erb new file mode 100644 index 00000000000..73f2a381684 --- /dev/null +++ b/app/views/finish_installation/confirm_email.html.erb @@ -0,0 +1,3 @@ +

<%= t 'first_installation.confirm_email.title' %>

+ +<%= raw(t 'first_installation.confirm_email.message', email: @email) %> diff --git a/app/views/finish_installation/index.html.erb b/app/views/finish_installation/index.html.erb new file mode 100644 index 00000000000..4ae9cbba1f0 --- /dev/null +++ b/app/views/finish_installation/index.html.erb @@ -0,0 +1,16 @@ +

<%= t 'first_installation.congratulations' %>

+ +
+ <%= image_tag "/images/wizard/tada.svg", class: "tada" %> +
+ +
+ <%= t 'first_installation.register.help' %> +
+ +
+ <%= link_to(finish_installation_register_path, class: 'wizard-btn primary') do %> + + <%= t 'first_installation.register.button' %> + <% end %> +
diff --git a/app/views/finish_installation/register.html.erb b/app/views/finish_installation/register.html.erb new file mode 100644 index 00000000000..8d952a355d2 --- /dev/null +++ b/app/views/finish_installation/register.html.erb @@ -0,0 +1,57 @@ +

<%= t 'first_installation.register.title' %>

+ +<%- if @allowed_emails.present? %> + <%= form_tag(finish_installation_register_path) do %> + +
+ + +
+ <%= select_tag :email, options_for_select(@allowed_emails, selected: params[:email]), class: 'combobox' %> +
+
+ +
+ + +
<%= t 'js.user.username.instructions' %>
+ +
+ <%= text_field_tag(:username, params[:username]) %> +
+ <%- @user.errors[:username].each do |e| %> +
<%= e.to_s %>
+ <%- end %> +
+ +
+ + +
<%= t 'js.user.password.instructions', count: SiteSetting.min_admin_password_length %>
+ +
+ <%= password_field_tag(:password, params[:password]) %> +
+ <% @user.errors[:password].each do |e| %> +
<%= e.to_s %>
+ <% end %> +
+ + <%= submit_tag(t('first_installation.register.button'), class: 'wizard-btn primary') %> + + <%- end %> +<%- else -%> +

<%= raw(t 'first_installation.register.no_emails') %>

+<%- end %> + + diff --git a/app/views/layouts/finish_installation.html.erb b/app/views/layouts/finish_installation.html.erb new file mode 100644 index 00000000000..2b709a56c9c --- /dev/null +++ b/app/views/layouts/finish_installation.html.erb @@ -0,0 +1,23 @@ + + + <%= stylesheet_link_tag 'wizard' %> + <%= render partial: "common/special_font_face" %> + <%= script 'jquery_include' %> + <%= script 'wizard-vendor' %> + <%= render partial: "layouts/head" %> + <%= t 'wizard.title' %> + + + +
+
+
+ <%= yield %> +
+ +
+
+ + diff --git a/config/initializers/006-ensure_login_hint.rb b/config/initializers/006-ensure_login_hint.rb index b497eb93e39..fb0a526c7e0 100644 --- a/config/initializers/006-ensure_login_hint.rb +++ b/config/initializers/006-ensure_login_hint.rb @@ -6,7 +6,7 @@ if User.limit(20).count < 20 && User.where(admin: true).count == 1 else emails = GlobalSetting.developer_emails.split(",") if emails.length > 1 - emails = emails[0..-2].join(' , ') << " or #{emails[-1]} " + emails = emails[0..-2].join(', ') << " or #{emails[-1]} " else emails = emails[0] end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index f3bd464db31..fd7ebcc9b6f 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -3193,6 +3193,17 @@ en: staff_tag_remove_disallowed: "The tag \"%{tag}\" may only be removed by staff." rss_by_tag: "Topics tagged %{tag}" + first_installation: + congratulations: "Congratulations, you installed Discourse!" + register: + button: "Register" + title: "Register Admin Account" + help: "register a new account to get started" + no_emails: "Unfortunately, no administrator emails were defined during setup, so finalizing the configuration may be difficult." + confirm_email: + title: "Confirm your Email" + message: "

We sent an activation mail to %{email}. Please follow the instructions in the email to activate your account.

If it doesn't arrive, ensure you have set up email correctly for your Discourse and check your spam folder.

" + wizard: title: "Discourse Setup" step: diff --git a/config/routes.rb b/config/routes.rb index fe1b7885980..5c51163ef59 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -37,6 +37,11 @@ Discourse::Application.routes.draw do end end + get "finish-installation" => "finish_installation#index" + get "finish-installation/register" => "finish_installation#register" + post "finish-installation/register" => "finish_installation#register" + get "finish-installation/confirm-email" => "finish_installation#confirm_email" + resources :directory_items get "site" => "site#site" @@ -687,6 +692,8 @@ Discourse::Application.routes.draw do # special case for top root to: "list#top", constraints: HomePageConstraint.new("top"), :as => "top_lists" + root to: 'finish_installation#index', constraints: HomePageConstraint.new("finish_installation"), as: 'installation_redirect' + get "/user-api-key/new" => "user_api_keys#new" post "/user-api-key" => "user_api_keys#create" post "/user-api-key/revoke" => "user_api_keys#revoke" diff --git a/lib/homepage_constraint.rb b/lib/homepage_constraint.rb index 247da1c904c..0783fd51f04 100644 --- a/lib/homepage_constraint.rb +++ b/lib/homepage_constraint.rb @@ -4,6 +4,8 @@ class HomePageConstraint end def matches?(request) + return @filter == 'finish_installation' if SiteSetting.has_login_hint? + provider = Discourse.current_user_provider.new(request.env) homepage = provider.current_user ? SiteSetting.homepage : SiteSetting.anonymous_homepage homepage == @filter diff --git a/lib/wizard.rb b/lib/wizard.rb index 0ae5e6ac59a..2e1689c5b62 100644 --- a/lib/wizard.rb +++ b/lib/wizard.rb @@ -76,13 +76,17 @@ class Wizard def requires_completion? return false unless SiteSetting.wizard_enabled? + first_admin = User.where(admin: true) .where.not(id: Discourse.system_user.id) .where.not(auth_token_updated_at: nil) .order(:auth_token_updated_at) - .first - @user.present? && first_admin == @user && !completed? && (Topic.count < 15) + @user.present? && first_admin.first == @user && !completed? && (Topic.count < 15) + end + + def self.user_requires_completion?(user) + Wizard::Builder.new(user).build.requires_completion? end end diff --git a/public/images/wizard/tada.svg b/public/images/wizard/tada.svg new file mode 100644 index 00000000000..c6c77289993 --- /dev/null +++ b/public/images/wizard/tada.svg @@ -0,0 +1 @@ +image/svg+xml diff --git a/spec/controllers/finish_installation_controller_spec.rb b/spec/controllers/finish_installation_controller_spec.rb new file mode 100644 index 00000000000..2ada049fbf2 --- /dev/null +++ b/spec/controllers/finish_installation_controller_spec.rb @@ -0,0 +1,85 @@ +require 'rails_helper' + +describe FinishInstallationController do + + describe '.index' do + context "has_login_hint is false" do + before do + SiteSetting.has_login_hint = false + end + + it "doesn't allow access" do + get :index + expect(response).not_to be_success + end + end + + context "has_login_hint is true" do + before do + SiteSetting.has_login_hint = true + end + + it "allows access" do + get :index + expect(response).to be_success + end + end + end + + describe '.register' do + context "has_login_hint is false" do + before do + SiteSetting.has_login_hint = false + end + + it "doesn't allow access" do + get :register + expect(response).not_to be_success + end + end + + context "has_login_hint is true" do + before do + SiteSetting.has_login_hint = true + GlobalSetting.stubs(:developer_emails).returns("robin@example.com") + end + + it "allows access" do + get :register + expect(response).to be_success + end + + it "raises an error when the email is not in the allowed list" do + expect { + post :register, email: 'notrobin@example.com', username: 'eviltrout', password: 'disismypasswordokay' + }.to raise_error(Discourse::InvalidParameters) + end + + it "doesn't redirect when fields are wrong" do + post :register, email: 'robin@example.com', username: '', password: 'disismypasswordokay' + expect(response).not_to be_redirect + end + + it "registers the admin when the email is in the list" do + Jobs.expects(:enqueue).with(:critical_user_email, has_entries(type: :signup)) + post :register, email: 'robin@example.com', username: 'eviltrout', password: 'disismypasswordokay' + expect(response).to be_redirect + expect(User.where(username: 'eviltrout').exists?).to eq(true) + end + + end + end + + describe '.confirm_email' do + context "has_login_hint is false" do + before do + SiteSetting.has_login_hint = false + end + + it "shows the page" do + get :confirm_email + expect(response).to be_success + end + end + end +end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb index d5f259cdaf7..3f6331f0bc6 100644 --- a/spec/controllers/users_controller_spec.rb +++ b/spec/controllers/users_controller_spec.rb @@ -254,6 +254,14 @@ describe UsersController do expect(session["password-#{token}"]).to be_blank end + it "redirects to the wizard if you're the first admin" do + user = Fabricate(:admin, auth_token: SecureRandom.hex(16), auth_token_updated_at: Time.now) + token = user.email_tokens.create(email: user.email).token + get :password_reset, token: token + put :password_reset, token: token, password: 'hg9ow8yhg98oadminlonger' + expect(response).to be_redirect + end + it "doesn't invalidate the token when loading the page" do user = Fabricate(:user, auth_token: SecureRandom.hex(16)) email_token = user.email_tokens.create(email: user.email)