Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - app/controllers/application_controller.rb - app/controllers/auth/confirmations_controller.rb - app/controllers/auth/sessions_controller.rb - app/controllers/settings/deletes_controller.rb - app/controllers/settings/two_factor_authentication/recovery_codes_controller.rb
This commit is contained in:
@ -8,7 +8,7 @@ class AboutController < ApplicationController
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in
|
||||
|
||||
skip_before_action :check_user_permissions, only: [:more, :terms]
|
||||
skip_before_action :require_functional!, only: [:more, :terms]
|
||||
|
||||
def show; end
|
||||
|
||||
|
@ -42,7 +42,7 @@ class AccountsController < ApplicationController
|
||||
|
||||
format.json do
|
||||
expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
|
||||
render json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
|
||||
render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter, fields: restrict_fields_to
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -11,7 +11,7 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
|
||||
|
||||
def show
|
||||
expires_in 3.minutes, public: public_fetch_mode?
|
||||
render json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
|
||||
render_with_cache json: collection_presenter, content_type: 'application/activity+json', serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, skip_activities: true
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -7,7 +7,7 @@ class Api::BaseController < ApplicationController
|
||||
include RateLimitHeaders
|
||||
|
||||
skip_before_action :store_current_location
|
||||
skip_before_action :check_user_permissions
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :set_cache_headers
|
||||
|
||||
|
@ -6,8 +6,7 @@ class Api::V1::CustomEmojisController < Api::BaseController
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
def index
|
||||
render_cached_json('api:v1:custom_emojis', expires_in: 1.minute) do
|
||||
ActiveModelSerializers::SerializableResource.new(CustomEmoji.local.where(disabled: false).includes(:category), each_serializer: REST::CustomEmojiSerializer)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.local.where(disabled: false).includes(:category) }
|
||||
end
|
||||
end
|
||||
|
@ -7,7 +7,8 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
render_cached_json('api:v1:instances:activity:show', expires_in: 1.day) { activity }
|
||||
expires_in 1.day, public: true
|
||||
render_with_cache json: :activity, expires_in: 1.day
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -7,7 +7,8 @@ class Api::V1::Instances::PeersController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render_cached_json('api:v1:instances:peers:index', expires_in: 1.day) { Account.remote.domains }
|
||||
expires_in 1.day, public: true
|
||||
render_with_cache(expires_in: 1.day) { Account.remote.domains }
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -5,8 +5,7 @@ class Api::V1::InstancesController < Api::BaseController
|
||||
skip_before_action :set_cache_headers
|
||||
|
||||
def show
|
||||
render_cached_json('api:v1:instances', expires_in: 5.minutes) do
|
||||
ActiveModelSerializers::SerializableResource.new({}, serializer: REST::InstanceSerializer)
|
||||
end
|
||||
expires_in 3.minutes, public: true
|
||||
render_with_cache json: {}, serializer: REST::InstanceSerializer
|
||||
end
|
||||
end
|
||||
|
@ -10,6 +10,7 @@ class ApplicationController < ActionController::Base
|
||||
include Localized
|
||||
include UserTrackingConcern
|
||||
include SessionTrackingConcern
|
||||
include CacheConcern
|
||||
|
||||
helper_method :current_account
|
||||
helper_method :current_session
|
||||
@ -25,7 +26,7 @@ class ApplicationController < ActionController::Base
|
||||
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
||||
|
||||
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
||||
before_action :check_user_permissions, if: :user_signed_in?
|
||||
before_action :require_functional!, if: :user_signed_in?
|
||||
|
||||
def raise_not_found
|
||||
raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
|
||||
@ -57,8 +58,8 @@ class ApplicationController < ActionController::Base
|
||||
forbidden unless current_user&.staff?
|
||||
end
|
||||
|
||||
def check_user_permissions
|
||||
forbidden if current_user.disabled? || current_user.account.suspended?
|
||||
def require_functional!
|
||||
redirect_to edit_user_registration_path unless current_user.functional?
|
||||
end
|
||||
|
||||
def after_sign_out_path_for(_resource_or_scope)
|
||||
@ -190,52 +191,14 @@ class ApplicationController < ActionController::Base
|
||||
current_user.setting_skin
|
||||
end
|
||||
|
||||
def cache_collection(raw, klass)
|
||||
return raw unless klass.respond_to?(:with_includes)
|
||||
|
||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
unless uncached_ids.empty?
|
||||
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
end
|
||||
end
|
||||
|
||||
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
|
||||
end
|
||||
|
||||
def respond_with_error(code)
|
||||
respond_to do |format|
|
||||
format.any { head code }
|
||||
|
||||
format.html do
|
||||
set_locale
|
||||
use_pack 'error'
|
||||
render "errors/#{code}", layout: 'error', status: code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def render_cached_json(cache_key, **options)
|
||||
options[:expires_in] ||= 3.minutes
|
||||
cache_public = options.key?(:public) ? options.delete(:public) : true
|
||||
content_type = options.delete(:content_type) || 'application/json'
|
||||
|
||||
data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
|
||||
yield.to_json
|
||||
end
|
||||
|
||||
expires_in options[:expires_in], public: cache_public
|
||||
render json: data, content_type: content_type
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
|
||||
end
|
||||
end
|
||||
|
@ -4,20 +4,9 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
||||
layout 'auth'
|
||||
|
||||
before_action :set_body_classes
|
||||
before_action :set_user, only: [:finish_signup]
|
||||
before_action :set_pack
|
||||
|
||||
def finish_signup
|
||||
return unless request.patch? && params[:user]
|
||||
|
||||
if @user.update(user_params)
|
||||
@user.skip_reconfirmation!
|
||||
bypass_sign_in(@user)
|
||||
redirect_to root_path, notice: I18n.t('devise.confirmations.send_instructions')
|
||||
else
|
||||
@show_errors = true
|
||||
end
|
||||
end
|
||||
skip_before_action :require_functional!
|
||||
|
||||
private
|
||||
|
||||
@ -25,18 +14,10 @@ class Auth::ConfirmationsController < Devise::ConfirmationsController
|
||||
use_pack 'auth'
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email)
|
||||
end
|
||||
|
||||
def after_confirmation_path_for(_resource_name, user)
|
||||
if user.created_by_application && truthy_param?(:redirect_to_app)
|
||||
user.created_by_application.redirect_uri
|
||||
|
@ -27,7 +27,7 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
if resource.email_verified?
|
||||
root_path
|
||||
else
|
||||
finish_signup_path
|
||||
auth_setup_path(missing_email: '1')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -10,6 +10,9 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
before_action :set_sessions, only: [:edit, :update]
|
||||
before_action :set_instance_presenter, only: [:new, :create, :update]
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
|
||||
skip_before_action :require_functional!, only: [:edit, :update]
|
||||
|
||||
def new
|
||||
super(&:build_invite_request)
|
||||
@ -44,7 +47,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
end
|
||||
|
||||
def after_sign_up_path_for(_resource)
|
||||
new_user_session_path
|
||||
auth_setup_path
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(_resource)
|
||||
@ -107,4 +110,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
||||
def set_sessions
|
||||
@sessions = current_user.session_activations
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
@ -6,9 +6,11 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
layout 'auth'
|
||||
|
||||
skip_before_action :require_no_authentication, only: [:create]
|
||||
skip_before_action :check_user_permissions, only: [:destroy]
|
||||
skip_before_action :require_functional!
|
||||
|
||||
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
|
||||
prepend_before_action :set_pack
|
||||
|
||||
before_action :set_instance_presenter, only: [:new]
|
||||
before_action :set_body_classes
|
||||
|
||||
|
58
app/controllers/auth/setup_controller.rb
Normal file
58
app/controllers/auth/setup_controller.rb
Normal file
@ -0,0 +1,58 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Auth::SetupController < ApplicationController
|
||||
layout 'auth'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :require_unconfirmed_or_pending!
|
||||
before_action :set_body_classes
|
||||
before_action :set_user
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
flash.now[:notice] = begin
|
||||
if @user.pending?
|
||||
I18n.t('devise.registrations.signed_up_but_pending')
|
||||
else
|
||||
I18n.t('devise.registrations.signed_up_but_unconfirmed')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
# This allows updating the e-mail without entering a password as is required
|
||||
# on the account settings page; however, we only allow this for accounts
|
||||
# that were not confirmed yet
|
||||
|
||||
if @user.update(user_params)
|
||||
redirect_to auth_setup_path, notice: I18n.t('devise.confirmations.send_instructions')
|
||||
else
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
helper_method :missing_email?
|
||||
|
||||
private
|
||||
|
||||
def require_unconfirmed_or_pending!
|
||||
redirect_to root_path if current_user.confirmed? && current_user.approved?
|
||||
end
|
||||
|
||||
def set_user
|
||||
@user = current_user
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'lighter'
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(:email)
|
||||
end
|
||||
|
||||
def missing_email?
|
||||
truthy_param?(:missing_email)
|
||||
end
|
||||
end
|
50
app/controllers/concerns/cache_concern.rb
Normal file
50
app/controllers/concerns/cache_concern.rb
Normal file
@ -0,0 +1,50 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module CacheConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def render_with_cache(**options)
|
||||
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
|
||||
|
||||
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
|
||||
expires_in = options.delete(:expires_in) || 3.minutes
|
||||
body = Rails.cache.read(key, raw: true)
|
||||
|
||||
if body
|
||||
render(options.except(:json, :serializer, :each_serializer, :adapter, :fields).merge(json: body))
|
||||
else
|
||||
if block_given?
|
||||
options[:json] = yield
|
||||
elsif options[:json].is_a?(Symbol)
|
||||
options[:json] = send(options[:json])
|
||||
end
|
||||
|
||||
render(options)
|
||||
Rails.cache.write(key, response.body, expires_in: expires_in, raw: true)
|
||||
end
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
|
||||
end
|
||||
|
||||
def cache_collection(raw, klass)
|
||||
return raw unless klass.respond_to?(:with_includes)
|
||||
|
||||
raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
|
||||
cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
|
||||
uncached_ids = raw.map(&:id) - cached_keys_with_value.keys
|
||||
|
||||
klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)
|
||||
|
||||
unless uncached_ids.empty?
|
||||
uncached = klass.where(id: uncached_ids).with_includes.each_with_object({}) { |item, h| h[item.id] = item }
|
||||
|
||||
uncached.each_value do |item|
|
||||
Rails.cache.write(item, item)
|
||||
end
|
||||
end
|
||||
|
||||
raw.map { |item| cached_keys_with_value[item.id] || uncached[item.id] }.compact
|
||||
end
|
||||
end
|
@ -4,16 +4,19 @@ module Localized
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_locale
|
||||
around_action :set_locale
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_locale
|
||||
I18n.locale = default_locale
|
||||
I18n.locale = current_user.locale if user_signed_in?
|
||||
rescue I18n::InvalidLocale
|
||||
I18n.locale = default_locale
|
||||
locale = current_user.locale if respond_to?(:user_signed_in?) && user_signed_in?
|
||||
locale ||= session[:locale] ||= default_locale
|
||||
locale = default_locale unless I18n.available_locales.include?(locale.to_sym)
|
||||
|
||||
I18n.with_locale(locale) do
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def default_locale
|
||||
|
@ -8,7 +8,7 @@ class EmojisController < ApplicationController
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
expires_in 3.minutes, public: true
|
||||
render json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -8,6 +8,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
|
||||
before_action :set_pack
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
include Localized
|
||||
|
||||
def destroy
|
||||
|
@ -3,6 +3,9 @@
|
||||
class Settings::DeletesController < Settings::BaseController
|
||||
|
||||
prepend_before_action :check_enabled_deletion
|
||||
before_action :require_not_suspended!
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
@confirmation = Form::DeleteConfirmation.new
|
||||
@ -27,4 +30,8 @@ class Settings::DeletesController < Settings::BaseController
|
||||
def delete_params
|
||||
params.require(:form_delete_confirmation).permit(:password)
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
end
|
||||
|
@ -5,6 +5,8 @@ class Settings::SessionsController < ApplicationController
|
||||
before_action :authenticate_user!
|
||||
before_action :set_session, only: :destroy
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def destroy
|
||||
@session.destroy!
|
||||
flash[:notice] = I18n.t('sessions.revoke_success')
|
||||
|
@ -5,6 +5,8 @@ module Settings
|
||||
class ConfirmationsController < BaseController
|
||||
before_action :ensure_otp_secret
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def new
|
||||
prepare_two_factor_form
|
||||
end
|
||||
|
@ -3,6 +3,8 @@
|
||||
module Settings
|
||||
module TwoFactorAuthentication
|
||||
class RecoveryCodesController < BaseController
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def create
|
||||
@recovery_codes = current_user.generate_otp_backup_codes!
|
||||
current_user.save!
|
||||
|
@ -4,6 +4,8 @@ module Settings
|
||||
class TwoFactorAuthenticationsController < BaseController
|
||||
before_action :verify_otp_required, only: [:create]
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show
|
||||
@confirmation = Form::TwoFactorConfirmation.new
|
||||
end
|
||||
|
@ -34,14 +34,14 @@ class StatusesController < ApplicationController
|
||||
|
||||
format.json do
|
||||
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
|
||||
render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
||||
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def activity
|
||||
expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
|
||||
render json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
|
||||
render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
|
||||
end
|
||||
|
||||
def embed
|
||||
|
Reference in New Issue
Block a user