mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 08:49:06 +08:00
FEATURE: Configure Admin Account
Adds a "Step 0" to the wizard if the site has no admin accounts where the user is prompted to finish setting up their admin account from the list of acceptable email addresses. Once confirmed, the wizard begins.
This commit is contained in:
parent
674264726d
commit
c03d25f170
|
@ -11,7 +11,6 @@
|
|||
<p class='wizard-step-description'>{{{step.description}}}</p>
|
||||
{{/if}}
|
||||
|
||||
|
||||
{{#wizard-step-form step=step}}
|
||||
{{#each step.fields as |field|}}
|
||||
{{wizard-field field=field step=step wizard=wizard}}
|
||||
|
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
53
app/controllers/finish_installation_controller.rb
Normal file
53
app/controllers/finish_installation_controller.rb
Normal file
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
3
app/views/finish_installation/confirm_email.html.erb
Normal file
3
app/views/finish_installation/confirm_email.html.erb
Normal file
|
@ -0,0 +1,3 @@
|
|||
<h1><%= t 'first_installation.confirm_email.title' %></h1>
|
||||
|
||||
<%= raw(t 'first_installation.confirm_email.message', email: @email) %>
|
16
app/views/finish_installation/index.html.erb
Normal file
16
app/views/finish_installation/index.html.erb
Normal file
|
@ -0,0 +1,16 @@
|
|||
<h1><%= t 'first_installation.congratulations' %></h1>
|
||||
|
||||
<div class='row'>
|
||||
<%= image_tag "/images/wizard/tada.svg", class: "tada" %>
|
||||
</div>
|
||||
|
||||
<div class='row help-text'>
|
||||
<%= t 'first_installation.register.help' %>
|
||||
</div>
|
||||
|
||||
<div class='row'>
|
||||
<%= link_to(finish_installation_register_path, class: 'wizard-btn primary') do %>
|
||||
<i class='fa fa-user'></i>
|
||||
<%= t 'first_installation.register.button' %>
|
||||
<% end %>
|
||||
</div>
|
57
app/views/finish_installation/register.html.erb
Normal file
57
app/views/finish_installation/register.html.erb
Normal file
|
@ -0,0 +1,57 @@
|
|||
<h1><%= t 'first_installation.register.title' %></h1>
|
||||
|
||||
<%- if @allowed_emails.present? %>
|
||||
<%= form_tag(finish_installation_register_path) do %>
|
||||
|
||||
<div class='wizard-field text-field'>
|
||||
<label for="email">
|
||||
<span class="label-value"><%= t 'js.user.email.title' %></span>
|
||||
</label>
|
||||
|
||||
<div class='input-area'>
|
||||
<%= select_tag :email, options_for_select(@allowed_emails, selected: params[:email]), class: 'combobox' %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class='wizard-field text-field <% if @user.errors[:username].present? %>invalid<% end %>'>
|
||||
<label for="username">
|
||||
<span class="label-value"><%= t 'js.user.username.title' %></span>
|
||||
</label>
|
||||
|
||||
<div class='field-description'><%= t 'js.user.username.instructions' %></div>
|
||||
|
||||
<div class='input-area'>
|
||||
<%= text_field_tag(:username, params[:username]) %>
|
||||
</div>
|
||||
<%- @user.errors[:username].each do |e| %>
|
||||
<div class='field-error-description'><%= e.to_s %></div>
|
||||
<%- end %>
|
||||
</div>
|
||||
|
||||
<div class='wizard-field text-field <% if @user.errors[:username].present? %>invalid<% end %>'>
|
||||
<label for="password">
|
||||
<span class="label-value"><%= t 'js.user.password.title' %></span>
|
||||
</label>
|
||||
|
||||
<div class='field-description'><%= t 'js.user.password.instructions', count: SiteSetting.min_admin_password_length %></div>
|
||||
|
||||
<div class='input-area'>
|
||||
<%= password_field_tag(:password, params[:password]) %>
|
||||
</div>
|
||||
<% @user.errors[:password].each do |e| %>
|
||||
<div class='field-error-description'><%= e.to_s %></div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= submit_tag(t('first_installation.register.button'), class: 'wizard-btn primary') %>
|
||||
|
||||
<%- end %>
|
||||
<%- else -%>
|
||||
<p><%= raw(t 'first_installation.register.no_emails') %></p>
|
||||
<%- end %>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
$('select').select2({ width: '400px' });
|
||||
})();
|
||||
</script>
|
23
app/views/layouts/finish_installation.html.erb
Normal file
23
app/views/layouts/finish_installation.html.erb
Normal file
|
@ -0,0 +1,23 @@
|
|||
<html>
|
||||
<head>
|
||||
<%= stylesheet_link_tag 'wizard' %>
|
||||
<%= render partial: "common/special_font_face" %>
|
||||
<%= script 'jquery_include' %>
|
||||
<%= script 'wizard-vendor' %>
|
||||
<%= render partial: "layouts/head" %>
|
||||
<title><%= t 'wizard.title' %></title>
|
||||
</head>
|
||||
|
||||
<body class='wizard'>
|
||||
<div id='wizard-main'>
|
||||
<div class='wizard-column'>
|
||||
<div class='wizard-column-contents finish-installation'>
|
||||
<%= yield %>
|
||||
</div>
|
||||
<div class='wizard-footer'>
|
||||
<div class='discourse-logo'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -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
|
||||
|
|
|
@ -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 <a href='https://meta.discourse.org/t/how-to-create-an-administrator-account-after-install/14046'>may be difficult</a>."
|
||||
confirm_email:
|
||||
title: "Confirm your Email"
|
||||
message: "<p>We sent an activation mail to <b>%{email}</b>. Please follow the instructions in the email to activate your account.</p><p>If it doesn't arrive, ensure you have set up email correctly for your Discourse and check your spam folder.</p>"
|
||||
|
||||
wizard:
|
||||
title: "Discourse Setup"
|
||||
step:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
1
public/images/wizard/tada.svg
Normal file
1
public/images/wizard/tada.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 6.7 KiB |
85
spec/controllers/finish_installation_controller_spec.rb
Normal file
85
spec/controllers/finish_installation_controller_spec.rb
Normal file
|
@ -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
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue
Block a user