Compare commits

...

35 Commits

Author SHA1 Message Date
FredysFonseca
27efe7dbfd
Merge 733587c4bf into 40242fafee 2025-08-31 21:57:13 +00:00
FredysFonseca
733587c4bf
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-31 17:57:09 -04:00
FredysFonseca
e259a67151
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-27 18:54:33 -04:00
FredysFonseca
a5b2b564ab
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-24 20:52:39 -04:00
FredysFonseca
6d82a3d2a5
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-22 02:02:12 -04:00
FredysFonseca
879fd3406e
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-20 19:43:38 -04:00
FredysFonseca
50298a1ce1
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-17 17:44:13 -04:00
FredysFonseca
d114645e60
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-14 20:56:31 -04:00
FredysFonseca
8bd9d60cdf
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-12 22:02:18 -04:00
FredysFonseca
cf1db1a025
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-11 19:17:13 -04:00
FredysFonseca
4ce99fcf55
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-08 01:23:32 -04:00
FredysFonseca
1f6f3bf01a
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-06 23:03:46 -04:00
FredysFonseca
7af438159a
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-05 21:53:02 -04:00
FredysFonseca
7e1eeff268
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-04 18:51:13 -04:00
FredysFonseca
2b98d29942
Merge branch 'main' into feature/require-mfa-by-admin 2025-08-03 12:23:16 -04:00
FredysFonseca
6a40447759
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-30 13:27:35 -04:00
FredysFonseca
63d0efa0c2
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-29 18:13:55 -04:00
FredysFonseca
bdf102df4e
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-28 16:02:11 -04:00
FredysFonseca
e5f4362b8f
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-27 17:49:24 -04:00
FredysFonseca
26081d66fd
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-25 15:57:30 -04:00
Fredys Fonseca
a8f5e3fa62 feature/require-mfa-by-admin - Using Skip_before_action 2025-07-23 13:05:05 -05:00
FredysFonseca
f5c14b753a
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-23 12:48:44 -04:00
Fredys Fonseca
1073956fbc feature/require-mfa-by-admin - Refact Opt-Out 2025-07-23 00:50:43 -05:00
Fredys Fonseca
673d875a95 feature/require-mfa-by-admin - Change to config for 2025-07-23 00:50:42 -05:00
Fredys Fonseca
7a957b1f49 feature/require-mfa-by-admin - Update Flash 2025-07-23 00:50:42 -05:00
Fredys Fonseca
6fda7a9f56 feature/require-mfa-by-admin - Using ClimateControl 2025-07-23 00:50:42 -05:00
Fredys Fonseca
1c52aa76eb feature/require-mfa-by-admin - move docs 2025-07-23 00:50:42 -05:00
FredysFonseca
df97c75d34
Merge branch 'mastodon:main' into feature/require-mfa-by-admin 2025-07-23 01:49:50 -04:00
FredysFonseca
659c695a5a
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-20 17:31:47 -04:00
FredysFonseca
86e38dc4f6
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-18 14:35:48 -04:00
FredysFonseca
7b1f26eea2
Merge branch 'main' into feature/require-mfa-by-admin 2025-07-17 18:07:29 -04:00
Fredys Fonseca
6cc6c36c44 Add admin MFA enforcement feature 2025-07-17 06:38:58 +00:00
Fredys Fonseca
16b19f48cc Add admin MFA enforcement feature 2025-07-17 06:36:28 +00:00
Fredys Fonseca
4e1d61ecd0 Add admin MFA enforcement feature 2025-07-17 05:54:56 +00:00
Fredys Fonseca
218a9e70c5 Add admin MFA enforcement feature 2025-07-17 05:50:45 +00:00
13 changed files with 178 additions and 16 deletions

View File

@ -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

View File

@ -14,6 +14,7 @@ class ApplicationController < ActionController::Base
include DatabaseHelper
include AuthorizedFetchHelper
include SelfDestructHelper
include MfaForceConcern
helper_method :current_account
helper_method :current_session

View File

@ -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

View File

@ -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

View File

@ -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

View 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

View File

@ -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

View File

@ -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]

View File

@ -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

View File

@ -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

View File

@ -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
View 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' %>

View 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