mirror of
https://github.com/mastodon/mastodon.git
synced 2025-09-04 00:43:41 +00:00
Merge 733587c4bf
into 40242fafee
This commit is contained in:
commit
27efe7dbfd
|
@ -109,3 +109,6 @@ FETCH_REPLIES_MAX_SINGLE=500
|
|||
|
||||
# Max number of replies Collection pages to fetch - total
|
||||
FETCH_REPLIES_MAX_PAGES=500
|
||||
|
||||
# MFA Required for Users
|
||||
REQUIRE_MULTI_FACTOR_AUTH=false
|
|
@ -14,6 +14,7 @@ class ApplicationController < ActionController::Base
|
|||
include DatabaseHelper
|
||||
include AuthorizedFetchHelper
|
||||
include SelfDestructHelper
|
||||
include MfaForceConcern
|
||||
|
||||
helper_method :current_account
|
||||
helper_method :current_session
|
||||
|
|
|
@ -18,6 +18,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
skip_before_action :check_self_destruct!, only: [:edit, :update]
|
||||
skip_before_action :require_functional!, only: [:edit, :update]
|
||||
skip_before_action :check_mfa_requirement, only: [:edit, :update]
|
||||
|
||||
def new
|
||||
super(&:build_invite_request)
|
||||
|
@ -100,12 +101,13 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_invite
|
||||
@invite = begin
|
||||
invite = Invite.find_by(code: invite_code) if invite_code.present?
|
||||
invite if invite&.valid_for_use?
|
||||
if invite_code.present?
|
||||
Invite.find_by(code: invite_code)
|
||||
elsif params[:invite_code].present?
|
||||
Invite.find_by(code: params[:invite_code])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -132,15 +134,13 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
def require_rules_acceptance!
|
||||
return if @rules.empty? || (session[:accept_token].present? && params[:accept] == session[:accept_token])
|
||||
|
||||
@accept_token = session[:accept_token] = SecureRandom.hex
|
||||
@invite_code = invite_code
|
||||
|
||||
set_locale { render :rules }
|
||||
session[:accept_token] = SecureRandom.hex(16)
|
||||
redirect_to new_user_registration_path(accept: session[:accept_token])
|
||||
end
|
||||
|
||||
def is_flashing_format? # rubocop:disable Naming/PredicatePrefix
|
||||
if params[:action] == 'create'
|
||||
false # Disable flash messages for sign-up
|
||||
false
|
||||
else
|
||||
super
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
skip_before_action :require_no_authentication, only: [:create]
|
||||
skip_before_action :require_functional!
|
||||
skip_before_action :update_user_sign_in
|
||||
skip_before_action :check_mfa_requirement, only: [:destroy]
|
||||
|
||||
around_action :preserve_stored_location, only: :destroy, if: :continue_after?
|
||||
|
||||
|
@ -199,12 +200,8 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
|
||||
def respond_to_on_destroy
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: {
|
||||
redirect_to: after_sign_out_path_for(resource_name),
|
||||
}, status: 200
|
||||
end
|
||||
format.all { super }
|
||||
format.any(*navigational_formats) { redirect_to after_sign_out_path_for(:user) }
|
||||
format.all { head 204 }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ class Auth::SetupController < ApplicationController
|
|||
before_action :set_user
|
||||
|
||||
skip_before_action :require_functional!
|
||||
skip_before_action :check_mfa_requirement
|
||||
|
||||
def show; end
|
||||
|
||||
|
|
27
app/controllers/concerns/mfa_force_concern.rb
Normal file
27
app/controllers/concerns/mfa_force_concern.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MfaForceConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
prepend_before_action :check_mfa_requirement, if: :user_signed_in?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_mfa_requirement
|
||||
return unless mfa_force_enabled?
|
||||
return if current_user.otp_enabled?
|
||||
|
||||
flash[:alert] = I18n.t('require_multi_factor_auth.required_message')
|
||||
redirect_to settings_otp_authentication_path
|
||||
end
|
||||
|
||||
def mfa_force_enabled?
|
||||
mfa_config[:force_enabled]
|
||||
end
|
||||
|
||||
def mfa_config
|
||||
@mfa_config ||= Rails.application.config_for(:mfa)
|
||||
end
|
||||
end
|
|
@ -6,6 +6,7 @@ module Settings
|
|||
include ChallengableConcern
|
||||
|
||||
skip_before_action :require_functional!
|
||||
skip_before_action :check_mfa_requirement
|
||||
|
||||
before_action :require_challenge!
|
||||
before_action :ensure_otp_secret
|
||||
|
|
|
@ -6,6 +6,7 @@ module Settings
|
|||
include ChallengableConcern
|
||||
|
||||
skip_before_action :require_functional!
|
||||
skip_before_action :check_mfa_requirement
|
||||
|
||||
before_action :verify_otp_not_enabled, only: [:show]
|
||||
before_action :require_challenge!, only: [:create]
|
||||
|
|
|
@ -6,6 +6,7 @@ module Settings
|
|||
|
||||
skip_before_action :check_self_destruct!
|
||||
skip_before_action :require_functional!
|
||||
skip_before_action :check_mfa_requirement
|
||||
|
||||
before_action :require_challenge!, only: :disable
|
||||
before_action :require_otp_enabled
|
||||
|
|
|
@ -2148,3 +2148,7 @@ en:
|
|||
not_supported: This browser doesn't support security keys
|
||||
otp_required: To use security keys please enable two-factor authentication first.
|
||||
registered_on: Registered on %{date}
|
||||
|
||||
require_multi_factor_auth:
|
||||
required_message: The administrator of this site has configured as mandatory that users enable two-factor authentication due to security policies. Please configure your two-factor authentication to continue using the platform.
|
||||
security_policy: Security Policy Requirement
|
||||
|
|
|
@ -2146,4 +2146,4 @@ es:
|
|||
not_enabled: Aún no has activado WebAuthn
|
||||
not_supported: Este navegador no soporta claves de seguridad
|
||||
otp_required: Para usar claves de seguridad, por favor habilite primero la autenticación de doble factor.
|
||||
registered_on: Registrado el %{date}
|
||||
registered_on: Registrado el %{date}
|
15
config/mfa.yml
Normal file
15
config/mfa.yml
Normal file
|
@ -0,0 +1,15 @@
|
|||
# Multi-Factor Authentication configuration
|
||||
default: &default
|
||||
force_enabled: false
|
||||
|
||||
development:
|
||||
<<: *default
|
||||
force_enabled: <%= ENV.fetch('REQUIRE_MULTI_FACTOR_AUTH', 'false') == 'true' %>
|
||||
|
||||
test:
|
||||
<<: *default
|
||||
force_enabled: false
|
||||
|
||||
production:
|
||||
<<: *default
|
||||
force_enabled: <%= ENV.fetch('REQUIRE_MULTI_FACTOR_AUTH', 'false') == 'true' %>
|
111
spec/controllers/concerns/mfa_force_concern_spec.rb
Normal file
111
spec/controllers/concerns/mfa_force_concern_spec.rb
Normal file
|
@ -0,0 +1,111 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe MfaForceConcern do
|
||||
controller(ApplicationController) do
|
||||
def index
|
||||
render plain: 'OK'
|
||||
end
|
||||
end
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before do
|
||||
routes.draw { get 'index' => 'anonymous#index' }
|
||||
end
|
||||
|
||||
describe 'MFA force functionality' do
|
||||
context 'when REQUIRE_MULTI_FACTOR_AUTH is enabled' do
|
||||
before do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'true') do
|
||||
sign_in user, scope: :user
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user has MFA enabled' do
|
||||
before do
|
||||
user.update(otp_required_for_login: true)
|
||||
end
|
||||
|
||||
it 'allows access to normal pages' do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'true') do
|
||||
get :index
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user does not have MFA enabled' do
|
||||
before do
|
||||
user.update(otp_required_for_login: false)
|
||||
end
|
||||
|
||||
it 'redirects to MFA setup page' do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'true') do
|
||||
get :index
|
||||
expect(response).to redirect_to(settings_otp_authentication_path)
|
||||
end
|
||||
end
|
||||
|
||||
it 'shows the required message' do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'true') do
|
||||
get :index
|
||||
expect(flash[:alert]).to eq(I18n.t('require_multi_factor_auth.required_message'))
|
||||
end
|
||||
end
|
||||
|
||||
context 'when accessing MFA setup pages' do
|
||||
it 'allows access to OTP authentication page' do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'true') do
|
||||
allow(controller.request).to receive(:path).and_return('/settings/otp_authentication')
|
||||
get :index
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows access to MFA confirmation page' do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'true') do
|
||||
allow(controller.request).to receive(:path).and_return('/settings/two_factor_authentication/confirmation')
|
||||
get :index
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows access to logout' do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'true') do
|
||||
allow(controller.request).to receive(:path).and_return('/auth/sign_out')
|
||||
get :index
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when REQUIRE_MULTI_FACTOR_AUTH is disabled' do
|
||||
before do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'false') do
|
||||
sign_in user, scope: :user
|
||||
user.update(otp_required_for_login: false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows access to normal pages' do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'false') do
|
||||
get :index
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not signed in' do
|
||||
it 'allows access to normal pages' do
|
||||
ClimateControl.modify(REQUIRE_MULTI_FACTOR_AUTH: 'true') do
|
||||
get :index
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user