about summary refs log tree commit diff
diff options
context:
space:
mode:
authorFire Demon <firedemon@creature.cafe>2020-07-28 20:40:25 -0500
committerFire Demon <firedemon@creature.cafe>2020-08-30 05:45:16 -0500
commit054e15e4f03eecb174374466581b9662a6b38e24 (patch)
tree80db06ea08762f659878d8ffe2ffb4f54333b9c6
parent9234fb32e6b2b8bf8fb2184f9b1b57202eb5f625 (diff)
[Privacy] Add options for private accounts
-rw-r--r--app/controllers/accounts_controller.rb24
-rw-r--r--app/controllers/activitypub/outboxes_controller.rb11
-rw-r--r--app/controllers/activitypub/replies_controller.rb8
-rw-r--r--app/controllers/api/v1/accounts/statuses_controller.rb6
-rw-r--r--app/controllers/application_controller.rb42
-rw-r--r--app/controllers/settings/profiles_controller.rb2
-rw-r--r--app/controllers/statuses_controller.rb8
-rw-r--r--app/controllers/tags_controller.rb12
-rw-r--r--app/lib/activitypub/adapter.rb2
-rw-r--r--app/models/account.rb2
-rw-r--r--app/models/status.rb2
-rw-r--r--app/policies/status_policy.rb6
-rw-r--r--app/serializers/activitypub/actor_serializer.rb4
-rw-r--r--app/services/activitypub/process_account_service.rb2
-rw-r--r--app/views/settings/profiles/show.html.haml12
-rw-r--r--config/locales/simple_form.en-MP.yml6
-rw-r--r--db/migrate/20200728171900_add_private_to_accounts.rb7
-rw-r--r--db/migrate/20200728173757_add_require_auth_to_accounts.rb7
-rw-r--r--db/schema.rb4
19 files changed, 123 insertions, 44 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 651da89ad..ebc472087 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -10,6 +10,8 @@ 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?
 
@@ -20,10 +22,10 @@ class AccountsController < ApplicationController
         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,7 +42,9 @@ class AccountsController < ApplicationController
       end
 
       format.rss do
-        expires_in 1.minute, public: !(user_signed_in? || signed_request_account.present?)
+        return forbidden if unauthorized?
+
+        expires_in 1.minute, public: !current_account?
 
         limit     = params[:limit].present? ? [params[:limit].to_i, PAGE_SIZE_MAX].min : PAGE_SIZE
         @statuses = filtered_statuses.without_reblogs.limit(limit)
@@ -49,7 +53,7 @@ class AccountsController < ApplicationController
       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, fields: restrict_fields_to
       end
     end
@@ -152,10 +156,18 @@ class AccountsController < ApplicationController
   end
 
   def restrict_fields_to
-    if signed_request_account.present? || public_fetch_mode?
+    if signed_request_account.present? && !blocked?
       # Return all fields
     else
       %i(id type preferred_username inbox public_key endpoints)
     end
   end
+
+  def blocked?
+    @blocked ||= current_account && @account.blocking?(current_account)
+  end
+
+  def unauthorized?
+    @unauthorized ||= blocked? || (@account.private? && !following?(@account))
+  end
 end
diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb
index 60f1c526b..c4c0ce0c9 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', target_domain: signed_request_account&.domain
+    render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: current_account&.domain
   end
 
   private
@@ -49,7 +52,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   def set_statuses
     return unless page_requested?
 
-    @statuses = if known_visitor?
+    @statuses = if authenticated_or_following?(@account)
                   @account.statuses.without_semiprivate.permitted_for(@account, signed_request_account)
                 else
                   @account.statuses.permitted_for(@account, signed_request_account, user_signed_in: true)
@@ -66,8 +69,4 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
   def page_params
     { page: true, max_id: params[:max_id], min_id: params[:min_id] }.compact
   end
-
-  def known_visitor?
-    @known_visitor ||= user_signed_in? || (signed_request_account.present? && signed_request_account.following?(@account))
-  end
 end
diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb
index cec571e8a..4d553fc07 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, target_domain: signed_request_account&.domain
+    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
@@ -33,7 +33,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
   def set_replies
     @replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses
     @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
-    @replies = @replies.without_semiprivate unless known_visitor?
+    @replies = @replies.without_semiprivate unless authenticated_or_following?(@account)
     @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
   end
 
@@ -78,8 +78,4 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
   def page_params
     params_slice(:only_other_accounts, :min_id).merge(page: true)
   end
-
-  def known_visitor?
-    @known_visitor ||= user_signed_in? || (signed_request_account.present? && signed_request_account.following?(@account))
-  end
 end
diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb
index 4735fea8c..1c744ad73 100644
--- a/app/controllers/api/v1/accounts/statuses_controller.rb
+++ b/app/controllers/api/v1/accounts/statuses_controller.rb
@@ -26,6 +26,8 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def account_statuses
+    return [] if (@account.private && !following?(@account)) || (@account.require_auth && !current_account?)
+
     statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses
 
     statuses.merge!(only_media_scope) if truthy_param?(:only_media)
@@ -37,7 +39,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
   end
 
   def permitted_account_statuses
-    @account.statuses.permitted_for(@account, current_account, user_signed_in: user_signed_in?)
+    @account.statuses.permitted_for(@account, current_account, user_signed_in: authenticated_or_following?(@account))
   end
 
   def only_media_scope
@@ -49,7 +51,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
     # Also, Avoid getting slow by not narrowing down by `statuses.account_id`.
     # When narrowing down by `statuses.account_id`, `index_statuses_20180106` will be used
     # and the table will be joined by `Merge Semi Join`, so the query will be slow.
-    @account.statuses.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account)
+    @account.statuses.joins(:media_attachments).merge(@account.media_attachments).permitted_for(@account, current_account, user_signed_in: authenticated_or_following?(@account))
             .paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
             .reorder(id: :desc).distinct(:id).pluck(:id)
   end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e996c2217..9608f1cf9 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
@@ -71,6 +72,28 @@ class ApplicationController < ActionController::Base
     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)
     new_user_session_path
   end
@@ -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.functional? || following?(account)
+  end
+
+  def current_account?
+    current_account.present?
+  end
 end
diff --git a/app/controllers/settings/profiles_controller.rb b/app/controllers/settings/profiles_controller.rb
index d6e3c9863..8c4efa21d 100644
--- a/app/controllers/settings/profiles_controller.rb
+++ b/app/controllers/settings/profiles_controller.rb
@@ -24,7 +24,7 @@ class Settings::ProfilesController < Settings::BaseController
 
   def account_params
     params.require(:account).permit(:display_name, :note, :avatar, :header, :locked, :bot, :discoverable,
-                                    :require_dereference, :show_replies, :show_unlisted,
+                                    :require_dereference, :show_replies, :show_unlisted, :private, :require_auth,
                                     fields_attributes: [:name, :value])
   end
 
diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb
index 23cbb8c37..5c977d212 100644
--- a/app/controllers/statuses_controller.rb
+++ b/app/controllers/statuses_controller.rb
@@ -9,6 +9,8 @@ class StatusesController < ApplicationController
   layout 'public'
 
   before_action :require_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
+  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,7 +39,7 @@ 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, target_domain: signed_request_account&.domain
+        render_with_cache json: @status, content_type: 'application/activity+json', serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter, target_domain: current_account&.domain
       end
     end
   end
@@ -48,7 +50,7 @@ class StatusesController < ApplicationController
                       content_type: 'application/activity+json',
                       serializer: ActivityPub::ActivitySerializer,
                       adapter: ActivityPub::Adapter,
-                      target_domain: signed_request_account&.domain
+                      target_domain: current_account&.domain
   end
 
   def embed
diff --git a/app/controllers/tags_controller.rb b/app/controllers/tags_controller.rb
index 9cba38771..368419ef5 100644
--- a/app/controllers/tags_controller.rb
+++ b/app/controllers/tags_controller.rb
@@ -9,13 +9,13 @@ 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_tag
   before_action :set_local
   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|
@@ -38,11 +38,11 @@ class TagsController < ApplicationController
         expires_in 3.minutes, public: public_fetch_mode?
 
         @statuses = HashtagQueryService.new.call(@tag, filter_params, current_account, @local)
-        @statuses = @statuses.without_semiprivate unless known_visitor?
+        @statuses = @statuses.without_semiprivate unless authenticated_or_following?(@account)
         @statuses = @statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id])
         @statuses = cache_collection(@statuses, Status)
 
-        render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: signed_request_account&.domain
+        render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json', target_domain: current_account&.domain
       end
     end
   end
@@ -77,8 +77,4 @@ class TagsController < ApplicationController
   def filter_params
     params.slice(:any, :all, :none).permit(:any, :all, :none)
   end
-
-  def known_visitor?
-    @known_visitor ||= user_signed_in? || (signed_request_account.present? && signed_request_account.following?(@account))
-  end
 end
diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb
index ef46ae4ae..fb8257c77 100644
--- a/app/lib/activitypub/adapter.rb
+++ b/app/lib/activitypub/adapter.rb
@@ -12,6 +12,8 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base
     require_dereference: { 'mp' => 'http://the.monsterpit.net/ns#', 'requireDereference' => 'mp:requireDereference' },
     show_replies: { 'mp' => 'http://the.monsterpit.net/ns#', 'showReplies' => 'mp:showReplies' },
     show_unlisted: { 'mp' => 'http://the.monsterpit.net/ns#', 'showUnlisted' => 'mp:showUnlisted' },
+    private: { 'mp' => 'http://the.monsterpit.net/ns#', 'private' => 'mp:private' },
+    require_auth: { 'mp' => 'http://the.monsterpit.net/ns#', 'requireAuth' => 'mp:requireAuth' },
     manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' },
     sensitive: { 'sensitive' => 'as:sensitive' },
     hashtag: { 'Hashtag' => 'as:Hashtag' },
diff --git a/app/models/account.rb b/app/models/account.rb
index 860f93f03..7d19aac08 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -53,6 +53,8 @@
 #  require_dereference           :boolean          default(FALSE), not null
 #  show_replies                  :boolean          default(TRUE), not null
 #  show_unlisted                 :boolean          default(TRUE), not null
+#  private                       :boolean          default(FALSE), not null
+#  require_auth                  :boolean          default(FALSE), not null
 #
 
 class Account < ApplicationRecord
diff --git a/app/models/status.rb b/app/models/status.rb
index 33e566737..a5f133506 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -234,7 +234,7 @@ class Status < ApplicationRecord
   end
 
   def hidden?
-    !distributable?
+    !published? || !distributable?
   end
 
   def distributable?
diff --git a/app/policies/status_policy.rb b/app/policies/status_policy.rb
index 69d18c4bf..317f450eb 100644
--- a/app/policies/status_policy.rb
+++ b/app/policies/status_policy.rb
@@ -91,13 +91,13 @@ class StatusPolicy < ApplicationPolicy
   end
 
   def author_blocking?
-    return false if current_account.nil?
+    return author.require_auth? if current_account.nil?
 
     @preloaded_relations[:blocked_by] ? @preloaded_relations[:blocked_by][author.id] : author.blocking?(current_account)
   end
 
   def parent_author_blocking?
-    return false if current_account.nil? || parent_author.nil?
+    return parent_author&.require_auth? if current_account.nil? || parent_author.nil?
 
     @preloaded_relations[:blocked_by] ? @preloaded_relations[:blocked_by][parent_author.id] : parent_author.blocking?(current_account)
   end
@@ -162,7 +162,7 @@ class StatusPolicy < ApplicationPolicy
   end
 
   def public_conversation?
-    @public_conversation ||= (record.conversation&.public? || false)
+    @public_conversation ||= record.conversation&.public? || false
   end
 
   def visibility_for_remote_domain
diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb
index 5a63d15ea..8e5e5b4bb 100644
--- a/app/serializers/activitypub/actor_serializer.rb
+++ b/app/serializers/activitypub/actor_serializer.rb
@@ -24,8 +24,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
   attribute :moved_to, if: :moved?
   attribute :also_known_as, if: :also_known_as?
 
-  context_extensions :require_dereference, :show_replies
-  attributes :require_dereference, :show_replies, :show_unlisted
+  context_extensions :require_dereference, :show_replies, :private, :require_auth
+  attributes :require_dereference, :show_replies, :show_unlisted, :private, :require_auth
 
   class EndpointsSerializer < ActivityPub::Serializer
     include RoutingHelper
diff --git a/app/services/activitypub/process_account_service.rb b/app/services/activitypub/process_account_service.rb
index 2a5980c79..39e777b32 100644
--- a/app/services/activitypub/process_account_service.rb
+++ b/app/services/activitypub/process_account_service.rb
@@ -89,6 +89,8 @@ class ActivityPub::ProcessAccountService < BaseService
     @account.require_dereference     = @json['requireDereference'] || false
     @account.show_replies            = @json['showReplies'] || true
     @account.show_unlisted           = @json['showUnlisted'] || true
+    @account.private                 = @json['private'] || false
+    @account.require_auth            = @json['require_auth'] || false
   end
 
   def set_fetchable_attributes!
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 828b3ee4c..1b7765f32 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -24,15 +24,15 @@
   %hr.spacer/
 
   .fields-group
-    = f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
-
-  .fields-group
     = f.input :bot, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.bot')
   
   %h4= t 'settings.profiles.privacy'
 
   %p.hint= t 'settings.profiles.privacy_html'
 
+  .fields-group
+    = f.input :locked, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.locked')
+
   - if Setting.profile_directory
     .fields-group
       = f.input :discoverable, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.discoverable')
@@ -43,6 +43,12 @@
   .fields-group
     = f.input :show_unlisted, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.show_unlisted')
 
+  .fields-group
+    = f.input :private, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.private')
+
+  .fields-group
+    = f.input :require_auth, as: :boolean, wrapper: :with_label, hint: t('simple_form.hints.defaults.require_auth')
+
   %h4= t 'settings.profiles.advanced_privacy'
 
   %p.hint= t 'settings.profiles.advanced_privacy_html'
diff --git a/config/locales/simple_form.en-MP.yml b/config/locales/simple_form.en-MP.yml
index 21a614794..671cbda0c 100644
--- a/config/locales/simple_form.en-MP.yml
+++ b/config/locales/simple_form.en-MP.yml
@@ -13,7 +13,9 @@ en-MP:
       defaults:
         irreversible: Filtered roars will disappear irreversibly, even if filter is later removed
         phrase: Will be matched regardless of casing in text or content warning of a roar
-        require_dereference_html: "When enabled, Monsterpit will deliver your roars to other servers as pointers and require an authenticated request to access their (non-public) content.  This allows permissions and blocks you've set to be enforced more stringently.  <strong>Note that only the lastest versions of Mastodon and Glitch-Soc servers are compatable with this feature.</strong>"
+        private: Only allow authenticated followers to view your local profile.
+        require_auth: Require viewers to log in to access your profile, roars, and threads from Monsterpit.
+        require_dereference_html: "When enabled, Monsterpit will deliver your roars to other servers as pointers and require an authenticated request to access their (non-public) content.  This allows permissions and blocks you've set to be enforced more stringently.  <strong>This feature will make your roars inaccessible from Mastodon servers older than 3.2.0.</strong>"
         setting_aggregate_reblogs: Do not show new boosts for roars that have been recently boosted (only affects newly-received boosts)
         setting_default_content_type_html: When composing roars, assume they are written in raw HTML, unless specified otherwise
         setting_default_content_type_markdown: When composing roars, assume they are using Markdown for rich text formatting, unless specified otherwise
@@ -37,6 +39,8 @@ en-MP:
         include_statuses: Include reported roars in the e-mail
       defaults:
         bot: This is an automated account
+        private: Private mode
+        require_auth: Disallow anonymous access
         require_dereference: Indirect federation mode
         setting_crop_images: Crop images in non-expanded roars to 16x9
         setting_default_content_type: Default format for roars
diff --git a/db/migrate/20200728171900_add_private_to_accounts.rb b/db/migrate/20200728171900_add_private_to_accounts.rb
new file mode 100644
index 000000000..482d09576
--- /dev/null
+++ b/db/migrate/20200728171900_add_private_to_accounts.rb
@@ -0,0 +1,7 @@
+class AddPrivateToAccounts < ActiveRecord::Migration[5.2]
+  def change
+    safety_assured do
+      add_column :accounts, :private, :boolean, default: false, null: false
+    end
+  end
+end
diff --git a/db/migrate/20200728173757_add_require_auth_to_accounts.rb b/db/migrate/20200728173757_add_require_auth_to_accounts.rb
new file mode 100644
index 000000000..00a3c1642
--- /dev/null
+++ b/db/migrate/20200728173757_add_require_auth_to_accounts.rb
@@ -0,0 +1,7 @@
+class AddRequireAuthToAccounts < ActiveRecord::Migration[5.2]
+  def change
+    safety_assured do
+      add_column :accounts, :require_auth, :boolean, default: false, null: false
+    end
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 3bf2d2e9e..4fcdd7e2f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 2020_07_28_135753) do
+ActiveRecord::Schema.define(version: 2020_07_28_173757) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -195,6 +195,8 @@ ActiveRecord::Schema.define(version: 2020_07_28_135753) do
     t.boolean "require_dereference", default: false, null: false
     t.boolean "show_replies", default: true, null: false
     t.boolean "show_unlisted", default: true, null: false
+    t.boolean "private", default: false, null: false
+    t.boolean "require_auth", default: false, null: false
     t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
     t.index "lower((username)::text), COALESCE(lower((domain)::text), ''::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
     t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"