about summary refs log tree commit diff
path: root/app/controllers
diff options
context:
space:
mode:
Diffstat (limited to 'app/controllers')
-rw-r--r--app/controllers/about_controller.rb17
-rw-r--r--app/controllers/accounts_controller.rb85
-rw-r--r--app/controllers/activitypub/claims_controller.rb2
-rw-r--r--app/controllers/activitypub/inboxes_controller.rb2
-rw-r--r--app/controllers/activitypub/outboxes_controller.rb36
-rw-r--r--app/controllers/activitypub/replies_controller.rb2
-rw-r--r--app/controllers/admin/custom_emojis_controller.rb78
-rw-r--r--app/controllers/admin/domain_allows_controller.rb2
-rw-r--r--app/controllers/admin/pending_accounts_controller.rb6
-rw-r--r--app/controllers/admin/tags_controller.rb2
-rw-r--r--app/controllers/api/base_controller.rb2
-rw-r--r--app/controllers/api/v1/accounts/credentials_controller.rb4
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb76
-rw-r--r--app/controllers/api/v1/accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/admin/domain_allows_controller.rb54
-rw-r--r--app/controllers/api/v1/admin/domain_blocks_controller.rb54
-rw-r--r--app/controllers/api/v1/domain_permissions_controller.rb81
-rw-r--r--app/controllers/api/v1/instances/activity_controller.rb4
-rw-r--r--app/controllers/api/v1/instances/peers_controller.rb4
-rw-r--r--app/controllers/api/v1/instances_controller.rb2
-rw-r--r--app/controllers/api/v1/polls/votes_controller.rb1
-rw-r--r--app/controllers/api/v1/polls_controller.rb1
-rw-r--r--app/controllers/api/v1/statuses/hides_controller.rb28
-rw-r--r--app/controllers/api/v1/statuses/mutes_controller.rb4
-rw-r--r--app/controllers/api/v1/statuses/publishing_controller.rb26
-rw-r--r--app/controllers/api/v1/statuses_controller.rb103
-rw-r--r--app/controllers/application_controller.rb48
-rw-r--r--app/controllers/auth/registrations_controller.rb8
-rw-r--r--app/controllers/concerns/account_owned_concern.rb2
-rw-r--r--app/controllers/custom_emojis_controller.rb97
-rw-r--r--app/controllers/home_controller.rb2
-rw-r--r--app/controllers/matrix/base_controller.rb100
-rw-r--r--app/controllers/matrix/identity/v1/check_credentials_controller.rb53
-rw-r--r--app/controllers/media_controller.rb5
-rw-r--r--app/controllers/media_proxy_controller.rb3
-rw-r--r--app/controllers/remote_interaction_controller.rb4
-rw-r--r--app/controllers/settings/preferences/filters_controller.rb9
-rw-r--r--app/controllers/settings/preferences/privacy_controller.rb9
-rw-r--r--app/controllers/settings/preferences/publishing_controller.rb9
-rw-r--r--app/controllers/settings/preferences_controller.rb25
-rw-r--r--app/controllers/settings/profiles_controller.rb4
-rw-r--r--app/controllers/statuses_controller.rb14
-rw-r--r--app/controllers/tags_controller.rb6
-rw-r--r--app/controllers/user_profile_css_controller.rb24
-rw-r--r--app/controllers/user_webapp_css_controller.rb73
45 files changed, 992 insertions, 181 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index 5d5db937c..bf3d3ff42 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -4,16 +4,14 @@ class AboutController < ApplicationController
   before_action :set_pack
   layout 'public'
 
-  before_action :require_open_federation!, only: [:show, :more]
+  #before_action :require_open_federation!, only: [:show, :more]
   before_action :set_body_classes, only: :show
   before_action :set_instance_presenter
   before_action :set_expires_in, only: [:show, :more, :terms]
 
   skip_before_action :require_functional!, only: [:more, :terms]
 
-  def show; end
-
-  def more
+  def show
     flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
 
     toc_generator = TOCGenerator.new(@instance_presenter.site_extended_description)
@@ -21,10 +19,15 @@ class AboutController < ApplicationController
     @contents          = toc_generator.html
     @table_of_contents = toc_generator.toc
     @blocks            = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
+    @allows            = DomainAllow.where(hidden: false) if display_allows?
   end
 
+  alias more show
+
   def terms; end
 
+  helper_method :display_allows?
+
   helper_method :display_blocks?
   helper_method :display_blocks_rationale?
   helper_method :public_fetch_mode?
@@ -66,4 +69,10 @@ class AboutController < ApplicationController
   def set_expires_in
     expires_in 0, public: true
   end
+
+  # Monsterfork additions
+
+  def display_allows?
+    Setting.show_domain_allows == 'all' || (Setting.show_domain_allows == 'users' && user_signed_in?)
+  end
 end
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 356542767..352f84ea7 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -11,20 +11,24 @@ class AccountsController < ApplicationController
   before_action :set_cache_headers
   before_action :set_body_classes
 
+  before_action :require_authenticated!, if: -> { @account.require_auth? || @account.private? }
+
   skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
-  skip_before_action :require_functional!, unless: :whitelist_mode?
+  skip_before_action :require_functional! # , unless: :whitelist_mode?
 
   def show
+    @without_unlisted = !@account.show_unlisted?
+
     respond_to do |format|
       format.html do
         use_pack 'public'
-        expires_in 0, public: true unless user_signed_in?
+        expires_in 0, public: true unless user_signed_in? || signed_request_account.present?
 
         @pinned_statuses   = []
-        @endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
-        @featured_hashtags = @account.featured_tags.order(statuses_count: :desc)
+        @endorsed_accounts = unauthorized? ? [] : @account.endorsed_accounts.to_a.sample(4)
+        @featured_hashtags = unauthorized? ? [] : @account.featured_tags.order(statuses_count: :desc)
 
-        if current_account && @account.blocking?(current_account)
+        if unauthorized?
           @statuses = []
           return
         end
@@ -40,16 +44,19 @@ class AccountsController < ApplicationController
       end
 
       format.rss do
-        expires_in 1.minute, public: true
+        return render xml: '', status: 404 if rss_disabled? || unauthorized?
+
+        expires_in 1.minute, public: !current_account?
 
-        limit     = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
+        @without_unlisted = true
+        limit = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
         @statuses = filtered_statuses.without_reblogs.limit(limit)
         @statuses = cache_collection(@statuses, Status)
         render xml: RSS::AccountSerializer.render(@account, @statuses, params[:tag])
       end
 
       format.json do
-        expires_in 3.minutes, public: !(authorized_fetch_mode? && signed_request_account.present?)
+        expires_in 3.minutes, public: !current_account?
         render_with_cache json: @account, content_type: 'application/activity+json', serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
       end
     end
@@ -62,19 +69,27 @@ class AccountsController < ApplicationController
   end
 
   def show_pinned_statuses?
-    [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
+    [threads_requested?, replies_requested?, reblogs_requested?, mentions_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
   end
 
   def filtered_statuses
+    return mentions_scope if mentions_requested?
+
     default_statuses.tap do |statuses|
-      statuses.merge!(hashtag_scope)    if tag_requested?
       statuses.merge!(only_media_scope) if media_requested?
-      statuses.merge!(no_replies_scope) unless replies_requested?
     end
   end
 
   def default_statuses
-    @account.statuses.not_local_only.where(visibility: [:public, :unlisted])
+    @account.statuses.permitted_for(
+      @account,
+      current_account,
+      include_reblogs: !(threads_requested? || replies_requested?),
+      only_reblogs: reblogs_requested?,
+      include_replies: replies_requested?,
+      tag: tag_requested? ? params[:tag] : nil,
+      public: @without_unlisted
+    )
   end
 
   def only_media_scope
@@ -85,18 +100,10 @@ class AccountsController < ApplicationController
     @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
   end
 
-  def no_replies_scope
-    Status.without_replies
-  end
+  def mentions_scope
+    return Status.none unless current_account?
 
-  def hashtag_scope
-    tag = Tag.find_normalized(params[:tag])
-
-    if tag
-      Status.tagged_with(tag.id)
-    else
-      Status.none
-    end
+    Status.mentions_between(@account, current_account)
   end
 
   def username_param
@@ -124,8 +131,14 @@ class AccountsController < ApplicationController
       short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
     elsif media_requested?
       short_account_media_url(@account, max_id: max_id, min_id: min_id)
+    elsif threads_requested?
+      short_account_threads_url(@account, max_id: max_id, min_id: min_id)
     elsif replies_requested?
       short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
+    elsif reblogs_requested?
+      short_account_reblogs_url(@account, max_id: max_id, min_id: min_id)
+    elsif mentions_requested?
+      short_account_mentions_url(@account, max_id: max_id, min_id: min_id)
     else
       short_account_url(@account, max_id: max_id, min_id: min_id)
     end
@@ -135,7 +148,13 @@ class AccountsController < ApplicationController
     request.path.split('.').first.ends_with?('/media') && !tag_requested?
   end
 
+  def threads_requested?
+    request.path.split('.').first.ends_with?('/threads') && !tag_requested?
+  end
+
   def replies_requested?
+    return false unless current_account&.id == @account.id || @account.show_replies?
+
     request.path.split('.').first.ends_with?('/with_replies') && !tag_requested?
   end
 
@@ -143,6 +162,26 @@ class AccountsController < ApplicationController
     request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
   end
 
+  def reblogs_requested?
+    request.path.split('.').first.ends_with?('/reblogs') && !tag_requested?
+  end
+
+  def mentions_requested?
+    request.path.split('.').first.ends_with?('/mentions') && !tag_requested?
+  end
+
+  def blocked?
+    @blocked ||= current_account && @account.blocking?(current_account)
+  end
+
+  def unauthorized?
+    @unauthorized ||= blocked? || (@account.private? && !following?(@account))
+  end
+
+  def rss_disabled?
+    @account.user&.setting_rss_disabled
+  end
+
   def cached_filtered_status_page
     cache_collection_paginated_by_id(
       filtered_statuses,
diff --git a/app/controllers/activitypub/claims_controller.rb b/app/controllers/activitypub/claims_controller.rb
index 08ad952df..5009a9f05 100644
--- a/app/controllers/activitypub/claims_controller.rb
+++ b/app/controllers/activitypub/claims_controller.rb
@@ -4,7 +4,7 @@ class ActivityPub::ClaimsController < ActivityPub::BaseController
   include SignatureVerification
   include AccountOwnedConcern
 
-  skip_before_action :authenticate_user!
+  #skip_before_action :authenticate_user!
 
   before_action :require_signature!
   before_action :set_claim_result
diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb
index 0a561e7f0..3e67f3909 100644
--- a/app/controllers/activitypub/inboxes_controller.rb
+++ b/app/controllers/activitypub/inboxes_controller.rb
@@ -7,7 +7,7 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
 
   before_action :skip_unknown_actor_delete
   before_action :require_signature!
-  skip_before_action :authenticate_user!
+  #skip_before_action :authenticate_user!
 
   def create
     upgrade_account
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index 5fd735ad6..7c914298b 100644
--- a/app/controllers/activitypub/outboxes_controller.rb
+++ b/app/controllers/activitypub/outboxes_controller.rb
@@ -10,9 +10,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   before_action :set_statuses
   before_action :set_cache_headers
 
+  before_action :require_authenticated!, if: -> { @account.require_auth? }
+  before_action -> { require_following!(@account) }, if: -> { @account.private? }
+
   def show
-    expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
-    render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
+    expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(current_account.present? && page_requested?))
+    render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: current_account&.domain
   end
 
   private
@@ -54,11 +57,38 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
     account_outbox_url(@account, page: true, min_id: @statuses.first.id) unless @statuses.empty?
   end
 
+  def permitted_account_statuses
+    @account.statuses.permitted_for(
+      @account,
+      current_account,
+      include_replies: true,
+      include_reblogs: true,
+      public: !(owner? || follower?),
+      exclude_local_only: true
+    )
+  end
+
+  def owner?
+    return @owner if defined?(@owner)
+
+    @owner   = @account.id == current_account&.id
+    @owner ||= @account.moved_to_account_id == current_account&.id if @account.moved_to_account_id.present?
+    @owner
+  end
+
+  def follower?
+    @following ||= current_account&.following?(@account)
+  end
+
+  def mutual_follower?
+    follower? && @account.following?(current_account)
+  end
+
   def set_statuses
     return unless page_requested?
 
     @statuses = cache_collection_paginated_by_id(
-      @account.statuses.permitted_for(@account, signed_request_account),
+      permitted_account_statuses,
       Status,
       LIMIT,
       params_slice(:max_id, :min_id, :since_id)
diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb
index 43bf4e657..fd12f0745 100644
--- a/app/controllers/activitypub/replies_controller.rb
+++ b/app/controllers/activitypub/replies_controller.rb
@@ -14,7 +14,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
 
   def index
     expires_in 0, public: public_fetch_mode?
-    render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true
+    render json: replies_collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', skip_activities: true, target_domain: current_account&.domain
   end
 
   private
diff --git a/app/controllers/admin/custom_emojis_controller.rb b/app/controllers/admin/custom_emojis_controller.rb
deleted file mode 100644
index 71efb543e..000000000
--- a/app/controllers/admin/custom_emojis_controller.rb
+++ /dev/null
@@ -1,78 +0,0 @@
-# frozen_string_literal: true
-
-module Admin
-  class CustomEmojisController < BaseController
-    def index
-      authorize :custom_emoji, :index?
-
-      @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
-      @form          = Form::CustomEmojiBatch.new
-    end
-
-    def new
-      authorize :custom_emoji, :create?
-
-      @custom_emoji = CustomEmoji.new
-    end
-
-    def create
-      authorize :custom_emoji, :create?
-
-      @custom_emoji = CustomEmoji.new(resource_params)
-
-      if @custom_emoji.save
-        log_action :create, @custom_emoji
-        redirect_to admin_custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg')
-      else
-        render :new
-      end
-    end
-
-    def batch
-      @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
-      @form.save
-    rescue ActionController::ParameterMissing
-      flash[:alert] = I18n.t('admin.accounts.no_account_selected')
-    rescue Mastodon::NotPermittedError
-      flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
-    ensure
-      redirect_to admin_custom_emojis_path(filter_params)
-    end
-
-    private
-
-    def resource_params
-      params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
-    end
-
-    def filtered_custom_emojis
-      CustomEmojiFilter.new(filter_params).results
-    end
-
-    def filter_params
-      params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS)
-    end
-
-    def action_from_button
-      if params[:update]
-        'update'
-      elsif params[:list]
-        'list'
-      elsif params[:unlist]
-        'unlist'
-      elsif params[:enable]
-        'enable'
-      elsif params[:disable]
-        'disable'
-      elsif params[:copy]
-        'copy'
-      elsif params[:delete]
-        'delete'
-      end
-    end
-
-    def form_custom_emoji_batch_params
-      params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
-    end
-  end
-end
diff --git a/app/controllers/admin/domain_allows_controller.rb b/app/controllers/admin/domain_allows_controller.rb
index 31be1978b..95d9a31fb 100644
--- a/app/controllers/admin/domain_allows_controller.rb
+++ b/app/controllers/admin/domain_allows_controller.rb
@@ -35,6 +35,6 @@ class Admin::DomainAllowsController < Admin::BaseController
   end
 
   def resource_params
-    params.require(:domain_allow).permit(:domain)
+    params.require(:domain_allow).permit(:domain, :hidden)
   end
 end
diff --git a/app/controllers/admin/pending_accounts_controller.rb b/app/controllers/admin/pending_accounts_controller.rb
index b62a9bc84..8a9a51d84 100644
--- a/app/controllers/admin/pending_accounts_controller.rb
+++ b/app/controllers/admin/pending_accounts_controller.rb
@@ -18,19 +18,19 @@ module Admin
     end
 
     def approve_all
-      Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'approve').save
+      Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.confirmed.pluck(:account_id), action: 'approve').save
       redirect_to admin_pending_accounts_path(current_params)
     end
 
     def reject_all
-      Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.pluck(:account_id), action: 'reject').save
+      Form::AccountBatch.new(current_account: current_account, account_ids: User.pending.confirmed.pluck(:account_id), action: 'reject').save
       redirect_to admin_pending_accounts_path(current_params)
     end
 
     private
 
     def set_accounts
-      @accounts = Account.joins(:user).merge(User.pending.recent).includes(user: :invite_request).page(params[:page])
+      @accounts = Account.joins(:user).merge(User.pending.confirmed.recent).includes(user: :invite_request).page(params[:page])
     end
 
     def form_account_batch_params
diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb
index 59df4470e..8abe19626 100644
--- a/app/controllers/admin/tags_controller.rb
+++ b/app/controllers/admin/tags_controller.rb
@@ -54,7 +54,7 @@ module Admin
 
     def set_usage_by_domain
       @usage_by_domain = @tag.statuses
-                             .with_public_visibility
+                             .distributable
                              .excluding_silenced_accounts
                              .where(Status.arel_table[:id].gteq(Mastodon::Snowflake.id_at(Time.now.utc.beginning_of_day)))
                              .joins(:account)
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index e962c4e97..818819a3f 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -7,7 +7,7 @@ class Api::BaseController < ApplicationController
   include RateLimitHeaders
 
   skip_before_action :store_current_location
-  skip_before_action :require_functional!, unless: :whitelist_mode?
+  skip_before_action :require_functional! #, unless: :whitelist_mode?
 
   before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
   before_action :set_cache_headers
diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb
index 64b5cb747..3c8187a99 100644
--- a/app/controllers/api/v1/accounts/credentials_controller.rb
+++ b/app/controllers/api/v1/accounts/credentials_controller.rb
@@ -21,7 +21,9 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
   private
 
   def account_params
-    params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
+    params.permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable,
+                  :require_dereference, :show_replies, :show_unlisted,
+                  fields_attributes: [:name, :value])
   end
 
   def user_settings_params
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 92ccb8061..a0ce810ad 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -8,7 +8,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
 
   def index
     @statuses = load_statuses
-    render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
+    render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_account&.id)
   end
 
   private
@@ -17,17 +17,17 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
     @account = Account.find(params[:account_id])
   end
 
+  def owner?
+    @account.id == current_account&.id
+  end
+
   def load_statuses
     @account.suspended? ? [] : cached_account_statuses
   end
 
   def cached_account_statuses
     statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
-
     statuses.merge!(only_media_scope) if truthy_param?(:only_media)
-    statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
-    statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs)
-    statuses.merge!(hashtag_scope)    if params[:tagged].present?
 
     cache_collection_paginated_by_id(
       statuses,
@@ -38,39 +38,65 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def permitted_account_statuses
-    @account.statuses.permitted_for(@account, current_account)
+    return mentions_scope if truthy_param?(:mentions)
+    return Status.none if unauthorized?
+
+    @account.statuses.permitted_for(
+      @account,
+      current_account,
+      include_reblogs: include_reblogs?,
+      include_replies: include_replies?,
+      only_reblogs: only_reblogs?,
+      only_replies: only_replies?,
+      include_unpublished: owner?,
+      tag: params[:tagged]
+    )
   end
 
   def only_media_scope
     Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
   end
 
-  def pinned_scope
-    return Status.none if @account.blocking?(current_account)
+  def unauthorized?
+    (@account.private && !following?(@account)) || (@account.require_auth && !current_account?)
+  end
 
-    @account.pinned_statuses
+  def include_reblogs?
+    params[:include_reblogs].present? ? truthy_param?(:include_reblogs) : !truthy_param?(:exclude_reblogs)
+  end
+
+  def include_replies?
+    return false unless owner? || @account.show_replies?
+
+    params[:include_replies].present? ? truthy_param?(:include_replies) : !truthy_param?(:exclude_replies)
   end
 
-  def no_replies_scope
-    Status.without_replies
+  def only_reblogs?
+    truthy_param?(:only_reblogs).presence || false
   end
 
-  def no_reblogs_scope
-    Status.without_reblogs
+  def only_replies?
+    return false unless owner? || @account.show_replies?
+
+    truthy_param?(:only_replies).presence || false
   end
 
-  def hashtag_scope
-    tag = Tag.find_normalized(params[:tagged])
+  def mentions_scope
+    return Status.none unless current_account?
+
+    Status.mentions_between(@account, current_account)
+  end
 
-    if tag
-      Status.tagged_with(tag.id)
-    else
-      Status.none
-    end
+  def pinned_scope
+    return Status.none if @account.blocking?(current_account)
+
+    @account.pinned_statuses
   end
 
   def pagination_params(core_params)
-    params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params)
+    params.slice(:limit, :only_media, :include_replies, :exclude_replies, :only_replies, :include_reblogs, :exclude_reblogs, :only_relogs, :mentions)
+          .permit(:limit, :only_media, :include_replies, :exclude_replies, :only_replies, :include_reblogs, :exclude_reblogs, :only_relogs, :mentions)
+          .merge(core_params)
   end
 
   def insert_pagination_headers
@@ -78,15 +104,11 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def next_path
-    if records_continue?
-      api_v1_account_statuses_url pagination_params(max_id: pagination_max_id)
-    end
+    api_v1_account_statuses_url pagination_params(max_id: pagination_max_id) if records_continue?
   end
 
   def prev_path
-    unless @statuses.empty?
-      api_v1_account_statuses_url pagination_params(min_id: pagination_since_id)
-    end
+    api_v1_account_statuses_url pagination_params(min_id: pagination_since_id) unless @statuses.empty?
   end
 
   def records_continue?
diff --git a/app/controllers/api/v1/accounts_controller.rb b/app/controllers/api/v1/accounts_controller.rb
index aef51a647..453929afe 100644
--- a/app/controllers/api/v1/accounts_controller.rb
+++ b/app/controllers/api/v1/accounts_controller.rb
@@ -42,7 +42,7 @@ class Api::V1::AccountsController < Api::BaseController
   end
 
   def mute
-    MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications))
+    MuteService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications), timelines_only: truthy_param?(:timelines_only))
     render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
   end
 
diff --git a/app/controllers/api/v1/admin/domain_allows_controller.rb b/app/controllers/api/v1/admin/domain_allows_controller.rb
new file mode 100644
index 000000000..1b150d480
--- /dev/null
+++ b/app/controllers/api/v1/admin/domain_allows_controller.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+class Api::V1::Admin::DomainAllowsController < Api::BaseController
+  include Authorization
+
+  LIMIT = 100
+
+  before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:domain_allows' }, only: :show
+  before_action :require_staff!
+  after_action :insert_pagination_headers, only: :show
+
+  def show
+    @allows = load_domain_allows
+    render json: @allows
+  end
+
+  private
+
+  def load_domain_allows
+    DomainAllow.paginate_by_max_id(
+      limit_param(LIMIT),
+      params[:max_id],
+      params[:since_id]
+    )
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_admin_domain_allows_url pagination_params(max_id: pagination_max_id) if records_continue?
+  end
+
+  def prev_path
+    api_v1_admin_domain_allows_url pagination_params(since_id: pagination_since_id) unless @allows.empty?
+  end
+
+  def pagination_max_id
+    @allows.last.id
+  end
+
+  def pagination_since_id
+    @allows.first.id
+  end
+
+  def records_continue?
+    @allows.size == limit_param(LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+end
diff --git a/app/controllers/api/v1/admin/domain_blocks_controller.rb b/app/controllers/api/v1/admin/domain_blocks_controller.rb
new file mode 100644
index 000000000..c0ce0da25
--- /dev/null
+++ b/app/controllers/api/v1/admin/domain_blocks_controller.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+class Api::V1::Admin::DomainBlocksController < Api::BaseController
+  include Authorization
+
+  LIMIT = 100
+
+  before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:domain_blocks' }, only: :show
+  before_action :require_staff!
+  after_action :insert_pagination_headers, only: :show
+
+  def show
+    @blocks = load_domain_blocks
+    render json: @blocks
+  end
+
+  private
+
+  def load_domain_blocks
+    DomainBlock.paginate_by_max_id(
+      limit_param(LIMIT),
+      params[:max_id],
+      params[:since_id]
+    )
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_admin_domain_blocks_url pagination_params(max_id: pagination_max_id) if records_continue?
+  end
+
+  def prev_path
+    api_v1_admin_domain_blocks_url pagination_params(since_id: pagination_since_id) unless @blocks.empty?
+  end
+
+  def pagination_max_id
+    @blocks.last.id
+  end
+
+  def pagination_since_id
+    @blocks.first.id
+  end
+
+  def records_continue?
+    @blocks.size == limit_param(LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+end
diff --git a/app/controllers/api/v1/domain_permissions_controller.rb b/app/controllers/api/v1/domain_permissions_controller.rb
new file mode 100644
index 000000000..1b0e37135
--- /dev/null
+++ b/app/controllers/api/v1/domain_permissions_controller.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+class Api::V1::DomainPermissionsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :read, :'read:domain_permissions', :'read:domain_permissions:account' }, only: :show
+  before_action -> { doorkeeper_authorize! :write, :'write:domain_permissions', :'write:domain_permissions:account' }, only: [:create, :update, :destroy]
+  before_action :require_user!
+  before_action :set_permission, except: [:show, :create]
+  after_action :insert_pagination_headers
+
+  LIMIT = 100
+
+  def show
+    @permissions = load_account_domain_permissions
+    render json: @permissions, each_serializer: REST::AccountDomainPermissionSerializer
+  end
+
+  def create
+    @permission = current_account.domain_permissions.create!(domain_permission_params)
+    render json: @permission, serializer: REST::AccountDomainPermissionSerializer
+  end
+
+  def update
+    @permission.update!(domain_permission_params)
+    render json: @permission, serializer: REST::AccountDomainPermissionSerializer
+  end
+
+  def destroy
+    @permission.destroy!
+    render_empty
+  end
+
+  private
+
+  def load_account_domain_permissions
+    account_domain_permissions.paginate_by_max_id(
+      limit_param(LIMIT),
+      params[:max_id],
+      params[:since_id]
+    )
+  end
+
+  def set_permission
+    @permission = current_account.domain_permissions.find(params[:id])
+  end
+
+  def account_domain_permissions
+    current_account.domain_permissions
+  end
+
+  def insert_pagination_headers
+    set_pagination_headers(next_path, prev_path)
+  end
+
+  def next_path
+    api_v1_domain_permissions_url pagination_params(max_id: pagination_max_id) if records_continue?
+  end
+
+  def prev_path
+    api_v1_domain_permissions_url pagination_params(since_id: pagination_since_id) unless @permissions.empty?
+  end
+
+  def pagination_max_id
+    @permissions.last.id
+  end
+
+  def pagination_since_id
+    @permissions.first.id
+  end
+
+  def records_continue?
+    @permissions.size == limit_param(LIMIT)
+  end
+
+  def pagination_params(core_params)
+    params.slice(:limit).permit(:limit).merge(core_params)
+  end
+
+  def domain_permission_params
+    params.permit(:domain, :visibility)
+  end
+end
diff --git a/app/controllers/api/v1/instances/activity_controller.rb b/app/controllers/api/v1/instances/activity_controller.rb
index 4f6b4bcbf..f2ac902e1 100644
--- a/app/controllers/api/v1/instances/activity_controller.rb
+++ b/app/controllers/api/v1/instances/activity_controller.rb
@@ -4,7 +4,7 @@ class Api::V1::Instances::ActivityController < Api::BaseController
   before_action :require_enabled_api!
 
   skip_before_action :set_cache_headers
-  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
+  skip_before_action :require_authenticated_user! #, unless: :whitelist_mode?
 
   def show
     expires_in 1.day, public: true
@@ -33,6 +33,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
   end
 
   def require_enabled_api!
-    head 404 unless Setting.activity_api_enabled && !whitelist_mode?
+    head 404 unless Setting.activity_api_enabled #&& !whitelist_mode?
   end
 end
diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb
index 9fa440935..d30ef1fe9 100644
--- a/app/controllers/api/v1/instances/peers_controller.rb
+++ b/app/controllers/api/v1/instances/peers_controller.rb
@@ -4,7 +4,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
   before_action :require_enabled_api!
 
   skip_before_action :set_cache_headers
-  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
+  skip_before_action :require_authenticated_user! #, unless: :whitelist_mode?
 
   def index
     expires_in 1.day, public: true
@@ -14,6 +14,6 @@ class Api::V1::Instances::PeersController < Api::BaseController
   private
 
   def require_enabled_api!
-    head 404 unless Setting.peers_api_enabled && !whitelist_mode?
+    head 404 unless Setting.peers_api_enabled #&& !whitelist_mode?
   end
 end
diff --git a/app/controllers/api/v1/instances_controller.rb b/app/controllers/api/v1/instances_controller.rb
index 5b5058a7b..844bab68a 100644
--- a/app/controllers/api/v1/instances_controller.rb
+++ b/app/controllers/api/v1/instances_controller.rb
@@ -2,7 +2,7 @@
 
 class Api::V1::InstancesController < Api::BaseController
   skip_before_action :set_cache_headers
-  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
+  skip_before_action :require_authenticated_user! #, unless: :whitelist_mode?
 
   def show
     expires_in 3.minutes, public: true
diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb
index 513b937ef..91ca96ef0 100644
--- a/app/controllers/api/v1/polls/votes_controller.rb
+++ b/app/controllers/api/v1/polls/votes_controller.rb
@@ -17,6 +17,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
   def set_poll
     @poll = Poll.attached.find(params[:poll_id])
     authorize @poll.status, :show?
+    authorize @poll.status.reblog, :show? if @poll.status.reblog?
   rescue Mastodon::NotPermittedError
     not_found
   end
diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb
index 6435e9f0d..75f5a9f08 100644
--- a/app/controllers/api/v1/polls_controller.rb
+++ b/app/controllers/api/v1/polls_controller.rb
@@ -16,6 +16,7 @@ class Api::V1::PollsController < Api::BaseController
   def set_poll
     @poll = Poll.attached.find(params[:id])
     authorize @poll.status, :show?
+    authorize @poll.status.reblog, :show? if @poll.status.reblog?
   rescue Mastodon::NotPermittedError
     not_found
   end
diff --git a/app/controllers/api/v1/statuses/hides_controller.rb b/app/controllers/api/v1/statuses/hides_controller.rb
new file mode 100644
index 000000000..8c5457c82
--- /dev/null
+++ b/app/controllers/api/v1/statuses/hides_controller.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class Api::V1::Statuses::HidesController < Api::BaseController
+  include Authorization
+
+  before_action -> { doorkeeper_authorize! :write, :'write:mutes' }
+  before_action :require_user!
+  before_action :set_status
+
+  def create
+    MuteStatusService.new.call(current_account, @status)
+    render json: @status, serializer: REST::StatusSerializer
+  end
+
+  def destroy
+    current_account.unmute_status!(@status)
+    render json: @status, serializer: REST::StatusSerializer
+  end
+
+  private
+
+  def set_status
+    @status = Status.find(params[:status_id])
+    authorize @status, :show?
+  rescue Mastodon::NotPermittedError
+    not_found
+  end
+end
diff --git a/app/controllers/api/v1/statuses/mutes_controller.rb b/app/controllers/api/v1/statuses/mutes_controller.rb
index 87071a2b9..418c19840 100644
--- a/app/controllers/api/v1/statuses/mutes_controller.rb
+++ b/app/controllers/api/v1/statuses/mutes_controller.rb
@@ -9,12 +9,14 @@ class Api::V1::Statuses::MutesController < Api::BaseController
   before_action :set_conversation
 
   def create
-    current_account.mute_conversation!(@conversation)
+    MuteConversationService.new.call(current_account, @status.conversation)
     @mutes_map = { @conversation.id => true }
 
     render json: @status, serializer: REST::StatusSerializer
   end
 
+  alias update create
+
   def destroy
     current_account.unmute_conversation!(@conversation)
     @mutes_map = { @conversation.id => false }
diff --git a/app/controllers/api/v1/statuses/publishing_controller.rb b/app/controllers/api/v1/statuses/publishing_controller.rb
new file mode 100644
index 000000000..97c052e22
--- /dev/null
+++ b/app/controllers/api/v1/statuses/publishing_controller.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+class Api::V1::Statuses::PublishingController < Api::BaseController
+  include Authorization
+
+  before_action -> { doorkeeper_authorize! :write, :'write:statuses:publish' }
+  before_action :require_user!
+  before_action :set_status
+
+  def create
+    PublishStatusService.new.call(@status)
+
+    render json: @status,
+           serializer: (@status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer),
+           source_requested: truthy_param?(:source)
+  end
+
+  private
+
+  def set_status
+    @status = Status.unpublished.find(params[:status_id])
+    authorize @status, :destroy?
+  rescue Mastodon::NotPermittedError
+    not_found
+  end
+end
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index c8529318f..c7c429bfb 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -19,7 +19,7 @@ class Api::V1::StatusesController < Api::BaseController
 
   def show
     @status = cache_collection([@status], Status).first
-    render json: @status, serializer: REST::StatusSerializer
+    render json: @status, serializer: REST::StatusSerializer, source_requested: truthy_param?(:source)
   end
 
   def context
@@ -31,7 +31,7 @@ class Api::V1::StatusesController < Api::BaseController
     @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
     statuses = [@status] + @context.ancestors + @context.descendants
 
-    render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
+    render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id), current_account_id: current_user&.account_id
   end
 
   def create
@@ -41,24 +41,82 @@ class Api::V1::StatusesController < Api::BaseController
                                          media_ids: status_params[:media_ids],
                                          sensitive: status_params[:sensitive],
                                          spoiler_text: status_params[:spoiler_text],
+                                         title: status_params[:title],
+                                         footer: status_params[:footer],
+                                         notify: status_params[:notify],
+                                         publish: status_params[:publish],
                                          visibility: status_params[:visibility],
+                                         local_only: status_params[:local_only],
                                          scheduled_at: status_params[:scheduled_at],
                                          application: doorkeeper_token.application,
                                          poll: status_params[:poll],
                                          content_type: status_params[:content_type],
+                                         tags: parse_tags_param(status_params[:tags]),
+                                         mentions: parse_mentions_param(status_params[:mentions]),
                                          idempotency: request.headers['Idempotency-Key'],
-                                         with_rate_limit: true)
+                                         with_rate_limit: true,
+                                         expires_at: status_params[:expires_at],
+                                         publish_at: status_params[:publish_at])
 
-    render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
+    render json: @status,
+           serializer: (@status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer),
+           source_requested: truthy_param?(:source)
+  end
+
+  def update
+    @status = Status.where(account_id: current_user.account).find(params[:id])
+    authorize @status, :destroy?
+
+    @status = PostStatusService.new.call(current_user.account,
+                                         text: status_params[:status],
+                                         thread: @thread,
+                                         media_ids: status_params[:media_ids],
+                                         sensitive: status_params[:sensitive],
+                                         spoiler_text: status_params[:spoiler_text],
+                                         title: status_params[:title],
+                                         footer: status_params[:footer],
+                                         notify: status_params[:notify],
+                                         publish: status_params[:publish],
+                                         visibility: status_params[:visibility],
+                                         local_only: status_params[:local_only],
+                                         scheduled_at: status_params[:scheduled_at],
+                                         application: doorkeeper_token.application,
+                                         poll: status_params[:poll],
+                                         content_type: status_params[:content_type],
+                                         status: @status,
+                                         tags: parse_tags_param(status_params[:tags]),
+                                         mentions: parse_mentions_param(status_params[:mentions]),
+                                         idempotency: request.headers['Idempotency-Key'],
+                                         with_rate_limit: true,
+                                         expires_at: status_params[:expires_at],
+                                         publish_at: status_params[:publish_at])
+
+    render json: @status,
+           serializer: (@status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer),
+           source_requested: truthy_param?(:source)
   end
 
   def destroy
     @status = Status.where(account_id: current_user.account).find(params[:id])
     authorize @status, :destroy?
 
-    @status.discard
-    RemovalWorker.perform_async(@status.id, redraft: true)
-    @status.account.statuses_count = @status.account.statuses_count - 1
+    if !(current_user.setting_unpublish_on_delete && @status.published?) || truthy_param?(:redraft)
+      @status.discard
+      RemovalWorker.perform_async(@status.id, redraft: true, unpublished: true)
+      @status.account.statuses_count = @status.account.statuses_count - 1
+    else
+      RemovalWorker.perform_async(@status.id, redraft: true, unpublish: true)
+      tag_script = "#!redraft #{@status.id}\n"
+      @status.text = "#{tag_script}#{@status.text.sub(/^\s*#!redraft \d+\n/, '')}"
+      @status.original_text = "#{tag_script}#{@status.original_text.sub(/^\s*#!redraft \d+\n/, '')}"
+    end
+
+    @status.local_only = @status.originally_local_only?
+    unless @status.original_text.match?(/^\s*#!\s*federate\b/i)
+      tag_script = "#!federate #{@status.originally_local_only? ? 'off' : 'on'}\n"
+      @status.text.prepend(tag_script)
+      @status.original_text.prepend(tag_script)
+    end
 
     render json: @status, serializer: REST::StatusSerializer, source_requested: true
   end
@@ -84,9 +142,18 @@ class Api::V1::StatusesController < Api::BaseController
       :in_reply_to_id,
       :sensitive,
       :spoiler_text,
+      :title,
+      :footer,
+      :notify,
+      :publish,
       :visibility,
+      :local_only,
       :scheduled_at,
       :content_type,
+      :expires_at,
+      :publish_at,
+      tags: [],
+      mentions: [],
       media_ids: [],
       poll: [
         :multiple,
@@ -100,4 +167,26 @@ class Api::V1::StatusesController < Api::BaseController
   def pagination_params(core_params)
     params.slice(:limit).permit(:limit).merge(core_params)
   end
+
+  def parse_tags_param(tags_param)
+    return if tags_param.blank?
+
+    tags_param.select { |value| value.respond_to?(:to_str) && value.present? }
+  end
+
+  def parse_mentions_param(mentions_param)
+    return if mentions_param.blank?
+
+    mentions_param.map do |value|
+      next if value.blank?
+
+      value = value.split('@', 3) if value.respond_to?(:to_str)
+      next unless value.is_a?(Enumerable)
+
+      mentioned_account = Account.find_by(username: value[0], domain: value[1])
+      next if mentioned_account.nil? || mentioned_account.suspended?
+
+      mentioned_account
+    end
+  end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e996c2217..5e12e89c8 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base
   include SessionTrackingConcern
   include CacheConcern
   include DomainControlHelper
+  include SignatureVerification
 
   helper_method :current_account
   helper_method :current_session
@@ -44,11 +45,11 @@ class ApplicationController < ActionController::Base
   private
 
   def https_enabled?
-    Rails.env.production? && !request.path.start_with?('/health')
+    Rails.env.production? && !request.path.start_with?('/health', '/_matrix-internal/')
   end
 
   def authorized_fetch_mode?
-    ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
+    !(Rails.env.development? || Rails.env.test?)
   end
 
   def public_fetch_mode?
@@ -68,7 +69,29 @@ class ApplicationController < ActionController::Base
   end
 
   def require_functional!
-    redirect_to edit_user_registration_path unless current_user.functional?
+    redirect_to edit_user_registration_path unless current_user&.functional?
+  end
+
+  def require_authenticated!
+    return if current_account?
+
+    respond_to do |format|
+      format.any { redirect_to edit_user_registration_path }
+      format.json { forbidden }
+    end
+  end
+
+  def require_known!(account)
+    return if authenticated_or_following?(account)
+
+    respond_to do |format|
+      format.any { redirect_to edit_user_registration_path }
+      format.json { forbidden }
+    end
+  end
+
+  def require_following!(account)
+    forbidden unless following?(account)
   end
 
   def after_sign_out_path_for(_resource_or_scope)
@@ -197,7 +220,7 @@ class ApplicationController < ActionController::Base
   def current_account
     return @current_account if defined?(@current_account)
 
-    @current_account = current_user&.account
+    @current_account = current_user&.account.presence || signed_request_account
   end
 
   def current_session
@@ -225,4 +248,21 @@ class ApplicationController < ActionController::Base
       format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
     end
   end
+
+  def following?(account)
+    return if account.blank?
+
+    @account_following ||= {}
+    return @account_following[account.id] if @account_following[account.id].present?
+
+    @account_following[account.id] = current_account.present? && (current_account.id == account.id || current_account.following?(account))
+  end
+
+  def authenticated_or_following?(account)
+    current_user&.account.present? || following?(account)
+  end
+
+  def current_account?
+    current_account.present?
+  end
 end
diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb
index 96d973394..55975b274 100644
--- a/app/controllers/auth/registrations_controller.rb
+++ b/app/controllers/auth/registrations_controller.rb
@@ -35,6 +35,12 @@ class Auth::RegistrationsController < Devise::RegistrationsController
     end
   end
 
+  def create
+    super do |resource|
+      return redirect_to root_path if resource.destroyed?
+    end
+  end
+
   protected
 
   def update_resource(resource, params)
@@ -55,7 +61,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
 
   def configure_sign_up_params
     devise_parameter_sanitizer.permit(:sign_up) do |u|
-      u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement)
+      u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :username, :email, :password, :password_confirmation, :kobold, :invite_code, :agreement)
     end
   end
 
diff --git a/app/controllers/concerns/account_owned_concern.rb b/app/controllers/concerns/account_owned_concern.rb
index 460f71f65..65168efff 100644
--- a/app/controllers/concerns/account_owned_concern.rb
+++ b/app/controllers/concerns/account_owned_concern.rb
@@ -4,7 +4,7 @@ module AccountOwnedConcern
   extend ActiveSupport::Concern
 
   included do
-    before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json }
+    #before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json }
     before_action :set_account, if: :account_required?
     before_action :check_account_approval, if: :account_required?
     before_action :check_account_suspension, if: :account_required?
diff --git a/app/controllers/custom_emojis_controller.rb b/app/controllers/custom_emojis_controller.rb
new file mode 100644
index 000000000..0ef8d0a50
--- /dev/null
+++ b/app/controllers/custom_emojis_controller.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+class CustomEmojisController < ApplicationController
+  include Authorization
+  include AccountableConcern
+
+  layout 'admin'
+
+  before_action :authenticate_user!
+  before_action :set_pack
+  before_action :set_body_classes
+
+  def index
+    authorize :custom_emoji, :index?
+
+    @custom_emojis = filtered_custom_emojis.eager_load(:local_counterpart).page(params[:page])
+    @form          = Form::CustomEmojiBatch.new
+  end
+
+  def new
+    authorize :custom_emoji, :create?
+
+    @custom_emoji = CustomEmoji.new(account: current_account)
+  end
+
+  def create
+    authorize :custom_emoji, :create?
+
+    @custom_emoji = CustomEmoji.new(resource_params.merge(account: current_account))
+
+    if @custom_emoji.save
+      log_action :create, @custom_emoji
+      redirect_to custom_emojis_path, notice: I18n.t('admin.custom_emojis.created_msg')
+    else
+      render :new
+    end
+  end
+
+  def batch
+    @form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
+    @form.save
+  rescue ActionController::ParameterMissing
+    flash[:alert] = I18n.t('admin.accounts.no_account_selected')
+  rescue Mastodon::NotPermittedError
+    flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
+  ensure
+    redirect_to custom_emojis_path(filter_params)
+  end
+
+  private
+
+  def resource_params
+    params.require(:custom_emoji).permit(:shortcode, :image, :visible_in_picker)
+  end
+
+  def filtered_custom_emojis
+    CustomEmojiFilter.new(filter_params, current_account).results
+  end
+
+  def filter_params
+    params.slice(:page, *CustomEmojiFilter::KEYS).permit(:page, *CustomEmojiFilter::KEYS)
+  end
+
+  def action_from_button
+    if params[:update]
+      'update'
+    elsif params[:list]
+      'list'
+    elsif params[:unlist]
+      'unlist'
+    elsif params[:enable]
+      'enable'
+    elsif params[:disable]
+      'disable'
+    elsif params[:copy]
+      'copy'
+    elsif params[:delete]
+      'delete'
+    elsif params[:claim]
+      'claim'
+    elsif params[:unclaim]
+      'unclaim'
+    end
+  end
+
+  def form_custom_emoji_batch_params
+    params.require(:form_custom_emoji_batch).permit(:action, :category_id, :category_name, custom_emoji_ids: [])
+  end
+
+  def set_pack
+    use_pack 'settings'
+  end
+
+  def set_body_classes
+    @body_classes = 'admin'
+  end
+end
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index c9b840881..d15adbf62 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -47,7 +47,7 @@ class HomeController < ApplicationController
   end
 
   def default_redirect_path
-    if request.path.start_with?('/web') || whitelist_mode?
+    if request.path.start_with?('/web') #|| whitelist_mode?
       new_user_session_path
     elsif single_user_mode?
       short_account_path(Account.local.without_suspended.where('id > 0').first)
diff --git a/app/controllers/matrix/base_controller.rb b/app/controllers/matrix/base_controller.rb
new file mode 100644
index 000000000..5922501ec
--- /dev/null
+++ b/app/controllers/matrix/base_controller.rb
@@ -0,0 +1,100 @@
+# frozen_string_literal: true
+
+class Matrix::BaseController < ApplicationController
+  include RateLimitHeaders
+
+  skip_before_action :store_current_location
+  skip_before_action :require_functional!
+
+  before_action :set_cache_headers
+
+  protect_from_forgery with: :null_session
+
+  skip_around_action :set_locale
+
+  rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
+    render json: { success: false, error: e.to_s }, status: 422
+  end
+
+  rescue_from ActiveRecord::RecordNotUnique do
+    render json: { success: false, error: 'Duplicate record' }, status: 422
+  end
+
+  rescue_from ActiveRecord::RecordNotFound do
+    render json: { success: false, error: 'Record not found' }, status: 404
+  end
+
+  rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
+    render json: { success: false, error: 'Remote data could not be fetched' }, status: 503
+  end
+
+  rescue_from OpenSSL::SSL::SSLError do
+    render json: { success: false, error: 'Remote SSL certificate could not be verified' }, status: 503
+  end
+
+  rescue_from Mastodon::NotPermittedError do
+    render json: { success: false, error: 'This action is not allowed' }, status: 403
+  end
+
+  rescue_from Mastodon::RaceConditionError do
+    render json: { success: false, error: 'There was a temporary problem serving your request, please try again' }, status: 503
+  end
+
+  rescue_from Mastodon::RateLimitExceededError do
+    render json: { auth: { success: false }, success: false, error: I18n.t('errors.429') }, status: 429
+  end
+
+  rescue_from ActionController::ParameterMissing do |e|
+    render json: { success: false, error: e.to_s }, status: 400
+  end
+
+  def doorkeeper_unauthorized_render_options(error: nil)
+    { json: { success: false, error: (error.try(:description) || 'Not authorized') } }
+  end
+
+  def doorkeeper_forbidden_render_options(*)
+    { json: { success: false, error: 'This action is outside the authorized scopes' } }
+  end
+
+  protected
+
+  def current_resource_owner
+    @current_user ||= User.find(doorkeeper_token.resource_owner_id) if doorkeeper_token
+  end
+
+  def current_user
+    current_resource_owner || super
+  rescue ActiveRecord::RecordNotFound
+    nil
+  end
+
+  def require_authenticated_user!
+    render json: { success: false, error: 'This method requires an authenticated user' }, status: 401 unless current_user
+  end
+
+  def require_user!
+    if !current_user
+      render json: { success: false, error: 'This method requires an authenticated user' }, status: 422
+    elsif current_user.disabled?
+      render json: { success: false, error: 'Your login is currently disabled' }, status: 403
+    elsif !current_user.confirmed?
+      render json: { success: false, error: 'Your login is missing a confirmed e-mail address' }, status: 403
+    elsif !current_user.approved?
+      render json: { success: false, error: 'Your login is currently pending approval' }, status: 403
+    else
+      set_user_activity
+    end
+  end
+
+  def render_empty
+    render json: {}, status: 200
+  end
+
+  def authorize_if_got_token!(*scopes)
+    doorkeeper_authorize!(*scopes) if doorkeeper_token
+  end
+
+  def set_cache_headers
+    response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
+  end
+end
diff --git a/app/controllers/matrix/identity/v1/check_credentials_controller.rb b/app/controllers/matrix/identity/v1/check_credentials_controller.rb
new file mode 100644
index 000000000..1770c6767
--- /dev/null
+++ b/app/controllers/matrix/identity/v1/check_credentials_controller.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+class Matrix::Identity::V1::CheckCredentialsController < Matrix::BaseController
+  def create
+    matrix_profile = matrix_profile_json
+    return render json: fail_json if matrix_profile.blank?
+
+    render json: matrix_profile
+  rescue ActionController::ParameterMissing, ActiveRecord::RecordNotFound
+    render json: fail_json
+  end
+
+  private
+
+  def resource_params
+    params.require(:user).permit(:id, :password)
+  end
+
+  def matrix_domains
+    ENV.fetch('MATRIX_AUTH_DOMAINS', '').delete(',').split.to_set
+  end
+
+  def matrix_profile_json
+    user_params = resource_params
+    return unless user_params[:id].present? && user_params[:password].present? && user_params[:id][0] == '@'
+
+    (username, domain) = user_params[:id].downcase.split(':', 2)
+    return unless matrix_domains.include?(domain)
+
+    user = User.find_by_lower_username!(username[1..-1])
+    return unless user.valid_password?(user_params[:password])
+
+    {
+      auth: {
+        success: true,
+        mxid: "@#{username}:#{domain}",
+        profile: {
+          display_name: user.account.display_name.presence || user.username,
+          three_pids: [
+            {
+              medium: 'email',
+              address: user.email,
+            },
+          ]
+        }
+      }
+    }
+  end
+
+  def fail_json
+    { auth: { success: false } }
+  end
+end
diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb
index 772fc42cb..db8ccd173 100644
--- a/app/controllers/media_controller.rb
+++ b/app/controllers/media_controller.rb
@@ -4,9 +4,9 @@ class MediaController < ApplicationController
   include Authorization
 
   skip_before_action :store_current_location
-  skip_before_action :require_functional!, unless: :whitelist_mode?
+  skip_before_action :require_functional! #, unless: :whitelist_mode?
 
-  before_action :authenticate_user!, if: :whitelist_mode?
+  #before_action :authenticate_user!, if: :whitelist_mode?
   before_action :set_media_attachment
   before_action :verify_permitted_status!
   before_action :check_playable, only: :player
@@ -33,6 +33,7 @@ class MediaController < ApplicationController
 
   def verify_permitted_status!
     authorize @media_attachment.status, :show?
+    authorize @media_attachment.status.reblog, :show? if @media_attachment.status.reblog?
   rescue Mastodon::NotPermittedError
     not_found
   end
diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb
index 0b1d09de9..ee7568a33 100644
--- a/app/controllers/media_proxy_controller.rb
+++ b/app/controllers/media_proxy_controller.rb
@@ -7,7 +7,7 @@ class MediaProxyController < ApplicationController
   skip_before_action :store_current_location
   skip_before_action :require_functional!
 
-  before_action :authenticate_user!, if: :whitelist_mode?
+  #before_action :authenticate_user!, if: :whitelist_mode?
 
   rescue_from ActiveRecord::RecordInvalid, with: :not_found
   rescue_from Mastodon::UnexpectedResponseError, with: :not_found
@@ -19,6 +19,7 @@ class MediaProxyController < ApplicationController
       if lock.acquired?
         @media_attachment = MediaAttachment.remote.attached.find(params[:id])
         authorize @media_attachment.status, :show?
+        authorize @media_attachment.status.reblog, :show? if @media_attachment.status.reblog?
         redownload! if @media_attachment.needs_redownload? && !reject_media?
       else
         raise Mastodon::RaceConditionError
diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb
index a277bfa10..5ead3aaa0 100644
--- a/app/controllers/remote_interaction_controller.rb
+++ b/app/controllers/remote_interaction_controller.rb
@@ -5,13 +5,13 @@ class RemoteInteractionController < ApplicationController
 
   layout 'modal'
 
-  before_action :authenticate_user!, if: :whitelist_mode?
+  #before_action :authenticate_user!, if: :whitelist_mode?
   before_action :set_interaction_type
   before_action :set_status
   before_action :set_body_classes
   before_action :set_pack
 
-  skip_before_action :require_functional!, unless: :whitelist_mode?
+  skip_before_action :require_functional! #, unless: :whitelist_mode?
 
   def new
     @remote_follow = RemoteFollow.new(session_params)
diff --git a/app/controllers/settings/preferences/filters_controller.rb b/app/controllers/settings/preferences/filters_controller.rb
new file mode 100644
index 000000000..c58a698ef
--- /dev/null
+++ b/app/controllers/settings/preferences/filters_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Settings::Preferences::FiltersController < Settings::PreferencesController
+  private
+
+  def after_update_redirect_path
+    settings_preferences_filters_path
+  end
+end
diff --git a/app/controllers/settings/preferences/privacy_controller.rb b/app/controllers/settings/preferences/privacy_controller.rb
new file mode 100644
index 000000000..f447fa598
--- /dev/null
+++ b/app/controllers/settings/preferences/privacy_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Settings::Preferences::PrivacyController < Settings::PreferencesController
+  private
+
+  def after_update_redirect_path
+    settings_preferences_privacy_path
+  end
+end
diff --git a/app/controllers/settings/preferences/publishing_controller.rb b/app/controllers/settings/preferences/publishing_controller.rb
new file mode 100644
index 000000000..5b298d94d
--- /dev/null
+++ b/app/controllers/settings/preferences/publishing_controller.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class Settings::Preferences::PublishingController < Settings::PreferencesController
+  private
+
+  def after_update_redirect_path
+    settings_preferences_publishing_path
+  end
+end
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index 87431f8cf..0a7c53f83 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -4,7 +4,11 @@ class Settings::PreferencesController < Settings::BaseController
   def show; end
 
   def update
-    user_settings.update(user_settings_params.to_h)
+    old_home_reblogs = current_user.home_reblogs?
+  
+    if user_settings.update(user_settings_params.to_h)
+      ClearReblogsWorker.perform_async(current_user.account_id) unless old_home_reblogs == current_user.home_reblogs? || current_user.home_reblogs?
+    end
 
     if current_user.update(user_params)
       I18n.locale = current_user.locale
@@ -49,7 +53,6 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_noindex,
       :setting_hide_network,
       :setting_hide_followers_count,
-      :setting_aggregate_reblogs,
       :setting_show_application,
       :setting_advanced_layout,
       :setting_default_content_type,
@@ -57,6 +60,24 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_use_pending_items,
       :setting_trends,
       :setting_crop_images,
+      :setting_manual_publish,
+      :setting_style_dashed_nest,
+      :setting_style_underline_a,
+      :setting_style_css_profile,
+      :setting_style_css_webapp,
+      :setting_style_wide_media,
+      :setting_publish_in,
+      :setting_unpublish_in,
+      :setting_unpublish_delete,
+      :setting_boost_every,
+      :setting_boost_jitter,
+      :setting_boost_random,
+      :setting_filter_unknown,
+      :setting_unpublish_on_delete,
+      :setting_rss_disabled,
+      :setting_home_reblogs,
+      :setting_max_history_public,
+      :setting_max_history_private,
       notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
       interactions: %i(must_be_follower must_be_following must_be_following_dm)
     )
diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb
index 0c15447a6..541ba2d5d 100644
--- a/app/controllers/settings/profiles_controller.rb
+++ b/app/controllers/settings/profiles_controller.rb
@@ -20,7 +20,9 @@ class Settings::ProfilesController < Settings::BaseController
   private
 
   def account_params
-    params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable, fields_attributes: [:name, :value])
+    params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable,
+                                    :require_dereference, :show_replies, :show_unlisted, :private, :require_auth,
+                                    fields_attributes: [:name, :value])
   end
 
   def set_account
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index a6ab8828f..6f8e74414 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -8,7 +8,9 @@ class StatusesController < ApplicationController
 
   layout 'public'
 
-  before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
+  before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? && current_user&.account_id != @account.id }
+  before_action :require_authenticated!, if: -> { @account.require_auth? }
+  before_action -> { require_following!(@account) }, if: -> { request.format != :json && @account.private? }
   before_action :set_status
   before_action :set_instance_presenter
   before_action :set_link_headers
@@ -19,7 +21,7 @@ class StatusesController < ApplicationController
   before_action :set_autoplay, only: :embed
 
   skip_around_action :set_locale, if: -> { request.format == :json }
-  skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
+  skip_before_action :require_functional!, only: [:show, :embed] # , unless: :whitelist_mode?
 
   content_security_policy only: :embed do |p|
     p.frame_ancestors(false)
@@ -37,14 +39,18 @@ class StatusesController < ApplicationController
 
       format.json do
         expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
-        render_with_cache 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, target_domain: current_account&.domain
       end
     end
   end
 
   def activity
     expires_in 3.minutes, public: @status.distributable? && public_fetch_mode?
-    render_with_cache json: ActivityPub::ActivityPresenter.from_status(@status), content_type: 'application/activity+json', serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
+    render_with_cache json: ActivityPub::ActivityPresenter.from_status(@status),
+                      content_type: 'application/activity+json',
+                      serializer: ActivityPub::ActivitySerializer,
+                      adapter: ActivityPub::Adapter,
+                      target_domain: current_account&.domain
   end
 
   def embed
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 64736e77f..d8b6019f5 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -9,14 +9,14 @@ class TagsController < ApplicationController
   layout 'public'
 
   before_action :require_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
-  before_action :authenticate_user!, if: :whitelist_mode?
+  # before_action :authenticate_user!, if: :whitelist_mode?
   before_action :set_local
   before_action :set_tag
   before_action :set_statuses
   before_action :set_body_classes
   before_action :set_instance_presenter
 
-  skip_before_action :require_functional!, unless: :whitelist_mode?
+  skip_before_action :require_functional! # , unless: :whitelist_mode?
 
   def show
     respond_to do |format|
@@ -32,7 +32,7 @@ class TagsController < ApplicationController
 
       format.json do
         expires_in 3.minutes, public: public_fetch_mode?
-        render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
+        render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: current_account&.domain
       end
     end
   end
diff --git a/app/controllers/user_profile_css_controller.rb b/app/controllers/user_profile_css_controller.rb
new file mode 100644
index 000000000..0a0588e88
--- /dev/null
+++ b/app/controllers/user_profile_css_controller.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class UserProfileCssController < ApplicationController
+  skip_before_action :store_current_location
+  skip_before_action :require_functional!
+
+  before_action :set_cache_headers
+  before_action :set_account
+
+  def show
+    expires_in 3.minutes, public: true
+    render plain: css, content_type: 'text/css'
+  end
+
+  private
+
+  def css
+    @account.user&.setting_style_css_profile_errors.blank? ? (@account.user&.setting_style_css_profile || '') : ''
+  end
+
+  def set_account
+    @account = Account.find(params[:id])
+  end
+end
diff --git a/app/controllers/user_webapp_css_controller.rb b/app/controllers/user_webapp_css_controller.rb
new file mode 100644
index 000000000..b2baa2843
--- /dev/null
+++ b/app/controllers/user_webapp_css_controller.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+class UserWebappCssController < ApplicationController
+  skip_before_action :store_current_location
+  skip_before_action :require_functional!
+
+  before_action :set_cache_headers
+  before_action :set_account
+
+  def show
+    expires_in 3.minutes, public: false
+    render plain: css, content_type: 'text/css'
+  end
+
+  private
+
+  def css_dashed_nest
+    return unless @account.user&.setting_style_dashed_nest
+
+    %(
+      div[data-nest-level]
+      { border-style: dashed; }
+    )
+  end
+
+  def css_underline_a
+    return unless @account.user&.setting_style_underline_a
+
+    %(
+      .status__content__text a,
+      .reply-indicator__content a,
+      .composer--reply > .content a,
+      .account__header__content a
+      { text-decoration: underline; }
+
+      .status__content__text a:hover,
+      .reply-indicator__content a:hover,
+      .composer--reply > .content a:hover,
+      .account__header__content a:hover
+      { text-decoration: none; }
+    )
+  end
+
+  def css_wide_media
+    return unless @account.user&.setting_style_wide_media
+
+    %(
+      .media-gallery
+      { height: auto !important; }
+
+      .media-gallery__item
+      { width: 100% !important; }
+
+      .spoiler-button + .media-gallery__item
+      { height: 5em !important; }
+
+      .spoiler-button--minified + .media-gallery__item
+      { height: 280px !important; }
+    )
+  end
+
+  def css_webapp
+    @account.user&.setting_style_css_webapp_errors.blank? ? (@account.user&.setting_style_css_webapp || '') : ''
+  end
+
+  def css
+    "#{css_dashed_nest}\n#{css_underline_a}\n#{css_wide_media}\n#{css_webapp}".squish
+  end
+
+  def set_account
+    @account = Account.find(params[:id])
+  end
+end