Compare commits

...

38 Commits

Author SHA1 Message Date
FredysFonseca
d4d5ae215b
Merge 733587c4bf into 14cb5ff881 2025-09-03 20:05:42 +00:00
Claire
14cb5ff881
Add compatibility hack for GoToSocial interaction policies (#36004)
Some checks are pending
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
2025-09-03 19:35:43 +00:00
diondiondion
bc952ebde9
Add new plain (text-only) button variant (#36002)
Some checks failed
Check i18n / check-i18n (push) Waiting to run
CodeQL / Analyze (javascript) (push) Waiting to run
CodeQL / Analyze (ruby) (push) Waiting to run
Check formatting / lint (push) Waiting to run
Ruby Linting / lint (push) Waiting to run
Historical data migration test / test (14-alpine) (push) Waiting to run
Historical data migration test / test (15-alpine) (push) Waiting to run
Historical data migration test / test (16-alpine) (push) Waiting to run
Historical data migration test / test (17-alpine) (push) Waiting to run
Ruby Testing / build (production) (push) Waiting to run
Ruby Testing / build (test) (push) Waiting to run
Ruby Testing / test (.ruby-version) (push) Blocked by required conditions
Ruby Testing / test (3.2) (push) Blocked by required conditions
Ruby Testing / test (3.3) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (.ruby-version) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.2) (push) Blocked by required conditions
Ruby Testing / ImageMagick tests (3.3) (push) Blocked by required conditions
Ruby Testing / End to End testing (.ruby-version) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.2) (push) Blocked by required conditions
Ruby Testing / End to End testing (3.3) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, docker.elastic.co/elasticsearch/elasticsearch:8.10.2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (.ruby-version, opensearchproject/opensearch:2) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.2, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Ruby Testing / Elastic Search integration testing (3.3, docker.elastic.co/elasticsearch/elasticsearch:7.17.13) (push) Blocked by required conditions
Chromatic / Run Chromatic (push) Has been cancelled
CSS Linting / lint (push) Has been cancelled
JavaScript Linting / lint (push) Has been cancelled
JavaScript Testing / test (push) Has been cancelled
2025-09-03 12:34:29 +00:00
renovate[bot]
c1542643f5
fix(deps): update dependency sass to v1.92.0 (#36001)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-09-03 12:23:00 +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
18 changed files with 245 additions and 19 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

@ -8,6 +8,7 @@ const meta = {
component: Button,
args: {
secondary: false,
plain: false,
compact: false,
dangerous: false,
disabled: false,
@ -57,6 +58,14 @@ export const Secondary: Story = {
play: buttonTest,
};
export const Plain: Story = {
args: {
plain: true,
children: 'Plain button',
},
play: buttonTest,
};
export const Compact: Story = {
args: {
compact: true,
@ -101,6 +110,14 @@ export const SecondaryDisabled: Story = {
play: disabledButtonTest,
};
export const PlainDisabled: Story = {
args: {
...Plain.args,
disabled: true,
},
play: disabledButtonTest,
};
const loadingButtonTest: Story['play'] = async ({
args,
canvas,

View File

@ -9,6 +9,7 @@ interface BaseProps
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
block?: boolean;
secondary?: boolean;
plain?: boolean;
compact?: boolean;
dangerous?: boolean;
loading?: boolean;
@ -35,6 +36,7 @@ export const Button: React.FC<Props> = ({
disabled,
block,
secondary,
plain,
compact,
dangerous,
loading,
@ -62,6 +64,7 @@ export const Button: React.FC<Props> = ({
<button
className={classNames('button', className, {
'button-secondary': secondary,
'button--plain': plain,
'button--compact': compact,
'button--block': block,
'button--dangerous': dangerous,

View File

@ -201,6 +201,41 @@
}
}
&.button--plain {
color: $highlight-text-color;
background: transparent;
padding: 6px;
// The button has no outline, so we use negative margin to
// visually align its label with its surroundings while maintaining
// a generous click target
margin-inline: -6px;
border: 1px solid transparent;
&:active,
&:focus,
&:hover {
border-color: transparent;
color: lighten($highlight-text-color, 4%);
background-color: transparent;
text-decoration: none;
}
&:disabled,
&.disabled {
opacity: 0.7;
border-color: transparent;
color: $ui-button-disabled-color;
&:active,
&:focus,
&:hover {
border-color: transparent;
color: $ui-button-disabled-color;
}
}
}
&.button-tertiary {
background: transparent;
padding: 6px 17px;

View File

@ -232,6 +232,15 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
canQuote: {
automaticApproval: approved_uris,
},
canReply: {
always: 'https://www.w3.org/ns/activitystreams#Public',
},
canLike: {
always: 'https://www.w3.org/ns/activitystreams#Public',
},
canAnnounce: {
always: 'https://www.w3.org/ns/activitystreams#Public',
},
}
end

View File

@ -2151,3 +2151,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

@ -2149,4 +2149,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

View File

@ -11957,8 +11957,8 @@ __metadata:
linkType: hard
"sass@npm:^1.62.1":
version: 1.91.0
resolution: "sass@npm:1.91.0"
version: 1.92.0
resolution: "sass@npm:1.92.0"
dependencies:
"@parcel/watcher": "npm:^2.4.1"
chokidar: "npm:^4.0.0"
@ -11969,7 +11969,7 @@ __metadata:
optional: true
bin:
sass: sass.js
checksum: 10c0/5be1c98f7a618cb5f90b62f63d2aa0f78f9bf369c93ec7cd9880752a26b0ead19aa63cc341e8a26ce6c74d080baa5705f1685dff52fe6a3f28a7828ae50182b6
checksum: 10c0/bdff9fa6988620e2a81962efdd016e3894d19934cfadc105cf41db767f59dd47afd8ff32840e612ef700cb67e19d9e83c108f1724eb8f0bef56c4877dbe6f14d
languageName: node
linkType: hard