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/admin/confirmations_controller.rb19
-rw-r--r--app/controllers/admin/reported_statuses_controller.rb19
-rw-r--r--app/controllers/admin/statuses_controller.rb35
-rw-r--r--app/controllers/api/v1/accounts/credentials_controller.rb2
-rw-r--r--app/controllers/api/v1/accounts/follower_accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/accounts/following_accounts_controller.rb2
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb16
-rw-r--r--app/controllers/api/v1/push/subscriptions_controller.rb56
-rw-r--r--app/controllers/api/v1/statuses/pins_controller.rb4
-rw-r--r--app/controllers/api/v1/statuses_controller.rb10
-rw-r--r--app/controllers/api/v1/timelines/direct_controller.rb15
-rw-r--r--app/controllers/api/v1/trends_controller.rb17
-rw-r--r--app/controllers/api/v2/search_controller.rb8
-rw-r--r--app/controllers/api/web/push_subscriptions_controller.rb11
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/concerns/session_tracking_concern.rb22
-rw-r--r--app/controllers/concerns/signature_verification.rb4
-rw-r--r--app/controllers/follower_accounts_controller.rb34
-rw-r--r--app/controllers/following_accounts_controller.rb34
-rw-r--r--app/controllers/invites_controller.rb10
-rw-r--r--app/controllers/media_proxy_controller.rb2
-rw-r--r--app/controllers/oauth/authorized_applications_controller.rb5
-rw-r--r--app/controllers/oauth/tokens_controller.rb14
-rw-r--r--app/controllers/settings/applications_controller.rb2
-rw-r--r--app/controllers/settings/preferences_controller.rb1
-rw-r--r--app/controllers/settings/profiles_controller.rb3
-rw-r--r--app/controllers/shares_controller.rb1
27 files changed, 250 insertions, 104 deletions
diff --git a/app/controllers/admin/confirmations_controller.rb b/app/controllers/admin/confirmations_controller.rb
index 34dfb458e..8d3477e66 100644
--- a/app/controllers/admin/confirmations_controller.rb
+++ b/app/controllers/admin/confirmations_controller.rb
@@ -3,6 +3,7 @@
 module Admin
   class ConfirmationsController < BaseController
     before_action :set_user
+    before_action :check_confirmation, only: [:resend]
 
     def create
       authorize @user, :confirm?
@@ -11,10 +12,28 @@ module Admin
       redirect_to admin_accounts_path
     end
 
+    def resend
+      authorize @user, :confirm?
+
+      @user.resend_confirmation_instructions
+
+      log_action :confirm, @user
+
+      flash[:notice] = I18n.t('admin.accounts.resend_confirmation.success')
+      redirect_to admin_accounts_path
+    end
+
     private
 
     def set_user
       @user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
     end
+
+    def check_confirmation
+      if @user.confirmed?
+        flash[:error] = I18n.t('admin.accounts.resend_confirmation.already_confirmed')
+        redirect_to admin_accounts_path
+      end
+    end
   end
 end
diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb
index 522f68c98..d3c2f5e9e 100644
--- a/app/controllers/admin/reported_statuses_controller.rb
+++ b/app/controllers/admin/reported_statuses_controller.rb
@@ -3,7 +3,6 @@
 module Admin
   class ReportedStatusesController < BaseController
     before_action :set_report
-    before_action :set_status, only: [:update, :destroy]
 
     def create
       authorize :status, :update?
@@ -14,20 +13,6 @@ module Admin
       redirect_to admin_report_path(@report)
     end
 
-    def update
-      authorize @status, :update?
-      @status.update!(status_params)
-      log_action :update, @status
-      redirect_to admin_report_path(@report)
-    end
-
-    def destroy
-      authorize @status, :destroy?
-      RemovalWorker.perform_async(@status.id)
-      log_action :destroy, @status
-      render json: @status
-    end
-
     private
 
     def status_params
@@ -51,9 +36,5 @@ module Admin
     def set_report
       @report = Report.find(params[:report_id])
     end
-
-    def set_status
-      @status = @report.statuses.find(params[:id])
-    end
   end
 end
diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb
index d5787acfb..382bfc4a2 100644
--- a/app/controllers/admin/statuses_controller.rb
+++ b/app/controllers/admin/statuses_controller.rb
@@ -5,7 +5,6 @@ module Admin
     helper_method :current_params
 
     before_action :set_account
-    before_action :set_status, only: [:update, :destroy]
 
     PER_PAGE = 20
 
@@ -26,40 +25,18 @@ module Admin
     def create
       authorize :status, :update?
 
-      @form         = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account))
+      @form         = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
       flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
 
       redirect_to admin_account_statuses_path(@account.id, current_params)
     end
 
-    def update
-      authorize @status, :update?
-      @status.update!(status_params)
-      log_action :update, @status
-      redirect_to admin_account_statuses_path(@account.id, current_params)
-    end
-
-    def destroy
-      authorize @status, :destroy?
-      RemovalWorker.perform_async(@status.id)
-      log_action :destroy, @status
-      render json: @status
-    end
-
     private
 
-    def status_params
-      params.require(:status).permit(:sensitive)
-    end
-
     def form_status_batch_params
       params.require(:form_status_batch).permit(:action, status_ids: [])
     end
 
-    def set_status
-      @status = @account.statuses.find(params[:id])
-    end
-
     def set_account
       @account = Account.find(params[:account_id])
     end
@@ -72,5 +49,15 @@ module Admin
         page: page > 1 && page,
       }.select { |_, value| value.present? }
     end
+
+    def action_from_button
+      if params[:nsfw_on]
+        'nsfw_on'
+      elsif params[:nsfw_off]
+        'nsfw_off'
+      elsif params[:delete]
+        'delete'
+      end
+    end
   end
 end
diff --git a/app/controllers/api/v1/accounts/credentials_controller.rb b/app/controllers/api/v1/accounts/credentials_controller.rb
index a3c4008e6..259d07be8 100644
--- a/app/controllers/api/v1/accounts/credentials_controller.rb
+++ b/app/controllers/api/v1/accounts/credentials_controller.rb
@@ -21,7 +21,7 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
   private
 
   def account_params
-    params.permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value])
+    params.permit(:display_name, :note, :avatar, :header, :locked, :bot, fields_attributes: [:name, :value])
   end
 
   def user_settings_params
diff --git a/app/controllers/api/v1/accounts/follower_accounts_controller.rb b/app/controllers/api/v1/accounts/follower_accounts_controller.rb
index c4f600c54..4578cf6ca 100644
--- a/app/controllers/api/v1/accounts/follower_accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/follower_accounts_controller.rb
@@ -19,6 +19,8 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
   end
 
   def load_accounts
+    return [] if @account.user_hides_network? && current_account.id != @account.id
+
     default_accounts.merge(paginated_follows).to_a
   end
 
diff --git a/app/controllers/api/v1/accounts/following_accounts_controller.rb b/app/controllers/api/v1/accounts/following_accounts_controller.rb
index 90b1f7fc5..ce2bbda85 100644
--- a/app/controllers/api/v1/accounts/following_accounts_controller.rb
+++ b/app/controllers/api/v1/accounts/following_accounts_controller.rb
@@ -19,6 +19,8 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
   end
 
   def load_accounts
+    return [] if @account.user_hides_network? && current_account.id != @account.id
+
     default_accounts.merge(paginated_follows).to_a
   end
 
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index cbcc7ef04..c40155cb5 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -27,19 +27,17 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def account_statuses
-    default_statuses.tap do |statuses|
-      statuses.merge!(only_media_scope) if truthy_param?(:only_media)
-      statuses.merge!(pinned_scope) if truthy_param?(:pinned)
-      statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
-    end
-  end
-
-  def default_statuses
-    permitted_account_statuses.paginate_by_max_id(
+    statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
+    statuses = statuses.paginate_by_max_id(
       limit_param(DEFAULT_STATUSES_LIMIT),
       params[:max_id],
       params[:since_id]
     )
+
+    statuses.merge!(only_media_scope) if truthy_param?(:only_media)
+    statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies)
+
+    statuses
   end
 
   def permitted_account_statuses
diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb
new file mode 100644
index 000000000..1a19bd0ef
--- /dev/null
+++ b/app/controllers/api/v1/push/subscriptions_controller.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+class Api::V1::Push::SubscriptionsController < Api::BaseController
+  before_action -> { doorkeeper_authorize! :push }
+  before_action :require_user!
+  before_action :set_web_push_subscription
+
+  def create
+    @web_subscription&.destroy!
+
+    @web_subscription = ::Web::PushSubscription.create!(
+      endpoint: subscription_params[:endpoint],
+      key_p256dh: subscription_params[:keys][:p256dh],
+      key_auth: subscription_params[:keys][:auth],
+      data: data_params,
+      user_id: current_user.id,
+      access_token_id: doorkeeper_token.id
+    )
+
+    render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
+  end
+
+  def show
+    raise ActiveRecord::RecordNotFound if @web_subscription.nil?
+
+    render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
+  end
+
+  def update
+    raise ActiveRecord::RecordNotFound if @web_subscription.nil?
+
+    @web_subscription.update!(data: data_params)
+
+    render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
+  end
+
+  def destroy
+    @web_subscription&.destroy!
+    render_empty
+  end
+
+  private
+
+  def set_web_push_subscription
+    @web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
+  end
+
+  def subscription_params
+    params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
+  end
+
+  def data_params
+    return {} if params[:data].blank?
+    params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention])
+  end
+end
diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb
index bba6a6f48..54f8be667 100644
--- a/app/controllers/api/v1/statuses/pins_controller.rb
+++ b/app/controllers/api/v1/statuses/pins_controller.rb
@@ -39,7 +39,7 @@ class Api::V1::Statuses::PinsController < Api::BaseController
       adapter: ActivityPub::Adapter
     ).as_json
 
-    ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
+    ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account.id)
   end
 
   def distribute_remove_activity!
@@ -49,6 +49,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
       adapter: ActivityPub::Adapter
     ).as_json
 
-    ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account)
+    ActivityPub::RawDistributionWorker.perform_async(Oj.dump(json), current_account.id)
   end
 end
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 01880565c..289d91045 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -10,6 +10,12 @@ class Api::V1::StatusesController < Api::BaseController
 
   respond_to :json
 
+  # This API was originally unlimited, pagination cannot be introduced without
+  # breaking backwards-compatibility. Arbitrarily high number to cover most
+  # conversations as quasi-unlimited, it would be too much work to render more
+  # than this anyway
+  CONTEXT_LIMIT = 4_096
+
   def show
     cached  = Rails.cache.read(@status.cache_key)
     @status = cached unless cached.nil?
@@ -17,8 +23,8 @@ class Api::V1::StatusesController < Api::BaseController
   end
 
   def context
-    ancestors_results   = @status.in_reply_to_id.nil? ? [] : @status.ancestors(DEFAULT_STATUSES_LIMIT, current_account)
-    descendants_results = @status.descendants(DEFAULT_STATUSES_LIMIT, current_account)
+    ancestors_results   = @status.in_reply_to_id.nil? ? [] : @status.ancestors(CONTEXT_LIMIT, current_account)
+    descendants_results = @status.descendants(CONTEXT_LIMIT, current_account)
     loaded_ancestors    = cache_collection(ancestors_results, Status)
     loaded_descendants  = cache_collection(descendants_results, Status)
 
diff --git a/app/controllers/api/v1/timelines/direct_controller.rb b/app/controllers/api/v1/timelines/direct_controller.rb
index d455227eb..ef64078be 100644
--- a/app/controllers/api/v1/timelines/direct_controller.rb
+++ b/app/controllers/api/v1/timelines/direct_controller.rb
@@ -23,15 +23,18 @@ class Api::V1::Timelines::DirectController < Api::BaseController
   end
 
   def direct_statuses
-    direct_timeline_statuses.paginate_by_max_id(
-      limit_param(DEFAULT_STATUSES_LIMIT),
-      params[:max_id],
-      params[:since_id]
-    )
+    direct_timeline_statuses
   end
 
   def direct_timeline_statuses
-    Status.as_direct_timeline(current_account)
+    # this query requires built in pagination.
+    Status.as_direct_timeline(
+      current_account,
+      limit_param(DEFAULT_STATUSES_LIMIT),
+      params[:max_id],
+      params[:since_id],
+      true # returns array of cache_ids object
+    )
   end
 
   def insert_pagination_headers
diff --git a/app/controllers/api/v1/trends_controller.rb b/app/controllers/api/v1/trends_controller.rb
new file mode 100644
index 000000000..bcea9857e
--- /dev/null
+++ b/app/controllers/api/v1/trends_controller.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class Api::V1::TrendsController < Api::BaseController
+  before_action :set_tags
+
+  respond_to :json
+
+  def index
+    render json: @tags, each_serializer: REST::TagSerializer
+  end
+
+  private
+
+  def set_tags
+    @tags = TrendingTags.get(limit_param(10))
+  end
+end
diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb
new file mode 100644
index 000000000..2e91d68ee
--- /dev/null
+++ b/app/controllers/api/v2/search_controller.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+class Api::V2::SearchController < Api::V1::SearchController
+  def index
+    @search = Search.new(search)
+    render json: @search, serializer: REST::V2::SearchSerializer
+  end
+end
diff --git a/app/controllers/api/web/push_subscriptions_controller.rb b/app/controllers/api/web/push_subscriptions_controller.rb
index 249e7c186..fe8e42580 100644
--- a/app/controllers/api/web/push_subscriptions_controller.rb
+++ b/app/controllers/api/web/push_subscriptions_controller.rb
@@ -31,22 +31,23 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
       endpoint: subscription_params[:endpoint],
       key_p256dh: subscription_params[:keys][:p256dh],
       key_auth: subscription_params[:keys][:auth],
-      data: data
+      data: data,
+      user_id: active_session.user_id,
+      access_token_id: active_session.access_token_id
     )
 
     active_session.update!(web_push_subscription: web_subscription)
 
-    render json: web_subscription.as_payload
+    render json: web_subscription, serializer: REST::WebPushSubscriptionSerializer
   end
 
   def update
     params.require([:id])
 
     web_subscription = ::Web::PushSubscription.find(params[:id])
-
     web_subscription.update!(data: data_params)
 
-    render json: web_subscription.as_payload
+    render json: web_subscription, serializer: REST::WebPushSubscriptionSerializer
   end
 
   private
@@ -56,6 +57,6 @@ class Api::Web::PushSubscriptionsController < Api::Web::BaseController
   end
 
   def data_params
-    @data_params ||= params.require(:data).permit(:alerts)
+    @data_params ||= params.require(:data).permit(alerts: [:follow, :favourite, :reblog, :mention])
   end
 end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 158c0c10e..cc92894a5 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -9,6 +9,7 @@ class ApplicationController < ActionController::Base
 
   include Localized
   include UserTrackingConcern
+  include SessionTrackingConcern
 
   helper_method :current_account
   helper_method :current_session
@@ -20,6 +21,7 @@ class ApplicationController < ActionController::Base
   rescue_from ActionController::RoutingError, with: :not_found
   rescue_from ActiveRecord::RecordNotFound, with: :not_found
   rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
+  rescue_from ActionController::UnknownFormat, with: :not_acceptable
   rescue_from Mastodon::NotPermittedError, with: :forbidden
 
   before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
@@ -142,6 +144,10 @@ class ApplicationController < ActionController::Base
     respond_with_error(422)
   end
 
+  def not_acceptable
+    respond_with_error(406)
+  end
+
   def single_user_mode?
     @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
   end
diff --git a/app/controllers/concerns/session_tracking_concern.rb b/app/controllers/concerns/session_tracking_concern.rb
new file mode 100644
index 000000000..45361b019
--- /dev/null
+++ b/app/controllers/concerns/session_tracking_concern.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module SessionTrackingConcern
+  extend ActiveSupport::Concern
+
+  UPDATE_SIGN_IN_HOURS = 24
+
+  included do
+    before_action :set_session_activity
+  end
+
+  private
+
+  def set_session_activity
+    return unless session_needs_update?
+    current_session.touch
+  end
+
+  def session_needs_update?
+    !current_session.nil? && current_session.updated_at < UPDATE_SIGN_IN_HOURS.hours.ago
+  end
+end
diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb
index f289228d3..41aa1c8a6 100644
--- a/app/controllers/concerns/signature_verification.rb
+++ b/app/controllers/concerns/signature_verification.rb
@@ -107,9 +107,7 @@ module SignatureVerification
 
   def incompatible_signature?(signature_params)
     signature_params['keyId'].blank? ||
-      signature_params['signature'].blank? ||
-      signature_params['algorithm'].blank? ||
-      signature_params['algorithm'] != 'rsa-sha256'
+      signature_params['signature'].blank?
   end
 
   def account_from_key_id(key_id)
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index c74d3f86d..f5670c6bf 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -4,16 +4,19 @@ class FollowerAccountsController < ApplicationController
   include AccountControllerConcern
 
   def index
-    @follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
-
     respond_to do |format|
       format.html do
         use_pack 'public'
 
-        @relationships = AccountRelationshipsPresenter.new(@follows.map(&:account_id), current_user.account_id) if user_signed_in?
+        next if @account.user_hides_network?
+
+        follows
+        @relationships = AccountRelationshipsPresenter.new(follows.map(&:account_id), current_user.account_id) if user_signed_in?
       end
 
       format.json do
+        raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
+
         render json: collection_presenter,
                serializer: ActivityPub::CollectionSerializer,
                adapter: ActivityPub::Adapter,
@@ -24,28 +27,31 @@ class FollowerAccountsController < ApplicationController
 
   private
 
+  def follows
+    @follows ||= Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
+  end
+
   def page_url(page)
     account_followers_url(@account, page: page) unless page.nil?
   end
 
   def collection_presenter
-    page = ActivityPub::CollectionPresenter.new(
-      id: account_followers_url(@account, page: params.fetch(:page, 1)),
-      type: :ordered,
-      size: @account.followers_count,
-      items: @follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
-      part_of: account_followers_url(@account),
-      next: page_url(@follows.next_page),
-      prev: page_url(@follows.prev_page)
-    )
     if params[:page].present?
-      page
+      ActivityPub::CollectionPresenter.new(
+        id: account_followers_url(@account, page: params.fetch(:page, 1)),
+        type: :ordered,
+        size: @account.followers_count,
+        items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) },
+        part_of: account_followers_url(@account),
+        next: page_url(follows.next_page),
+        prev: page_url(follows.prev_page)
+      )
     else
       ActivityPub::CollectionPresenter.new(
         id: account_followers_url(@account),
         type: :ordered,
         size: @account.followers_count,
-        first: page
+        first: page_url(1)
       )
     end
   end
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index 4c1e3f327..098b2a20c 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -4,16 +4,19 @@ class FollowingAccountsController < ApplicationController
   include AccountControllerConcern
 
   def index
-    @follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
-
     respond_to do |format|
       format.html do
         use_pack 'public'
 
-        @relationships = AccountRelationshipsPresenter.new(@follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
+        next if @account.user_hides_network?
+
+        follows
+        @relationships = AccountRelationshipsPresenter.new(follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
       end
 
       format.json do
+        raise Mastodon::NotPermittedError if params[:page].present? && @account.user_hides_network?
+
         render json: collection_presenter,
                serializer: ActivityPub::CollectionSerializer,
                adapter: ActivityPub::Adapter,
@@ -24,28 +27,31 @@ class FollowingAccountsController < ApplicationController
 
   private
 
+  def follows
+    @follows ||= Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
+  end
+
   def page_url(page)
     account_following_index_url(@account, page: page) unless page.nil?
   end
 
   def collection_presenter
-    page = ActivityPub::CollectionPresenter.new(
-      id: account_following_index_url(@account, page: params.fetch(:page, 1)),
-      type: :ordered,
-      size: @account.following_count,
-      items: @follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) },
-      part_of: account_following_index_url(@account),
-      next: page_url(@follows.next_page),
-      prev: page_url(@follows.prev_page)
-    )
     if params[:page].present?
-      page
+      ActivityPub::CollectionPresenter.new(
+        id: account_following_index_url(@account, page: params.fetch(:page, 1)),
+        type: :ordered,
+        size: @account.following_count,
+        items: follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) },
+        part_of: account_following_index_url(@account),
+        next: page_url(follows.next_page),
+        prev: page_url(follows.prev_page)
+      )
     else
       ActivityPub::CollectionPresenter.new(
         id: account_following_index_url(@account),
         type: :ordered,
         size: @account.following_count,
-        first: page
+        first: page_url(1)
       )
     end
   end
diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb
index 189e4072e..70818610c 100644
--- a/app/controllers/invites_controller.rb
+++ b/app/controllers/invites_controller.rb
@@ -11,7 +11,7 @@ class InvitesController < ApplicationController
   def index
     authorize :invite, :create?
 
-    @invites = Invite.where(user: current_user)
+    @invites = invites
     @invite  = Invite.new(expires_in: 1.day.to_i)
   end
 
@@ -24,13 +24,13 @@ class InvitesController < ApplicationController
     if @invite.save
       redirect_to invites_path
     else
-      @invites = Invite.where(user: current_user)
+      @invites = invites
       render :index
     end
   end
 
   def destroy
-    @invite = Invite.where(user: current_user).find(params[:id])
+    @invite = invites.find(params[:id])
     authorize @invite, :destroy?
     @invite.expire!
     redirect_to invites_path
@@ -42,6 +42,10 @@ class InvitesController < ApplicationController
     use_pack 'settings'
   end
 
+  def invites
+    Invite.where(user: current_user)
+  end
+
   def resource_params
     params.require(:invite).permit(:max_uses, :expires_in)
   end
diff --git a/app/controllers/media_proxy_controller.rb b/app/controllers/media_proxy_controller.rb
index 155670837..d820b257e 100644
--- a/app/controllers/media_proxy_controller.rb
+++ b/app/controllers/media_proxy_controller.rb
@@ -8,6 +8,8 @@ class MediaProxyController < ApplicationController
       if lock.acquired?
         @media_attachment = MediaAttachment.remote.find(params[:id])
         redownload! if @media_attachment.needs_redownload? && !reject_media?
+      else
+        raise Mastodon::RaceConditionError
       end
     end
 
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index f95d672ec..1e420b3e7 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -9,6 +9,11 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
 
   include Localized
 
+  def destroy
+    Web::PushSubscription.unsubscribe_for(params[:id], current_resource_owner)
+    super
+  end
+
   private
 
   def store_current_location
diff --git a/app/controllers/oauth/tokens_controller.rb b/app/controllers/oauth/tokens_controller.rb
new file mode 100644
index 000000000..fa6d58f25
--- /dev/null
+++ b/app/controllers/oauth/tokens_controller.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class Oauth::TokensController < Doorkeeper::TokensController
+  def revoke
+    unsubscribe_for_token if authorized? && token.accessible?
+    super
+  end
+
+  private
+
+  def unsubscribe_for_token
+    Web::PushSubscription.where(access_token_id: token.id).delete_all
+  end
+end
diff --git a/app/controllers/settings/applications_controller.rb b/app/controllers/settings/applications_controller.rb
index 35a6f7f9e..03c890a50 100644
--- a/app/controllers/settings/applications_controller.rb
+++ b/app/controllers/settings/applications_controller.rb
@@ -6,7 +6,7 @@ class Settings::ApplicationsController < Settings::BaseController
   before_action :prepare_scopes, only: [:create, :update]
 
   def index
-    @applications = current_user.applications.page(params[:page])
+    @applications = current_user.applications.order(id: :desc).page(params[:page])
   end
 
   def new
diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb
index c853b5ab7..425664d49 100644
--- a/app/controllers/settings/preferences_controller.rb
+++ b/app/controllers/settings/preferences_controller.rb
@@ -40,6 +40,7 @@ class Settings::PreferencesController < Settings::BaseController
       :setting_reduce_motion,
       :setting_system_font_ui,
       :setting_noindex,
+      :setting_hide_network,
       notification_emails: %i(follow follow_request reblog favourite mention digest),
       interactions: %i(must_be_follower must_be_following)
     )
diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb
index 2b8330f2e..918dbc6c6 100644
--- a/app/controllers/settings/profiles_controller.rb
+++ b/app/controllers/settings/profiles_controller.rb
@@ -17,6 +17,7 @@ class Settings::ProfilesController < Settings::BaseController
       ActivityPub::UpdateDistributionWorker.perform_async(@account.id)
       redirect_to settings_profile_path, notice: I18n.t('generic.changes_saved_msg')
     else
+      @account.build_fields
       render :show
     end
   end
@@ -24,7 +25,7 @@ class Settings::ProfilesController < Settings::BaseController
   private
 
   def account_params
-    params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, fields_attributes: [:name, :value])
+    params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, fields_attributes: [:name, :value])
   end
 
   def set_account
diff --git a/app/controllers/shares_controller.rb b/app/controllers/shares_controller.rb
index 3cbaccb35..4624c29a6 100644
--- a/app/controllers/shares_controller.rb
+++ b/app/controllers/shares_controller.rb
@@ -16,6 +16,7 @@ class SharesController < ApplicationController
 
   def initial_state_params
     text = [params[:title], params[:text], params[:url]].compact.join(' ')
+
     {
       settings: Web::Setting.find_by(user: current_user)&.data || {},
       push_subscription: current_account.user.web_push_subscription(current_session),