mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
FEATURE: Enforce two-factor authentication. (#6348)
This commit is contained in:
parent
e711d3cb3a
commit
d352baa1a2
@ -39,6 +39,26 @@ export default Ember.Controller.extend({
|
|||||||
return findAll().length > 0;
|
return findAll().length > 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@computed(
|
||||||
|
"siteSettings.enforce_second_factor",
|
||||||
|
"currentUser",
|
||||||
|
"currentUser.second_factor_enabled",
|
||||||
|
"currentUser.staff"
|
||||||
|
)
|
||||||
|
showEnforcedNotice(
|
||||||
|
enforce_second_factor,
|
||||||
|
user,
|
||||||
|
second_factor_enabled,
|
||||||
|
staff
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
user &&
|
||||||
|
!second_factor_enabled &&
|
||||||
|
(enforce_second_factor === "all" ||
|
||||||
|
(enforce_second_factor === "staff" && staff))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
toggleSecondFactor(enable) {
|
toggleSecondFactor(enable) {
|
||||||
if (!this.get("secondFactorToken")) return;
|
if (!this.get("secondFactorToken")) return;
|
||||||
this.set("loading", true);
|
this.set("loading", true);
|
||||||
|
@ -13,5 +13,28 @@ export default RestrictedUserRoute.extend({
|
|||||||
|
|
||||||
setupController(controller, model) {
|
setupController(controller, model) {
|
||||||
controller.setProperties({ model, newUsername: model.get("username") });
|
controller.setProperties({ model, newUsername: model.get("username") });
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
willTransition(transition) {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
const controller = this.controllerFor("preferences/second-factor");
|
||||||
|
const user = controller.get("currentUser");
|
||||||
|
const settings = controller.get("siteSettings");
|
||||||
|
|
||||||
|
if (
|
||||||
|
transition.targetName === "preferences.second-factor" ||
|
||||||
|
!user ||
|
||||||
|
user.second_factor_enabled ||
|
||||||
|
(settings.enforce_second_factor === "staff" && !user.staff) ||
|
||||||
|
settings.enforce_second_factor === "no"
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
transition.abort();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,14 @@
|
|||||||
<section class='user-preferences'>
|
<section class='user-preferences'>
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
|
|
||||||
|
{{#if showEnforcedNotice}}
|
||||||
|
<div class="control-group">
|
||||||
|
<div class="controls">
|
||||||
|
<div class='alert alert-error'>{{i18n 'user.second_factor.enforced_notice'}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
{{#if errorMessage}}
|
{{#if errorMessage}}
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
|
@ -685,10 +685,9 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def redirect_to_login_if_required
|
def redirect_to_login_if_required
|
||||||
return if current_user || (request.format.json? && is_api?)
|
return if request.format.json? && is_api?
|
||||||
|
|
||||||
if SiteSetting.login_required?
|
|
||||||
|
|
||||||
|
if !current_user && SiteSetting.login_required?
|
||||||
flash.keep
|
flash.keep
|
||||||
dont_cache_page
|
dont_cache_page
|
||||||
|
|
||||||
@ -704,6 +703,18 @@ class ApplicationController < ActionController::Base
|
|||||||
redirect_to path("/login")
|
redirect_to path("/login")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if current_user &&
|
||||||
|
!current_user.totp_enabled? &&
|
||||||
|
!request.format.json? &&
|
||||||
|
!is_api? &&
|
||||||
|
((SiteSetting.enforce_second_factor == 'staff' && current_user.staff?) ||
|
||||||
|
SiteSetting.enforce_second_factor == 'all')
|
||||||
|
redirect_path = "#{GlobalSetting.relative_url_root}/u/#{current_user.username}/preferences/second-factor"
|
||||||
|
if !request.fullpath.start_with?(redirect_path)
|
||||||
|
redirect_to path(redirect_path)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def block_if_readonly_mode
|
def block_if_readonly_mode
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
class ExtraLocalesController < ApplicationController
|
class ExtraLocalesController < ApplicationController
|
||||||
|
|
||||||
layout :false
|
layout :false
|
||||||
skip_before_action :check_xhr, :preload_json
|
skip_before_action :check_xhr, :preload_json, :redirect_to_login_if_required
|
||||||
|
|
||||||
def show
|
def show
|
||||||
bundle = params[:bundle]
|
bundle = params[:bundle]
|
||||||
|
@ -810,6 +810,7 @@ en:
|
|||||||
Two factor authentication adds extra security to your account by requiring a one-time token in addition to your password. Tokens can be generated on <a href="https://www.google.com/search?q=authenticator+apps+for+android" target='_blank'>Android</a> and <a href="https://www.google.com/search?q=authenticator+apps+for+ios">iOS</a> devices.
|
Two factor authentication adds extra security to your account by requiring a one-time token in addition to your password. Tokens can be generated on <a href="https://www.google.com/search?q=authenticator+apps+for+android" target='_blank'>Android</a> and <a href="https://www.google.com/search?q=authenticator+apps+for+ios">iOS</a> devices.
|
||||||
oauth_enabled_warning: "Please note that social logins will be disabled once two factor authentication has been enabled on your account."
|
oauth_enabled_warning: "Please note that social logins will be disabled once two factor authentication has been enabled on your account."
|
||||||
use: "<a href>Use Authenticator app</a>"
|
use: "<a href>Use Authenticator app</a>"
|
||||||
|
enforced_notice: "You are required to enable two factor authentication before accessing this site."
|
||||||
|
|
||||||
change_about:
|
change_about:
|
||||||
title: "Change About Me"
|
title: "Change About Me"
|
||||||
|
@ -1339,6 +1339,7 @@ en:
|
|||||||
notification_email: "The from: email address used when sending all essential system emails. The domain specified here must have SPF, DKIM and reverse PTR records set correctly for email to arrive."
|
notification_email: "The from: email address used when sending all essential system emails. The domain specified here must have SPF, DKIM and reverse PTR records set correctly for email to arrive."
|
||||||
email_custom_headers: "A pipe-delimited list of custom email headers"
|
email_custom_headers: "A pipe-delimited list of custom email headers"
|
||||||
email_subject: "Customizable subject format for standard emails. See <a href='https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801' target='_blank'>https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801</a>"
|
email_subject: "Customizable subject format for standard emails. See <a href='https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801' target='_blank'>https://meta.discourse.org/t/customize-subject-format-for-standard-emails/20801</a>"
|
||||||
|
enforce_second_factor: "Forces users to enable second factor authentication. Select 'all' to enforce it to all users. Select 'staff' to enforce it to staff users only."
|
||||||
force_https: "Force your site to use HTTPS only. WARNING: do NOT enable this until you verify HTTPS is fully set up and working absolutely everywhere! Did you check your CDN, all social logins, and any external logos / dependencies to make sure they are all HTTPS compatible, too?"
|
force_https: "Force your site to use HTTPS only. WARNING: do NOT enable this until you verify HTTPS is fully set up and working absolutely everywhere! Did you check your CDN, all social logins, and any external logos / dependencies to make sure they are all HTTPS compatible, too?"
|
||||||
same_site_cookies: "Use same site cookies, they eliminate all vectors Cross Site Request Forgery on supported browsers (Lax or Strict). Warning: Strict will only work on sites that force login and use SSO."
|
same_site_cookies: "Use same site cookies, they eliminate all vectors Cross Site Request Forgery on supported browsers (Lax or Strict). Warning: Strict will only work on sites that force login and use SSO."
|
||||||
summary_score_threshold: "The minimum score required for a post to be included in 'Summarize This Topic'"
|
summary_score_threshold: "The minimum score required for a post to be included in 'Summarize This Topic'"
|
||||||
|
@ -1220,6 +1220,14 @@ trust:
|
|||||||
default: true
|
default: true
|
||||||
|
|
||||||
security:
|
security:
|
||||||
|
enforce_second_factor:
|
||||||
|
client: true
|
||||||
|
type: enum
|
||||||
|
default: 'no'
|
||||||
|
choices:
|
||||||
|
- 'no'
|
||||||
|
- 'staff'
|
||||||
|
- 'all'
|
||||||
force_https:
|
force_https:
|
||||||
default: false
|
default: false
|
||||||
shadowed_by_global: true
|
shadowed_by_global: true
|
||||||
|
@ -20,6 +20,63 @@ RSpec.describe ApplicationController do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#redirect_to_second_factor_if_required' do
|
||||||
|
let(:admin) { Fabricate(:admin) }
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
admin # to skip welcome wizard at home page `/`
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should redirect admins when enforce_second_factor is 'all'" do
|
||||||
|
SiteSetting.enforce_second_factor = "all"
|
||||||
|
sign_in(admin)
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should redirect users when enforce_second_factor is 'all'" do
|
||||||
|
SiteSetting.enforce_second_factor = "all"
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
expect(response).to redirect_to("/u/#{user.username}/preferences/second-factor")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should redirect admins when enforce_second_factor is 'staff'" do
|
||||||
|
SiteSetting.enforce_second_factor = "staff"
|
||||||
|
sign_in(admin)
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
expect(response).to redirect_to("/u/#{admin.username}/preferences/second-factor")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not redirect users when enforce_second_factor is 'staff'" do
|
||||||
|
SiteSetting.enforce_second_factor = "staff"
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not redirect admins when turned off" do
|
||||||
|
SiteSetting.enforce_second_factor = "no"
|
||||||
|
sign_in(admin)
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not redirect users when turned off" do
|
||||||
|
SiteSetting.enforce_second_factor = "no"
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
get "/"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'invalid request params' do
|
describe 'invalid request params' do
|
||||||
before do
|
before do
|
||||||
@old_logger = Rails.logger
|
@old_logger = Rails.logger
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
import { acceptance, replaceCurrentUser } from "helpers/qunit-helpers";
|
||||||
|
|
||||||
|
acceptance("Enforce Second Factor", {
|
||||||
|
loggedIn: true
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("as an admin", async assert => {
|
||||||
|
await visit("/u/eviltrout/preferences/second-factor");
|
||||||
|
Discourse.SiteSettings.enforce_second_factor = "staff";
|
||||||
|
|
||||||
|
await visit("/u/eviltrout/summary");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".control-label").text(),
|
||||||
|
"Password",
|
||||||
|
"it will not transition from second-factor preferences"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("#toggle-hamburger-menu");
|
||||||
|
await click("a.admin-link");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".control-label").text(),
|
||||||
|
"Password",
|
||||||
|
"it stays at second-factor preferences"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("as a user", async assert => {
|
||||||
|
replaceCurrentUser({ staff: false, admin: false });
|
||||||
|
|
||||||
|
await visit("/u/eviltrout/preferences/second-factor");
|
||||||
|
Discourse.SiteSettings.enforce_second_factor = "all";
|
||||||
|
|
||||||
|
await visit("/u/eviltrout/summary");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".control-label").text(),
|
||||||
|
"Password",
|
||||||
|
"it will not transition from second-factor preferences"
|
||||||
|
);
|
||||||
|
|
||||||
|
await click("#toggle-hamburger-menu");
|
||||||
|
await click("a.about-link");
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
find(".control-label").text(),
|
||||||
|
"Password",
|
||||||
|
"it stays at second-factor preferences"
|
||||||
|
);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user