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/accounts_controller.rb4
-rw-r--r--app/controllers/activitypub/base_controller.rb4
-rw-r--r--app/controllers/activitypub/inboxes_controller.rb4
-rw-r--r--app/controllers/activitypub/replies_controller.rb2
-rw-r--r--app/controllers/admin/accounts_controller.rb7
-rw-r--r--app/controllers/admin/announcements_controller.rb2
-rw-r--r--app/controllers/api/base_controller.rb2
-rw-r--r--app/controllers/api/v1/admin/accounts_controller.rb8
-rw-r--r--app/controllers/api/v1/timelines/public_controller.rb4
-rw-r--r--app/controllers/auth/sessions_controller.rb22
-rw-r--r--app/controllers/concerns/account_owned_concern.rb20
-rw-r--r--app/controllers/concerns/sign_in_token_authentication_concern.rb18
-rw-r--r--app/controllers/concerns/signature_verification.rb16
-rw-r--r--app/controllers/concerns/two_factor_authentication_concern.rb35
-rw-r--r--app/controllers/concerns/user_tracking_concern.rb7
-rw-r--r--app/controllers/filters_controller.rb2
-rw-r--r--app/controllers/follower_accounts_controller.rb12
-rw-r--r--app/controllers/following_accounts_controller.rb12
-rw-r--r--app/controllers/relationships_controller.rb9
-rw-r--r--app/controllers/settings/deletes_controller.rb2
-rw-r--r--app/controllers/well_known/webfinger_controller.rb2
21 files changed, 149 insertions, 45 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 356542767..052394ab4 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -103,6 +103,10 @@ class AccountsController < ApplicationController
     params[:username]
   end
 
+  def skip_temporary_suspension_response?
+    request.format == :json
+  end
+
   def rss_url
     if tag_requested?
       short_account_tag_url(@account, params[:tag], format: 'rss')
diff --git a/app/controllers/activitypub/base_controller.rb b/app/controllers/activitypub/base_controller.rb
index 0c2591e97..4cbc3ab8f 100644
--- a/app/controllers/activitypub/base_controller.rb
+++ b/app/controllers/activitypub/base_controller.rb
@@ -8,4 +8,8 @@ class ActivityPub::BaseController < Api::BaseController
   def set_cache_headers
     response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
   end
+
+  def skip_temporary_suspension_response?
+    false
+  end
 end
diff --git a/app/controllers/activitypub/inboxes_controller.rb b/app/controllers/activitypub/inboxes_controller.rb
index fdb60d590..d3044f180 100644
--- a/app/controllers/activitypub/inboxes_controller.rb
+++ b/app/controllers/activitypub/inboxes_controller.rb
@@ -33,6 +33,10 @@ class ActivityPub::InboxesController < ActivityPub::BaseController
     params[:account_username].present?
   end
 
+  def skip_temporary_suspension_response?
+    true
+  end
+
   def body
     return @body if defined?(@body)
 
diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb
index 43bf4e657..fde6c861f 100644
--- a/app/controllers/activitypub/replies_controller.rb
+++ b/app/controllers/activitypub/replies_controller.rb
@@ -31,7 +31,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
   end
 
   def set_replies
-    @replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses
+    @replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses
     @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
     @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
   end
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index b9b75727d..1dd7430e0 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -53,6 +53,13 @@ module Admin
       redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
     end
 
+    def unsensitive
+      authorize @account, :unsensitive?
+      @account.unsensitize!
+      log_action :unsensitive, @account
+      redirect_to admin_account_path(@account.id)
+    end
+
     def unsilence
       authorize @account, :unsilence?
       @account.unsilence!
diff --git a/app/controllers/admin/announcements_controller.rb b/app/controllers/admin/announcements_controller.rb
index 494fd13d0..351b9a991 100644
--- a/app/controllers/admin/announcements_controller.rb
+++ b/app/controllers/admin/announcements_controller.rb
@@ -71,7 +71,7 @@ class Admin::AnnouncementsController < Admin::BaseController
   private
 
   def set_announcements
-    @announcements = AnnouncementFilter.new(filter_params).results.page(params[:page])
+    @announcements = AnnouncementFilter.new(filter_params).results.reverse_chronological.page(params[:page])
   end
 
   def set_announcement
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index e962c4e97..fe199e689 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -103,7 +103,7 @@ class Api::BaseController < ApplicationController
     elsif !current_user.functional?
       render json: { error: 'Your login is currently disabled' }, status: 403
     else
-      set_user_activity
+      update_user_sign_in
     end
   end
 
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index 3af572f25..63cc521ed 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -22,6 +22,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
     active
     pending
     disabled
+    sensitized
     silenced
     suspended
     username
@@ -68,6 +69,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
     render json: @account, serializer: REST::Admin::AccountSerializer
   end
 
+  def unsensitive
+    authorize @account, :unsensitive?
+    @account.unsensitize!
+    log_action :unsensitive, @account
+    render json: @account, serializer: REST::Admin::AccountSerializer
+  end
+
   def unsilence
     authorize @account, :unsilence?
     @account.unsilence!
diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb
index fbd99667c..493fe4776 100644
--- a/app/controllers/api/v1/timelines/public_controller.rb
+++ b/app/controllers/api/v1/timelines/public_controller.rb
@@ -38,7 +38,9 @@ class Api::V1::Timelines::PublicController < Api::BaseController
       local: truthy_param?(:local),
       remote: truthy_param?(:remote),
       only_media: truthy_param?(:only_media),
-      allow_local_only: truthy_param?(:allow_local_only)
+      allow_local_only: truthy_param?(:allow_local_only),
+      with_replies: Setting.show_replies_in_public_timelines,
+      with_reblogs: Setting.show_reblogs_in_public_timelines,
     )
   end
 
diff --git a/app/controllers/auth/sessions_controller.rb b/app/controllers/auth/sessions_controller.rb
index 1cf6a0a59..548832b21 100644
--- a/app/controllers/auth/sessions_controller.rb
+++ b/app/controllers/auth/sessions_controller.rb
@@ -7,6 +7,7 @@ class Auth::SessionsController < Devise::SessionsController
 
   skip_before_action :require_no_authentication, only: [:create]
   skip_before_action :require_functional!
+  skip_before_action :update_user_sign_in
 
   prepend_before_action :set_pack
 
@@ -26,6 +27,7 @@ class Auth::SessionsController < Devise::SessionsController
 
   def create
     super do |resource|
+      resource.update_sign_in!(request, new_sign_in: true)
       remember_me(resource)
       flash.delete(:notice)
     end
@@ -59,7 +61,7 @@ class Auth::SessionsController < Devise::SessionsController
 
   def find_user
     if session[:attempt_user_id]
-      User.find(session[:attempt_user_id])
+      User.find_by(id: session[:attempt_user_id])
     else
       user   = User.authenticate_with_ldap(user_params) if Devise.ldap_authentication
       user ||= User.authenticate_with_pam(user_params) if Devise.pam_authentication
@@ -92,6 +94,7 @@ class Auth::SessionsController < Devise::SessionsController
 
   def require_no_authentication
     super
+
     # Delete flash message that isn't entirely useful and may be confusing in
     # most cases because /web doesn't display/clear flash messages.
     flash.delete(:alert) if flash[:alert] == I18n.t('devise.failure.already_authenticated')
@@ -113,13 +116,30 @@ class Auth::SessionsController < Devise::SessionsController
 
   def home_paths(resource)
     paths = [about_path]
+
     if single_user_mode? && resource.is_a?(User)
       paths << short_account_path(username: resource.account)
     end
+
     paths
   end
 
   def continue_after?
     truthy_param?(:continue)
   end
+
+  def restart_session
+    clear_attempt_from_session
+    redirect_to new_user_session_path, alert: I18n.t('devise.failure.timeout')
+  end
+
+  def set_attempt_session(user)
+    session[:attempt_user_id]         = user.id
+    session[:attempt_user_updated_at] = user.updated_at.to_s
+  end
+
+  def clear_attempt_from_session
+    session.delete(:attempt_user_id)
+    session.delete(:attempt_user_updated_at)
+  end
 end
diff --git a/app/controllers/concerns/account_owned_concern.rb b/app/controllers/concerns/account_owned_concern.rb
index 460f71f65..62e379846 100644
--- a/app/controllers/concerns/account_owned_concern.rb
+++ b/app/controllers/concerns/account_owned_concern.rb
@@ -29,6 +29,24 @@ module AccountOwnedConcern
   end
 
   def check_account_suspension
-    expires_in(3.minutes, public: true) && gone if @account.suspended?
+    if @account.suspended_permanently?
+      permanent_suspension_response
+    elsif @account.suspended? && !skip_temporary_suspension_response?
+      temporary_suspension_response
+    end
+  end
+
+  def skip_temporary_suspension_response?
+    false
+  end
+
+  def permanent_suspension_response
+    expires_in(3.minutes, public: true)
+    gone
+  end
+
+  def temporary_suspension_response
+    expires_in(3.minutes, public: true)
+    forbidden
   end
 end
diff --git a/app/controllers/concerns/sign_in_token_authentication_concern.rb b/app/controllers/concerns/sign_in_token_authentication_concern.rb
index f5178930b..51ebcb115 100644
--- a/app/controllers/concerns/sign_in_token_authentication_concern.rb
+++ b/app/controllers/concerns/sign_in_token_authentication_concern.rb
@@ -18,7 +18,9 @@ module SignInTokenAuthenticationConcern
   def authenticate_with_sign_in_token
     user = self.resource = find_user
 
-    if user_params[:sign_in_token_attempt].present? && session[:attempt_user_id]
+    if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s
+      restart_session
+    elsif user_params.key?(:sign_in_token_attempt) && session[:attempt_user_id]
       authenticate_with_sign_in_token_attempt(user)
     elsif user.present? && user.external_or_valid_password?(user_params[:password])
       prompt_for_sign_in_token(user)
@@ -27,7 +29,7 @@ module SignInTokenAuthenticationConcern
 
   def authenticate_with_sign_in_token_attempt(user)
     if valid_sign_in_token_attempt?(user)
-      session.delete(:attempt_user_id)
+      clear_attempt_from_session
       remember_me(user)
       sign_in(user)
     else
@@ -42,11 +44,11 @@ module SignInTokenAuthenticationConcern
       UserMailer.sign_in_token(user, request.remote_ip, request.user_agent, Time.now.utc.to_s).deliver_later!
     end
 
-    set_locale do
-      session[:attempt_user_id] = user.id
-      use_pack 'auth'
-      @body_classes = 'lighter'
-      render :sign_in_token
-    end
+    set_attempt_session(user)
+    use_pack 'auth'
+
+    @body_classes = 'lighter'
+
+    set_locale { render :sign_in_token }
   end
 end
diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb
index f69c62ec2..fc3978fbb 100644
--- a/app/controllers/concerns/signature_verification.rb
+++ b/app/controllers/concerns/signature_verification.rb
@@ -76,6 +76,7 @@ module SignatureVerification
     raise SignatureVerificationError, 'Signed request date outside acceptable time window' unless matches_time_window?
 
     verify_signature_strength!
+    verify_body_digest!
 
     account = account_from_key_id(signature_params['keyId'])
 
@@ -126,10 +127,19 @@ module SignatureVerification
   def verify_signature_strength!
     raise SignatureVerificationError, 'Mastodon requires the Date header or (created) pseudo-header to be signed' unless signed_headers.include?('date') || signed_headers.include?('(created)')
     raise SignatureVerificationError, 'Mastodon requires the Digest header or (request-target) pseudo-header to be signed' unless signed_headers.include?(Request::REQUEST_TARGET) || signed_headers.include?('digest')
-    raise SignatureVerificationError, 'Mastodon requires the Host header to be signed' unless signed_headers.include?('host')
+    raise SignatureVerificationError, 'Mastodon requires the Host header to be signed when doing a GET request' if request.get? && !signed_headers.include?('host')
     raise SignatureVerificationError, 'Mastodon requires the Digest header to be signed when doing a POST request' if request.post? && !signed_headers.include?('digest')
   end
 
+  def verify_body_digest!
+    return unless signed_headers.include?('digest')
+
+    digests = request.headers['Digest'].split(',').map { |digest| digest.split('=', 2) }.map { |key, value| [key.downcase, value] }
+    sha256  = digests.assoc('sha-256')
+    raise SignatureVerificationError, "Mastodon only supports SHA-256 in Digest header. Offered algorithms: #{digests.map(&:first).join(', ')}" if sha256.nil?
+    raise SignatureVerificationError, "Invalid Digest value. Computed SHA-256 digest: #{body_digest}; given: #{sha256[1]}" if body_digest != sha256[1]
+  end
+
   def verify_signature(account, signature, compare_signed_string)
     if account.keypair.public_key.verify(OpenSSL::Digest.new('SHA256'), signature, compare_signed_string)
       @signed_request_account = account
@@ -153,8 +163,6 @@ module SignatureVerification
         raise SignatureVerificationError, 'Pseudo-header (expires) used but corresponding argument missing' if signature_params['expires'].blank?
 
         "(expires): #{signature_params['expires']}"
-      elsif signed_header == 'digest'
-        "digest: #{body_digest}"
       else
         "#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
       end
@@ -187,7 +195,7 @@ module SignatureVerification
   end
 
   def body_digest
-    "SHA-256=#{Digest::SHA256.base64digest(request_body)}"
+    @body_digest ||= Digest::SHA256.base64digest(request_body)
   end
 
   def to_header_name(name)
diff --git a/app/controllers/concerns/two_factor_authentication_concern.rb b/app/controllers/concerns/two_factor_authentication_concern.rb
index 6b043a804..4800db348 100644
--- a/app/controllers/concerns/two_factor_authentication_concern.rb
+++ b/app/controllers/concerns/two_factor_authentication_concern.rb
@@ -37,9 +37,11 @@ module TwoFactorAuthenticationConcern
   def authenticate_with_two_factor
     user = self.resource = find_user
 
-    if user.webauthn_enabled? && user_params[:credential].present? && session[:attempt_user_id]
+    if user.present? && session[:attempt_user_id].present? && session[:attempt_user_updated_at] != user.updated_at.to_s
+      restart_session
+    elsif user.webauthn_enabled? && user_params.key?(:credential) && session[:attempt_user_id]
       authenticate_with_two_factor_via_webauthn(user)
-    elsif user_params[:otp_attempt].present? && session[:attempt_user_id]
+    elsif user_params.key?(:otp_attempt) && session[:attempt_user_id]
       authenticate_with_two_factor_via_otp(user)
     elsif user.present? && user.external_or_valid_password?(user_params[:password])
       prompt_for_two_factor(user)
@@ -50,7 +52,7 @@ module TwoFactorAuthenticationConcern
     webauthn_credential = WebAuthn::Credential.from_get(user_params[:credential])
 
     if valid_webauthn_credential?(user, webauthn_credential)
-      session.delete(:attempt_user_id)
+      clear_attempt_from_session
       remember_me(user)
       sign_in(user)
       render json: { redirect_path: root_path }, status: :ok
@@ -61,7 +63,7 @@ module TwoFactorAuthenticationConcern
 
   def authenticate_with_two_factor_via_otp(user)
     if valid_otp_attempt?(user)
-      session.delete(:attempt_user_id)
+      clear_attempt_from_session
       remember_me(user)
       sign_in(user)
     else
@@ -71,17 +73,20 @@ module TwoFactorAuthenticationConcern
   end
 
   def prompt_for_two_factor(user)
-    set_locale do
-      session[:attempt_user_id] = user.id
-      use_pack 'auth'
-      @body_classes = 'lighter'
-      @webauthn_enabled = user.webauthn_enabled?
-      @scheme_type = if user.webauthn_enabled? && user_params[:otp_attempt].blank?
-                       'webauthn'
-                     else
-                       'totp'
-                     end
-      render :two_factor
+    set_attempt_session(user)
+
+    use_pack 'auth'
+
+    @body_classes     = 'lighter'
+    @webauthn_enabled = user.webauthn_enabled?
+    @scheme_type      = begin
+      if user.webauthn_enabled? && user_params[:otp_attempt].blank?
+        'webauthn'
+      else
+        'totp'
+      end
     end
+
+    set_locale { render :two_factor }
   end
 end
diff --git a/app/controllers/concerns/user_tracking_concern.rb b/app/controllers/concerns/user_tracking_concern.rb
index be10705fc..efda37fae 100644
--- a/app/controllers/concerns/user_tracking_concern.rb
+++ b/app/controllers/concerns/user_tracking_concern.rb
@@ -6,14 +6,13 @@ module UserTrackingConcern
   UPDATE_SIGN_IN_HOURS = 24
 
   included do
-    before_action :set_user_activity
+    before_action :update_user_sign_in
   end
 
   private
 
-  def set_user_activity
-    return unless user_needs_sign_in_update?
-    current_user.update_tracked_fields!(request)
+  def update_user_sign_in
+    current_user.update_sign_in!(request) if user_needs_sign_in_update?
   end
 
   def user_needs_sign_in_update?
diff --git a/app/controllers/filters_controller.rb b/app/controllers/filters_controller.rb
index 76be03e53..0d4c1b97c 100644
--- a/app/controllers/filters_controller.rb
+++ b/app/controllers/filters_controller.rb
@@ -10,7 +10,7 @@ class FiltersController < ApplicationController
   before_action :set_body_classes
 
   def index
-    @filters = current_account.custom_filters
+    @filters = current_account.custom_filters.order(:phrase)
   end
 
   def new
diff --git a/app/controllers/follower_accounts_controller.rb b/app/controllers/follower_accounts_controller.rb
index 5ffbdae79..18b281325 100644
--- a/app/controllers/follower_accounts_controller.rb
+++ b/app/controllers/follower_accounts_controller.rb
@@ -53,6 +53,14 @@ class FollowerAccountsController < ApplicationController
     account_followers_url(@account, page: page) unless page.nil?
   end
 
+  def next_page_url
+    page_url(follows.next_page) if follows.respond_to?(:next_page)
+  end
+
+  def prev_page_url
+    page_url(follows.prev_page) if follows.respond_to?(:prev_page)
+  end
+
   def collection_presenter
     options = { type: :ordered }
     options[:size] = @account.followers_count unless Setting.hide_followers_count || @account.user&.setting_hide_followers_count
@@ -61,8 +69,8 @@ class FollowerAccountsController < ApplicationController
         id: account_followers_url(@account, page: params.fetch(:page, 1)),
         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),
+        next: next_page_url,
+        prev: prev_page_url,
         **options
       )
     else
diff --git a/app/controllers/following_accounts_controller.rb b/app/controllers/following_accounts_controller.rb
index 69820ebb7..eba44d34b 100644
--- a/app/controllers/following_accounts_controller.rb
+++ b/app/controllers/following_accounts_controller.rb
@@ -53,6 +53,14 @@ class FollowingAccountsController < ApplicationController
     account_following_index_url(@account, page: page) unless page.nil?
   end
 
+  def next_page_url
+    page_url(follows.next_page) if follows.respond_to?(:next_page)
+  end
+
+  def prev_page_url
+    page_url(follows.prev_page) if follows.respond_to?(:prev_page)
+  end
+
   def collection_presenter
     if page_requested?
       ActivityPub::CollectionPresenter.new(
@@ -61,8 +69,8 @@ class FollowingAccountsController < ApplicationController
         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)
+        next: next_page_url,
+        prev: prev_page_url
       )
     else
       ActivityPub::CollectionPresenter.new(
diff --git a/app/controllers/relationships_controller.rb b/app/controllers/relationships_controller.rb
index f1ab980c8..d40770726 100644
--- a/app/controllers/relationships_controller.rb
+++ b/app/controllers/relationships_controller.rb
@@ -6,6 +6,7 @@ class RelationshipsController < ApplicationController
   before_action :authenticate_user!
   before_action :set_accounts, only: :show
   before_action :set_pack
+  before_action :set_relationships, only: :show
   before_action :set_body_classes
 
   helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
@@ -29,6 +30,10 @@ class RelationshipsController < ApplicationController
     @accounts = RelationshipFilter.new(current_account, filter_params).results.page(params[:page]).per(40)
   end
 
+  def set_relationships
+    @relationships = AccountRelationshipsPresenter.new(@accounts.pluck(:id), current_user.account_id)
+  end
+
   def form_account_batch_params
     params.require(:form_account_batch).permit(:action, account_ids: [])
   end
@@ -50,7 +55,9 @@ class RelationshipsController < ApplicationController
   end
 
   def action_from_button
-    if params[:unfollow]
+    if params[:follow]
+      'follow'
+    elsif params[:unfollow]
       'unfollow'
     elsif params[:remove_from_followers]
       'remove_from_followers'
diff --git a/app/controllers/settings/deletes_controller.rb b/app/controllers/settings/deletes_controller.rb
index f96c83b80..7b8f8d207 100644
--- a/app/controllers/settings/deletes_controller.rb
+++ b/app/controllers/settings/deletes_controller.rb
@@ -42,7 +42,7 @@ class Settings::DeletesController < Settings::BaseController
   end
 
   def destroy_account!
-    current_account.suspend!
+    current_account.suspend!(origin: :local)
     AccountDeletionWorker.perform_async(current_user.account_id)
     sign_out
   end
diff --git a/app/controllers/well_known/webfinger_controller.rb b/app/controllers/well_known/webfinger_controller.rb
index 9de9db6ba..0227f722a 100644
--- a/app/controllers/well_known/webfinger_controller.rb
+++ b/app/controllers/well_known/webfinger_controller.rb
@@ -35,7 +35,7 @@ module WellKnown
     end
 
     def check_account_suspension
-      expires_in(3.minutes, public: true) && gone if @account.suspended?
+      expires_in(3.minutes, public: true) && gone if @account.suspended_permanently?
     end
 
     def bad_request