Change account suspensions to be reversible by default (#14726)
This commit is contained in:
parent
bbcbf12215
commit
ed099d8bdc
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class AccountsController < BaseController
|
class AccountsController < BaseController
|
||||||
before_action :set_account, only: [:show, :redownload, :remove_avatar, :remove_header, :enable, :unsilence, :unsuspend, :memorialize, :approve, :reject]
|
before_action :set_account, except: [:index]
|
||||||
before_action :require_remote_account!, only: [:redownload]
|
before_action :require_remote_account!, only: [:redownload]
|
||||||
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
|
before_action :require_local_account!, only: [:enable, :memorialize, :approve, :reject]
|
||||||
|
|
||||||
|
@ -14,49 +14,58 @@ module Admin
|
||||||
def show
|
def show
|
||||||
authorize @account, :show?
|
authorize @account, :show?
|
||||||
|
|
||||||
|
@deletion_request = @account.deletion_request
|
||||||
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
||||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||||
@warnings = @account.targeted_account_warnings.latest.custom
|
@warnings = @account.targeted_account_warnings.latest.custom
|
||||||
|
@domain_block = DomainBlock.rule_for(@account.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def memorialize
|
def memorialize
|
||||||
authorize @account, :memorialize?
|
authorize @account, :memorialize?
|
||||||
@account.memorialize!
|
@account.memorialize!
|
||||||
log_action :memorialize, @account
|
log_action :memorialize, @account
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.memorialized_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def enable
|
def enable
|
||||||
authorize @account.user, :enable?
|
authorize @account.user, :enable?
|
||||||
@account.user.enable!
|
@account.user.enable!
|
||||||
log_action :enable, @account.user
|
log_action :enable, @account.user
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.enabled_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def approve
|
def approve
|
||||||
authorize @account.user, :approve?
|
authorize @account.user, :approve?
|
||||||
@account.user.approve!
|
@account.user.approve!
|
||||||
redirect_to admin_pending_accounts_path
|
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject
|
def reject
|
||||||
authorize @account.user, :reject?
|
authorize @account.user, :reject?
|
||||||
SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
||||||
redirect_to admin_pending_accounts_path
|
redirect_to admin_pending_accounts_path, notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize @account, :destroy?
|
||||||
|
Admin::AccountDeletionWorker.perform_async(@account.id)
|
||||||
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.destroyed_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsilence
|
def unsilence
|
||||||
authorize @account, :unsilence?
|
authorize @account, :unsilence?
|
||||||
@account.unsilence!
|
@account.unsilence!
|
||||||
log_action :unsilence, @account
|
log_action :unsilence, @account
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsilenced_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsuspend
|
def unsuspend
|
||||||
authorize @account, :unsuspend?
|
authorize @account, :unsuspend?
|
||||||
@account.unsuspend!
|
@account.unsuspend!
|
||||||
|
Admin::UnsuspensionWorker.perform_async(@account.id)
|
||||||
log_action :unsuspend, @account
|
log_action :unsuspend, @account
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.unsuspended_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def redownload
|
def redownload
|
||||||
|
@ -65,7 +74,7 @@ module Admin
|
||||||
@account.update!(last_webfingered_at: nil)
|
@account.update!(last_webfingered_at: nil)
|
||||||
ResolveAccountService.new.call(@account)
|
ResolveAccountService.new.call(@account)
|
||||||
|
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.redownloaded_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_avatar
|
def remove_avatar
|
||||||
|
@ -76,7 +85,7 @@ module Admin
|
||||||
|
|
||||||
log_action :remove_avatar, @account.user
|
log_action :remove_avatar, @account.user
|
||||||
|
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_avatar_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_header
|
def remove_header
|
||||||
|
@ -87,7 +96,7 @@ module Admin
|
||||||
|
|
||||||
log_action :remove_header, @account.user
|
log_action :remove_header, @account.user
|
||||||
|
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id), notice: I18n.t('admin.accounts.removed_header_msg', username: @account.acct)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -96,12 +96,12 @@ class Api::BaseController < ApplicationController
|
||||||
def require_user!
|
def require_user!
|
||||||
if !current_user
|
if !current_user
|
||||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||||
elsif current_user.disabled?
|
|
||||||
render json: { error: 'Your login is currently disabled' }, status: 403
|
|
||||||
elsif !current_user.confirmed?
|
elsif !current_user.confirmed?
|
||||||
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
|
render json: { error: 'Your login is missing a confirmed e-mail address' }, status: 403
|
||||||
elsif !current_user.approved?
|
elsif !current_user.approved?
|
||||||
render json: { error: 'Your login is currently pending approval' }, status: 403
|
render json: { error: 'Your login is currently pending approval' }, status: 403
|
||||||
|
elsif !current_user.functional?
|
||||||
|
render json: { error: 'Your login is currently disabled' }, status: 403
|
||||||
else
|
else
|
||||||
set_user_activity
|
set_user_activity
|
||||||
end
|
end
|
||||||
|
|
|
@ -58,7 +58,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
||||||
|
|
||||||
def reject
|
def reject
|
||||||
authorize @account.user, :reject?
|
authorize @account.user, :reject?
|
||||||
SuspendAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
||||||
|
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize @account, :destroy?
|
||||||
|
Admin::AccountDeletionWorker.perform_async(@account.id)
|
||||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -72,6 +78,7 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
||||||
def unsuspend
|
def unsuspend
|
||||||
authorize @account, :unsuspend?
|
authorize @account, :unsuspend?
|
||||||
@account.unsuspend!
|
@account.unsuspend!
|
||||||
|
Admin::UnsuspensionWorker.perform_async(@account.id)
|
||||||
log_action :unsuspend, @account
|
log_action :unsuspend, @account
|
||||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,7 +43,7 @@ class Settings::DeletesController < Settings::BaseController
|
||||||
|
|
||||||
def destroy_account!
|
def destroy_account!
|
||||||
current_account.suspend!
|
current_account.suspend!
|
||||||
Admin::SuspensionWorker.perform_async(current_user.account_id, true)
|
AccountDeletionWorker.perform_async(current_user.account_id)
|
||||||
sign_out
|
sign_out
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,7 @@ class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||||
|
|
||||||
def delete_person
|
def delete_person
|
||||||
lock_or_return("delete_in_progress:#{@account.id}") do
|
lock_or_return("delete_in_progress:#{@account.id}") do
|
||||||
SuspendAccountService.new.call(@account, reserve_username: false)
|
DeleteAccountService.new.call(@account, reserve_username: false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
@me = recipient
|
@me = recipient
|
||||||
@status = notification.target_status
|
@status = notification.target_status
|
||||||
|
|
||||||
return if @me.user.disabled? || @status.nil?
|
return unless @me.user.functional? && @status.present?
|
||||||
|
|
||||||
locale_for_account(@me) do
|
locale_for_account(@me) do
|
||||||
thread_by_conversation(@status.conversation)
|
thread_by_conversation(@status.conversation)
|
||||||
|
@ -22,7 +22,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
@me = recipient
|
@me = recipient
|
||||||
@account = notification.from_account
|
@account = notification.from_account
|
||||||
|
|
||||||
return if @me.user.disabled?
|
return unless @me.user.functional?
|
||||||
|
|
||||||
locale_for_account(@me) do
|
locale_for_account(@me) do
|
||||||
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
|
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
|
||||||
|
@ -34,7 +34,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
@account = notification.from_account
|
@account = notification.from_account
|
||||||
@status = notification.target_status
|
@status = notification.target_status
|
||||||
|
|
||||||
return if @me.user.disabled? || @status.nil?
|
return unless @me.user.functional? && @status.present?
|
||||||
|
|
||||||
locale_for_account(@me) do
|
locale_for_account(@me) do
|
||||||
thread_by_conversation(@status.conversation)
|
thread_by_conversation(@status.conversation)
|
||||||
|
@ -47,7 +47,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
@account = notification.from_account
|
@account = notification.from_account
|
||||||
@status = notification.target_status
|
@status = notification.target_status
|
||||||
|
|
||||||
return if @me.user.disabled? || @status.nil?
|
return unless @me.user.functional? && @status.present?
|
||||||
|
|
||||||
locale_for_account(@me) do
|
locale_for_account(@me) do
|
||||||
thread_by_conversation(@status.conversation)
|
thread_by_conversation(@status.conversation)
|
||||||
|
@ -59,7 +59,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
@me = recipient
|
@me = recipient
|
||||||
@account = notification.from_account
|
@account = notification.from_account
|
||||||
|
|
||||||
return if @me.user.disabled?
|
return unless @me.user.functional?
|
||||||
|
|
||||||
locale_for_account(@me) do
|
locale_for_account(@me) do
|
||||||
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
|
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
|
||||||
|
@ -67,7 +67,7 @@ class NotificationMailer < ApplicationMailer
|
||||||
end
|
end
|
||||||
|
|
||||||
def digest(recipient, **opts)
|
def digest(recipient, **opts)
|
||||||
return if recipient.user.disabled?
|
return unless recipient.user.functional?
|
||||||
|
|
||||||
@me = recipient
|
@me = recipient
|
||||||
@since = opts[:since] || [@me.user.last_emailed_at, (@me.user.current_sign_in_at + 1.day)].compact.max
|
@since = opts[:since] || [@me.user.last_emailed_at, (@me.user.current_sign_in_at + 1.day)].compact.max
|
||||||
|
@ -88,8 +88,10 @@ class NotificationMailer < ApplicationMailer
|
||||||
|
|
||||||
def thread_by_conversation(conversation)
|
def thread_by_conversation(conversation)
|
||||||
return if conversation.nil?
|
return if conversation.nil?
|
||||||
|
|
||||||
msg_id = "<conversation-#{conversation.id}.#{conversation.created_at.strftime('%Y-%m-%d')}@#{Rails.configuration.x.local_domain}>"
|
msg_id = "<conversation-#{conversation.id}.#{conversation.created_at.strftime('%Y-%m-%d')}@#{Rails.configuration.x.local_domain}>"
|
||||||
|
|
||||||
headers['In-Reply-To'] = msg_id
|
headers['In-Reply-To'] = msg_id
|
||||||
headers['References'] = msg_id
|
headers['References'] = msg_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,7 +15,7 @@ class UserMailer < Devise::Mailer
|
||||||
@token = token
|
@token = token
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.unconfirmed_email.presence || @resource.email,
|
mail to: @resource.unconfirmed_email.presence || @resource.email,
|
||||||
|
@ -29,7 +29,7 @@ class UserMailer < Devise::Mailer
|
||||||
@token = token
|
@token = token
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.reset_password_instructions.subject')
|
||||||
|
@ -40,7 +40,7 @@ class UserMailer < Devise::Mailer
|
||||||
@resource = user
|
@resource = user
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject')
|
||||||
|
@ -51,7 +51,7 @@ class UserMailer < Devise::Mailer
|
||||||
@resource = user
|
@resource = user
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject')
|
||||||
|
@ -62,7 +62,7 @@ class UserMailer < Devise::Mailer
|
||||||
@resource = user
|
@resource = user
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_enabled.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_enabled.subject')
|
||||||
|
@ -73,7 +73,7 @@ class UserMailer < Devise::Mailer
|
||||||
@resource = user
|
@resource = user
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_disabled.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_disabled.subject')
|
||||||
|
@ -84,7 +84,7 @@ class UserMailer < Devise::Mailer
|
||||||
@resource = user
|
@resource = user
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.two_factor_recovery_codes_changed.subject')
|
||||||
|
@ -95,7 +95,7 @@ class UserMailer < Devise::Mailer
|
||||||
@resource = user
|
@resource = user
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_enabled.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_enabled.subject')
|
||||||
|
@ -106,7 +106,7 @@ class UserMailer < Devise::Mailer
|
||||||
@resource = user
|
@resource = user
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_disabled.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_disabled.subject')
|
||||||
|
@ -118,7 +118,7 @@ class UserMailer < Devise::Mailer
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
@webauthn_credential = webauthn_credential
|
@webauthn_credential = webauthn_credential
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.added.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.added.subject')
|
||||||
|
@ -130,7 +130,7 @@ class UserMailer < Devise::Mailer
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
@webauthn_credential = webauthn_credential
|
@webauthn_credential = webauthn_credential
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject')
|
mail to: @resource.email, subject: I18n.t('devise.mailer.webauthn_credential.deleted.subject')
|
||||||
|
@ -141,7 +141,7 @@ class UserMailer < Devise::Mailer
|
||||||
@resource = user
|
@resource = user
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject')
|
mail to: @resource.email, subject: I18n.t('user_mailer.welcome.subject')
|
||||||
|
@ -153,7 +153,7 @@ class UserMailer < Devise::Mailer
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
@backup = backup
|
@backup = backup
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject')
|
mail to: @resource.email, subject: I18n.t('user_mailer.backup_ready.subject')
|
||||||
|
@ -181,7 +181,7 @@ class UserMailer < Devise::Mailer
|
||||||
@detection = Browser.new(user_agent)
|
@detection = Browser.new(user_agent)
|
||||||
@timestamp = timestamp.to_time.utc
|
@timestamp = timestamp.to_time.utc
|
||||||
|
|
||||||
return if @resource.disabled?
|
return unless @resource.active_for_authentication?
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email,
|
mail to: @resource.email,
|
||||||
|
|
|
@ -222,23 +222,20 @@ class Account < ApplicationRecord
|
||||||
|
|
||||||
def suspend!(date = Time.now.utc)
|
def suspend!(date = Time.now.utc)
|
||||||
transaction do
|
transaction do
|
||||||
user&.disable! if local?
|
create_deletion_request!
|
||||||
update!(suspended_at: date)
|
update!(suspended_at: date)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unsuspend!
|
def unsuspend!
|
||||||
transaction do
|
transaction do
|
||||||
user&.enable! if local?
|
deletion_request&.destroy!
|
||||||
update!(suspended_at: nil)
|
update!(suspended_at: nil)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def memorialize!
|
def memorialize!
|
||||||
transaction do
|
update!(memorial: true)
|
||||||
user&.disable! if local?
|
|
||||||
update!(memorial: true)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign?
|
def sign?
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: account_deletion_requests
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# account_id :bigint(8)
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
class AccountDeletionRequest < ApplicationRecord
|
||||||
|
DELAY_TO_DELETION = 30.days.freeze
|
||||||
|
|
||||||
|
belongs_to :account
|
||||||
|
|
||||||
|
def due_at
|
||||||
|
created_at + DELAY_TO_DELETION
|
||||||
|
end
|
||||||
|
end
|
|
@ -134,7 +134,7 @@ class Admin::AccountAction
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_email!
|
def process_email!
|
||||||
UserMailer.warning(target_account.user, warning, status_ids).deliver_now! if warnable?
|
UserMailer.warning(target_account.user, warning, status_ids).deliver_later! if warnable?
|
||||||
end
|
end
|
||||||
|
|
||||||
def warnable?
|
def warnable?
|
||||||
|
|
|
@ -60,5 +60,8 @@ module AccountAssociations
|
||||||
# Hashtags
|
# Hashtags
|
||||||
has_and_belongs_to_many :tags
|
has_and_belongs_to_many :tags
|
||||||
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
|
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
|
||||||
|
|
||||||
|
# Account deletion requests
|
||||||
|
has_one :deletion_request, class_name: 'AccountDeletionRequest', inverse_of: :account, dependent: :destroy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -69,6 +69,6 @@ class Form::AccountBatch
|
||||||
records = accounts.includes(:user)
|
records = accounts.includes(:user)
|
||||||
|
|
||||||
records.each { |account| authorize(account.user, :reject?) }
|
records.each { |account| authorize(account.user, :reject?) }
|
||||||
.each { |account| SuspendAccountService.new.call(account, reserve_email: false, reserve_username: false) }
|
.each { |account| DeleteAccountService.new.call(account, reserve_email: false, reserve_username: false) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ class Invite < ApplicationRecord
|
||||||
before_validation :set_code
|
before_validation :set_code
|
||||||
|
|
||||||
def valid_for_use?
|
def valid_for_use?
|
||||||
(max_uses.nil? || uses < max_uses) && !expired? && !(user.nil? || user.disabled?)
|
(max_uses.nil? || uses < max_uses) && !expired? && user&.functional?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -168,7 +168,7 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def active_for_authentication?
|
def active_for_authentication?
|
||||||
true
|
!account.memorial?
|
||||||
end
|
end
|
||||||
|
|
||||||
def suspicious_sign_in?(ip)
|
def suspicious_sign_in?(ip)
|
||||||
|
@ -176,7 +176,7 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def functional?
|
def functional?
|
||||||
confirmed? && approved? && !disabled? && !account.suspended? && account.moved_to_account_id.nil?
|
confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial? && account.moved_to_account_id.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
def unconfirmed_or_pending?
|
def unconfirmed_or_pending?
|
||||||
|
|
|
@ -17,6 +17,10 @@ class AccountPolicy < ApplicationPolicy
|
||||||
staff? && !record.user&.staff?
|
staff? && !record.user&.staff?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy?
|
||||||
|
record.suspended? && record.deletion_request.present? && admin?
|
||||||
|
end
|
||||||
|
|
||||||
def unsuspend?
|
def unsuspend?
|
||||||
staff?
|
staff?
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
class AfterUnallowDomainService < BaseService
|
class AfterUnallowDomainService < BaseService
|
||||||
def call(domain)
|
def call(domain)
|
||||||
Account.where(domain: domain).find_each do |account|
|
Account.where(domain: domain).find_each do |account|
|
||||||
SuspendAccountService.new.call(account, reserve_username: false)
|
DeleteAccountService.new.call(account, reserve_username: false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -36,7 +36,7 @@ class BlockDomainService < BaseService
|
||||||
def suspend_accounts!
|
def suspend_accounts!
|
||||||
blocked_domain_accounts.without_suspended.in_batches.update_all(suspended_at: @domain_block.created_at)
|
blocked_domain_accounts.without_suspended.in_batches.update_all(suspended_at: @domain_block.created_at)
|
||||||
blocked_domain_accounts.where(suspended_at: @domain_block.created_at).reorder(nil).find_each do |account|
|
blocked_domain_accounts.where(suspended_at: @domain_block.created_at).reorder(nil).find_each do |account|
|
||||||
SuspendAccountService.new.call(account, reserve_username: true, suspended_at: @domain_block.created_at)
|
DeleteAccountService.new.call(account, reserve_username: true, suspended_at: @domain_block.created_at)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class DeleteAccountService < BaseService
|
||||||
|
include Payloadable
|
||||||
|
|
||||||
|
ASSOCIATIONS_ON_SUSPEND = %w(
|
||||||
|
account_pins
|
||||||
|
active_relationships
|
||||||
|
block_relationships
|
||||||
|
blocked_by_relationships
|
||||||
|
conversation_mutes
|
||||||
|
conversations
|
||||||
|
custom_filters
|
||||||
|
domain_blocks
|
||||||
|
favourites
|
||||||
|
follow_requests
|
||||||
|
list_accounts
|
||||||
|
mute_relationships
|
||||||
|
muted_by_relationships
|
||||||
|
notifications
|
||||||
|
owned_lists
|
||||||
|
passive_relationships
|
||||||
|
report_notes
|
||||||
|
scheduled_statuses
|
||||||
|
status_pins
|
||||||
|
).freeze
|
||||||
|
|
||||||
|
ASSOCIATIONS_ON_DESTROY = %w(
|
||||||
|
reports
|
||||||
|
targeted_moderation_notes
|
||||||
|
targeted_reports
|
||||||
|
).freeze
|
||||||
|
|
||||||
|
# Suspend or remove an account and remove as much of its data
|
||||||
|
# as possible. If it's a local account and it has not been confirmed
|
||||||
|
# or never been approved, then side effects are skipped and both
|
||||||
|
# the user and account records are removed fully. Otherwise,
|
||||||
|
# it is controlled by options.
|
||||||
|
# @param [Account]
|
||||||
|
# @param [Hash] options
|
||||||
|
# @option [Boolean] :reserve_email Keep user record. Only applicable for local accounts
|
||||||
|
# @option [Boolean] :reserve_username Keep account record
|
||||||
|
# @option [Boolean] :skip_side_effects Side effects are ActivityPub and streaming API payloads
|
||||||
|
# @option [Time] :suspended_at Only applicable when :reserve_username is true
|
||||||
|
def call(account, **options)
|
||||||
|
@account = account
|
||||||
|
@options = { reserve_username: true, reserve_email: true }.merge(options)
|
||||||
|
|
||||||
|
if @account.local? && @account.user_unconfirmed_or_pending?
|
||||||
|
@options[:reserve_email] = false
|
||||||
|
@options[:reserve_username] = false
|
||||||
|
@options[:skip_side_effects] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
reject_follows!
|
||||||
|
purge_user!
|
||||||
|
purge_profile!
|
||||||
|
purge_content!
|
||||||
|
fulfill_deletion_request!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def reject_follows!
|
||||||
|
return if @account.local? || !@account.activitypub?
|
||||||
|
|
||||||
|
ActivityPub::DeliveryWorker.push_bulk(Follow.where(account: @account)) do |follow|
|
||||||
|
[build_reject_json(follow), follow.target_account_id, follow.account.inbox_url]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def purge_user!
|
||||||
|
return if !@account.local? || @account.user.nil?
|
||||||
|
|
||||||
|
if @options[:reserve_email]
|
||||||
|
@account.user.disable!
|
||||||
|
@account.user.invites.where(uses: 0).destroy_all
|
||||||
|
else
|
||||||
|
@account.user.destroy
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def purge_content!
|
||||||
|
distribute_delete_actor! if @account.local? && !@options[:skip_side_effects]
|
||||||
|
|
||||||
|
@account.statuses.reorder(nil).find_in_batches do |statuses|
|
||||||
|
statuses.reject! { |status| reported_status_ids.include?(status.id) } if @options[:reserve_username]
|
||||||
|
BatchedRemoveStatusService.new.call(statuses, skip_side_effects: @options[:skip_side_effects])
|
||||||
|
end
|
||||||
|
|
||||||
|
@account.media_attachments.reorder(nil).find_each do |media_attachment|
|
||||||
|
next if @options[:reserve_username] && reported_status_ids.include?(media_attachment.status_id)
|
||||||
|
|
||||||
|
media_attachment.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
@account.polls.reorder(nil).find_each do |poll|
|
||||||
|
next if @options[:reserve_username] && reported_status_ids.include?(poll.status_id)
|
||||||
|
|
||||||
|
poll.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
associations_for_destruction.each do |association_name|
|
||||||
|
destroy_all(@account.public_send(association_name))
|
||||||
|
end
|
||||||
|
|
||||||
|
@account.destroy unless @options[:reserve_username]
|
||||||
|
end
|
||||||
|
|
||||||
|
def purge_profile!
|
||||||
|
# If the account is going to be destroyed
|
||||||
|
# there is no point wasting time updating
|
||||||
|
# its values first
|
||||||
|
|
||||||
|
return unless @options[:reserve_username]
|
||||||
|
|
||||||
|
@account.silenced_at = nil
|
||||||
|
@account.suspended_at = @options[:suspended_at] || Time.now.utc
|
||||||
|
@account.locked = false
|
||||||
|
@account.memorial = false
|
||||||
|
@account.discoverable = false
|
||||||
|
@account.display_name = ''
|
||||||
|
@account.note = ''
|
||||||
|
@account.fields = []
|
||||||
|
@account.statuses_count = 0
|
||||||
|
@account.followers_count = 0
|
||||||
|
@account.following_count = 0
|
||||||
|
@account.moved_to_account = nil
|
||||||
|
@account.trust_level = :untrusted
|
||||||
|
@account.avatar.destroy
|
||||||
|
@account.header.destroy
|
||||||
|
@account.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def fulfill_deletion_request!
|
||||||
|
@account.deletion_request&.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_all(association)
|
||||||
|
association.in_batches.destroy_all
|
||||||
|
end
|
||||||
|
|
||||||
|
def distribute_delete_actor!
|
||||||
|
ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes) do |inbox_url|
|
||||||
|
[delete_actor_json, @account.id, inbox_url]
|
||||||
|
end
|
||||||
|
|
||||||
|
ActivityPub::LowPriorityDeliveryWorker.push_bulk(low_priority_delivery_inboxes) do |inbox_url|
|
||||||
|
[delete_actor_json, @account.id, inbox_url]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete_actor_json
|
||||||
|
@delete_actor_json ||= Oj.dump(serialize_payload(@account, ActivityPub::DeleteActorSerializer, signer: @account))
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_reject_json(follow)
|
||||||
|
Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer))
|
||||||
|
end
|
||||||
|
|
||||||
|
def delivery_inboxes
|
||||||
|
@delivery_inboxes ||= @account.followers.inboxes + Relay.enabled.pluck(:inbox_url)
|
||||||
|
end
|
||||||
|
|
||||||
|
def low_priority_delivery_inboxes
|
||||||
|
Account.inboxes - delivery_inboxes
|
||||||
|
end
|
||||||
|
|
||||||
|
def reported_status_ids
|
||||||
|
@reported_status_ids ||= Report.where(target_account: @account).unresolved.pluck(:status_ids).flatten.uniq
|
||||||
|
end
|
||||||
|
|
||||||
|
def associations_for_destruction
|
||||||
|
if @options[:reserve_username]
|
||||||
|
ASSOCIATIONS_ON_SUSPEND
|
||||||
|
else
|
||||||
|
ASSOCIATIONS_ON_SUSPEND + ASSOCIATIONS_ON_DESTROY
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,175 +1,52 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class SuspendAccountService < BaseService
|
class SuspendAccountService < BaseService
|
||||||
include Payloadable
|
def call(account)
|
||||||
|
|
||||||
ASSOCIATIONS_ON_SUSPEND = %w(
|
|
||||||
account_pins
|
|
||||||
active_relationships
|
|
||||||
block_relationships
|
|
||||||
blocked_by_relationships
|
|
||||||
conversation_mutes
|
|
||||||
conversations
|
|
||||||
custom_filters
|
|
||||||
domain_blocks
|
|
||||||
favourites
|
|
||||||
follow_requests
|
|
||||||
list_accounts
|
|
||||||
mute_relationships
|
|
||||||
muted_by_relationships
|
|
||||||
notifications
|
|
||||||
owned_lists
|
|
||||||
passive_relationships
|
|
||||||
report_notes
|
|
||||||
scheduled_statuses
|
|
||||||
status_pins
|
|
||||||
).freeze
|
|
||||||
|
|
||||||
ASSOCIATIONS_ON_DESTROY = %w(
|
|
||||||
reports
|
|
||||||
targeted_moderation_notes
|
|
||||||
targeted_reports
|
|
||||||
).freeze
|
|
||||||
|
|
||||||
# Suspend or remove an account and remove as much of its data
|
|
||||||
# as possible. If it's a local account and it has not been confirmed
|
|
||||||
# or never been approved, then side effects are skipped and both
|
|
||||||
# the user and account records are removed fully. Otherwise,
|
|
||||||
# it is controlled by options.
|
|
||||||
# @param [Account]
|
|
||||||
# @param [Hash] options
|
|
||||||
# @option [Boolean] :reserve_email Keep user record. Only applicable for local accounts
|
|
||||||
# @option [Boolean] :reserve_username Keep account record
|
|
||||||
# @option [Boolean] :skip_side_effects Side effects are ActivityPub and streaming API payloads
|
|
||||||
# @option [Time] :suspended_at Only applicable when :reserve_username is true
|
|
||||||
def call(account, **options)
|
|
||||||
@account = account
|
@account = account
|
||||||
@options = { reserve_username: true, reserve_email: true }.merge(options)
|
|
||||||
|
|
||||||
if @account.local? && @account.user_unconfirmed_or_pending?
|
suspend!
|
||||||
@options[:reserve_email] = false
|
unmerge_from_home_timelines!
|
||||||
@options[:reserve_username] = false
|
unmerge_from_list_timelines!
|
||||||
@options[:skip_side_effects] = true
|
privatize_media_attachments!
|
||||||
end
|
|
||||||
|
|
||||||
reject_follows!
|
|
||||||
purge_user!
|
|
||||||
purge_profile!
|
|
||||||
purge_content!
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def reject_follows!
|
def suspend!
|
||||||
return if @account.local? || !@account.activitypub?
|
@account.suspend! unless @account.suspended?
|
||||||
|
end
|
||||||
|
|
||||||
ActivityPub::DeliveryWorker.push_bulk(Follow.where(account: @account)) do |follow|
|
def unmerge_from_home_timelines!
|
||||||
[build_reject_json(follow), follow.target_account_id, follow.account.inbox_url]
|
@account.followers_for_local_distribution.find_each do |follower|
|
||||||
|
FeedManager.instance.unmerge_from_timeline(@account, follower)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def purge_user!
|
def unmerge_from_list_timelines!
|
||||||
return if !@account.local? || @account.user.nil?
|
@account.lists_for_local_distribution.find_each do |list|
|
||||||
|
FeedManager.instance.unmerge_from_list(@account, list)
|
||||||
if @options[:reserve_email]
|
|
||||||
@account.user.disable!
|
|
||||||
@account.user.invites.where(uses: 0).destroy_all
|
|
||||||
else
|
|
||||||
@account.user.destroy
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def purge_content!
|
def privatize_media_attachments!
|
||||||
distribute_delete_actor! if @account.local? && !@options[:skip_side_effects]
|
attachment_names = MediaAttachment.attachment_definitions.keys
|
||||||
|
|
||||||
@account.statuses.reorder(nil).find_in_batches do |statuses|
|
@account.media_attachments.find_each do |media_attachment|
|
||||||
statuses.reject! { |status| reported_status_ids.include?(status.id) } if @options[:reserve_username]
|
attachment_names.each do |attachment_name|
|
||||||
BatchedRemoveStatusService.new.call(statuses, skip_side_effects: @options[:skip_side_effects])
|
attachment = media_attachment.public_send(attachment_name)
|
||||||
end
|
styles = [:original] | attachment.styles.keys
|
||||||
|
|
||||||
@account.media_attachments.reorder(nil).find_each do |media_attachment|
|
styles.each do |style|
|
||||||
next if @options[:reserve_username] && reported_status_ids.include?(media_attachment.status_id)
|
case Paperclip::Attachment.default_options[:storage]
|
||||||
|
when :s3
|
||||||
media_attachment.destroy
|
attachment.s3_object(style).acl.put(:private)
|
||||||
end
|
when :fog
|
||||||
|
# Not supported
|
||||||
@account.polls.reorder(nil).find_each do |poll|
|
when :filesystem
|
||||||
next if @options[:reserve_username] && reported_status_ids.include?(poll.status_id)
|
FileUtils.chmod(0o600 & ~File.umask, attachment.path(style))
|
||||||
|
end
|
||||||
poll.destroy
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
associations_for_destruction.each do |association_name|
|
|
||||||
destroy_all(@account.public_send(association_name))
|
|
||||||
end
|
|
||||||
|
|
||||||
@account.destroy unless @options[:reserve_username]
|
|
||||||
end
|
|
||||||
|
|
||||||
def purge_profile!
|
|
||||||
# If the account is going to be destroyed
|
|
||||||
# there is no point wasting time updating
|
|
||||||
# its values first
|
|
||||||
|
|
||||||
return unless @options[:reserve_username]
|
|
||||||
|
|
||||||
@account.silenced_at = nil
|
|
||||||
@account.suspended_at = @options[:suspended_at] || Time.now.utc
|
|
||||||
@account.locked = false
|
|
||||||
@account.memorial = false
|
|
||||||
@account.discoverable = false
|
|
||||||
@account.display_name = ''
|
|
||||||
@account.note = ''
|
|
||||||
@account.fields = []
|
|
||||||
@account.statuses_count = 0
|
|
||||||
@account.followers_count = 0
|
|
||||||
@account.following_count = 0
|
|
||||||
@account.moved_to_account = nil
|
|
||||||
@account.trust_level = :untrusted
|
|
||||||
@account.avatar.destroy
|
|
||||||
@account.header.destroy
|
|
||||||
@account.save!
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy_all(association)
|
|
||||||
association.in_batches.destroy_all
|
|
||||||
end
|
|
||||||
|
|
||||||
def distribute_delete_actor!
|
|
||||||
ActivityPub::DeliveryWorker.push_bulk(delivery_inboxes) do |inbox_url|
|
|
||||||
[delete_actor_json, @account.id, inbox_url]
|
|
||||||
end
|
|
||||||
|
|
||||||
ActivityPub::LowPriorityDeliveryWorker.push_bulk(low_priority_delivery_inboxes) do |inbox_url|
|
|
||||||
[delete_actor_json, @account.id, inbox_url]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_actor_json
|
|
||||||
@delete_actor_json ||= Oj.dump(serialize_payload(@account, ActivityPub::DeleteActorSerializer, signer: @account))
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_reject_json(follow)
|
|
||||||
Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer))
|
|
||||||
end
|
|
||||||
|
|
||||||
def delivery_inboxes
|
|
||||||
@delivery_inboxes ||= @account.followers.inboxes + Relay.enabled.pluck(:inbox_url)
|
|
||||||
end
|
|
||||||
|
|
||||||
def low_priority_delivery_inboxes
|
|
||||||
Account.inboxes - delivery_inboxes
|
|
||||||
end
|
|
||||||
|
|
||||||
def reported_status_ids
|
|
||||||
@reported_status_ids ||= Report.where(target_account: @account).unresolved.pluck(:status_ids).flatten.uniq
|
|
||||||
end
|
|
||||||
|
|
||||||
def associations_for_destruction
|
|
||||||
if @options[:reserve_username]
|
|
||||||
ASSOCIATIONS_ON_SUSPEND
|
|
||||||
else
|
|
||||||
ASSOCIATIONS_ON_SUSPEND + ASSOCIATIONS_ON_DESTROY
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class UnsuspendAccountService < BaseService
|
||||||
|
def call(account)
|
||||||
|
@account = account
|
||||||
|
|
||||||
|
unsuspend!
|
||||||
|
merge_into_home_timelines!
|
||||||
|
merge_into_list_timelines!
|
||||||
|
publish_media_attachments!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def unsuspend!
|
||||||
|
@account.unsuspend! if @account.suspended?
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_into_home_timelines!
|
||||||
|
@account.followers_for_local_distribution.find_each do |follower|
|
||||||
|
FeedManager.instance.merge_into_timeline(@account, follower)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def merge_into_list_timelines!
|
||||||
|
@account.lists_for_local_distribution.find_each do |list|
|
||||||
|
FeedManager.instance.merge_into_list(@account, list)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def publish_media_attachments!
|
||||||
|
attachment_names = MediaAttachment.attachment_definitions.keys
|
||||||
|
|
||||||
|
@account.media_attachments.find_each do |media_attachment|
|
||||||
|
attachment_names.each do |attachment_name|
|
||||||
|
attachment = media_attachment.public_send(attachment_name)
|
||||||
|
styles = [:original] | attachment.styles.keys
|
||||||
|
|
||||||
|
styles.each do |style|
|
||||||
|
case Paperclip::Attachment.default_options[:storage]
|
||||||
|
when :s3
|
||||||
|
attachment.s3_object(style).acl.put(Paperclip::Attachment.default_options[:s3_permissions])
|
||||||
|
when :fog
|
||||||
|
# Not supported
|
||||||
|
when :filesystem
|
||||||
|
FileUtils.chmod(0o666 & ~File.umask, attachment.path(style))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -56,19 +56,21 @@
|
||||||
= link_to admin_action_logs_path(target_account_id: @account.id) do
|
= link_to admin_action_logs_path(target_account_id: @account.id) do
|
||||||
.dashboard__counters__text
|
.dashboard__counters__text
|
||||||
- if @account.local? && @account.user.nil?
|
- if @account.local? && @account.user.nil?
|
||||||
%span.neutral= t('admin.accounts.deleted')
|
= t('admin.accounts.deleted')
|
||||||
|
- elsif @account.memorial?
|
||||||
|
= t('admin.accounts.memorialized')
|
||||||
- elsif @account.suspended?
|
- elsif @account.suspended?
|
||||||
%span.red= t('admin.accounts.suspended')
|
= t('admin.accounts.suspended')
|
||||||
- elsif @account.silenced?
|
- elsif @account.silenced?
|
||||||
%span.red= t('admin.accounts.silenced')
|
= t('admin.accounts.silenced')
|
||||||
- elsif @account.local? && @account.user&.disabled?
|
- elsif @account.local? && @account.user&.disabled?
|
||||||
%span.red= t('admin.accounts.disabled')
|
= t('admin.accounts.disabled')
|
||||||
- elsif @account.local? && !@account.user&.confirmed?
|
- elsif @account.local? && !@account.user&.confirmed?
|
||||||
%span.neutral= t('admin.accounts.confirming')
|
= t('admin.accounts.confirming')
|
||||||
- elsif @account.local? && !@account.user_approved?
|
- elsif @account.local? && !@account.user_approved?
|
||||||
%span.neutral= t('admin.accounts.pending')
|
= t('admin.accounts.pending')
|
||||||
- else
|
- else
|
||||||
%span.neutral= t('admin.accounts.no_limits_imposed')
|
= t('admin.accounts.no_limits_imposed')
|
||||||
.dashboard__counters__label= t 'admin.accounts.login_status'
|
.dashboard__counters__label= t 'admin.accounts.login_status'
|
||||||
|
|
||||||
- unless @account.local? && @account.user.nil?
|
- unless @account.local? && @account.user.nil?
|
||||||
|
@ -122,19 +124,6 @@
|
||||||
= t('admin.accounts.confirming')
|
= t('admin.accounts.confirming')
|
||||||
%td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user)
|
%td= table_link_to 'refresh', t('admin.accounts.resend_confirmation.send'), resend_admin_account_confirmation_path(@account.id), method: :post if can?(:confirm, @account.user)
|
||||||
|
|
||||||
%tr
|
|
||||||
%th= t('admin.accounts.login_status')
|
|
||||||
%td
|
|
||||||
- if @account.user&.disabled?
|
|
||||||
= t('admin.accounts.disabled')
|
|
||||||
- else
|
|
||||||
= t('admin.accounts.enabled')
|
|
||||||
%td
|
|
||||||
- if @account.user&.disabled?
|
|
||||||
= table_link_to 'unlock', t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post if can?(:enable, @account.user)
|
|
||||||
- elsif @account.user_approved?
|
|
||||||
= table_link_to 'lock', t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable') if can?(:disable, @account.user)
|
|
||||||
|
|
||||||
%tr
|
%tr
|
||||||
%th= t('simple_form.labels.defaults.locale')
|
%th= t('simple_form.labels.defaults.locale')
|
||||||
%td= @account.user_locale
|
%td= @account.user_locale
|
||||||
|
@ -172,49 +161,62 @@
|
||||||
%td
|
%td
|
||||||
= @account.inbox_url
|
= @account.inbox_url
|
||||||
= fa_icon DeliveryFailureTracker.available?(@account.inbox_url) ? 'check' : 'times'
|
= fa_icon DeliveryFailureTracker.available?(@account.inbox_url) ? 'check' : 'times'
|
||||||
|
%td
|
||||||
|
= table_link_to 'search', @domain_block.present? ? t('admin.domain_blocks.view') : t('admin.accounts.view_domain'), admin_instance_path(@account.domain)
|
||||||
%tr
|
%tr
|
||||||
%th= t('admin.accounts.shared_inbox_url')
|
%th= t('admin.accounts.shared_inbox_url')
|
||||||
%td
|
%td
|
||||||
= @account.shared_inbox_url
|
= @account.shared_inbox_url
|
||||||
= fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times'
|
= fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times'
|
||||||
|
%td
|
||||||
|
- if @domain_block.nil?
|
||||||
|
= table_link_to 'ban', t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain)
|
||||||
|
|
||||||
%div.action-buttons
|
- if @account.suspended?
|
||||||
%div
|
%hr.spacer/
|
||||||
- if @account.local? && @account.user_approved?
|
|
||||||
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
|
|
||||||
- if @account.silenced?
|
|
||||||
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
|
|
||||||
- elsif !@account.local? || @account.user_approved?
|
|
||||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button button--destructive' if can?(:silence, @account)
|
|
||||||
|
|
||||||
- if @account.local?
|
%p.muted-hint= @deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(@deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible')
|
||||||
- if @account.user_pending?
|
|
||||||
= link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user)
|
|
||||||
= link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user)
|
|
||||||
|
|
||||||
- unless @account.user_confirmed?
|
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
|
||||||
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
|
|
||||||
|
|
||||||
- if @account.suspended?
|
- if @deletion_request.present?
|
||||||
= link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsuspend, @account)
|
= link_to t('admin.accounts.delete'), admin_account_path(@account.id), method: :destroy, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, @account)
|
||||||
- elsif !@account.local? || @account.user_approved?
|
- else
|
||||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
|
%div.action-buttons
|
||||||
|
%div
|
||||||
|
- if @account.local? && @account.user_approved?
|
||||||
|
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
|
||||||
|
|
||||||
- unless @account.local?
|
- if @account.user_disabled?
|
||||||
- if DomainBlock.rule_for(@account.domain)
|
= link_to t('admin.accounts.enable'), enable_admin_account_path(@account.id), method: :post, class: 'button' if can?(:enable, @account.user)
|
||||||
= link_to t('admin.domain_blocks.view'), admin_instance_path(@account.domain), class: 'button'
|
- else
|
||||||
|
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@account.id, type: 'disable'), class: 'button' if can?(:disable, @account.user)
|
||||||
|
|
||||||
|
- if @account.silenced?
|
||||||
|
= link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unsilence, @account)
|
||||||
|
- elsif !@account.local? || @account.user_approved?
|
||||||
|
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@account.id, type: 'silence'), class: 'button' if can?(:silence, @account)
|
||||||
|
|
||||||
|
- if @account.local?
|
||||||
|
- if @account.user_pending?
|
||||||
|
= link_to t('admin.accounts.approve'), approve_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, @account.user)
|
||||||
|
= link_to t('admin.accounts.reject'), reject_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, @account.user)
|
||||||
|
|
||||||
|
- unless @account.user_confirmed?
|
||||||
|
= link_to t('admin.accounts.confirm'), admin_account_confirmation_path(@account.id), method: :post, class: 'button' if can?(:confirm, @account.user)
|
||||||
|
|
||||||
|
- if !@account.local? || @account.user_approved?
|
||||||
|
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button' if can?(:suspend, @account)
|
||||||
|
|
||||||
|
%div
|
||||||
|
- if @account.local?
|
||||||
|
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
|
||||||
|
- if @account.user&.otp_required_for_login?
|
||||||
|
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
|
||||||
|
- if !@account.memorial? && @account.user_approved?
|
||||||
|
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
|
||||||
- else
|
- else
|
||||||
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'
|
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
|
||||||
|
|
||||||
%div
|
|
||||||
- if @account.local?
|
|
||||||
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
|
|
||||||
- if @account.user&.otp_required_for_login?
|
|
||||||
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
|
|
||||||
- if !@account.memorial? && @account.user_approved?
|
|
||||||
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
|
|
||||||
- else
|
|
||||||
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
|
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AccountDeletionWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: 'pull'
|
||||||
|
|
||||||
|
def perform(account_id)
|
||||||
|
DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: false)
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::AccountDeletionWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: 'pull'
|
||||||
|
|
||||||
|
def perform(account_id)
|
||||||
|
DeleteAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: true)
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,7 +5,9 @@ class Admin::SuspensionWorker
|
||||||
|
|
||||||
sidekiq_options queue: 'pull'
|
sidekiq_options queue: 'pull'
|
||||||
|
|
||||||
def perform(account_id, remove_user = false)
|
def perform(account_id)
|
||||||
SuspendAccountService.new.call(Account.find(account_id), reserve_username: true, reserve_email: !remove_user)
|
SuspendAccountService.new.call(Account.find(account_id))
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::UnsuspensionWorker
|
||||||
|
include Sidekiq::Worker
|
||||||
|
|
||||||
|
sidekiq_options queue: 'pull'
|
||||||
|
|
||||||
|
def perform(account_id)
|
||||||
|
UnsuspendAccountService.new.call(Account.find(account_id))
|
||||||
|
rescue ActiveRecord::RecordNotFound
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,9 +6,22 @@ class Scheduler::UserCleanupScheduler
|
||||||
sidekiq_options lock: :until_executed, retry: 0
|
sidekiq_options lock: :until_executed, retry: 0
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
|
clean_unconfirmed_accounts!
|
||||||
|
clean_suspended_accounts!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def clean_unconfirmed_accounts!
|
||||||
User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).reorder(nil).find_in_batches do |batch|
|
User.where('confirmed_at is NULL AND confirmation_sent_at <= ?', 2.days.ago).reorder(nil).find_in_batches do |batch|
|
||||||
Account.where(id: batch.map(&:account_id)).delete_all
|
Account.where(id: batch.map(&:account_id)).delete_all
|
||||||
User.where(id: batch.map(&:id)).delete_all
|
User.where(id: batch.map(&:id)).delete_all
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clean_suspended_accounts!
|
||||||
|
AccountDeletionRequest.where('created_at <= ?', AccountDeletionRequest::DELAY_TO_DELETION.ago).reorder(nil).find_each do |deletion_request|
|
||||||
|
Admin::AccountDeletionWorker.perform_async(deletion_request.account_id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -98,6 +98,7 @@ en:
|
||||||
add_email_domain_block: Block e-mail domain
|
add_email_domain_block: Block e-mail domain
|
||||||
approve: Approve
|
approve: Approve
|
||||||
approve_all: Approve all
|
approve_all: Approve all
|
||||||
|
approved_msg: Successfully approved %{username}'s sign-up application
|
||||||
are_you_sure: Are you sure?
|
are_you_sure: Are you sure?
|
||||||
avatar: Avatar
|
avatar: Avatar
|
||||||
by_domain: Domain
|
by_domain: Domain
|
||||||
|
@ -111,18 +112,21 @@ en:
|
||||||
confirm: Confirm
|
confirm: Confirm
|
||||||
confirmed: Confirmed
|
confirmed: Confirmed
|
||||||
confirming: Confirming
|
confirming: Confirming
|
||||||
|
delete: Delete data
|
||||||
deleted: Deleted
|
deleted: Deleted
|
||||||
demote: Demote
|
demote: Demote
|
||||||
disable: Disable
|
destroyed_msg: "%{username}'s data is now queued to be deleted imminently"
|
||||||
|
disable: Freeze
|
||||||
disable_two_factor_authentication: Disable 2FA
|
disable_two_factor_authentication: Disable 2FA
|
||||||
disabled: Disabled
|
disabled: Frozen
|
||||||
display_name: Display name
|
display_name: Display name
|
||||||
domain: Domain
|
domain: Domain
|
||||||
edit: Edit
|
edit: Edit
|
||||||
email: Email
|
email: Email
|
||||||
email_status: Email status
|
email_status: Email status
|
||||||
enable: Enable
|
enable: Unfreeze
|
||||||
enabled: Enabled
|
enabled: Enabled
|
||||||
|
enabled_msg: Successfully unfroze %{username}'s account
|
||||||
followers: Followers
|
followers: Followers
|
||||||
follows: Follows
|
follows: Follows
|
||||||
header: Header
|
header: Header
|
||||||
|
@ -138,6 +142,8 @@ en:
|
||||||
login_status: Login status
|
login_status: Login status
|
||||||
media_attachments: Media attachments
|
media_attachments: Media attachments
|
||||||
memorialize: Turn into memoriam
|
memorialize: Turn into memoriam
|
||||||
|
memorialized: Memorialized
|
||||||
|
memorialized_msg: Successfully turned %{username} into a memorial account
|
||||||
moderation:
|
moderation:
|
||||||
active: Active
|
active: Active
|
||||||
all: All
|
all: All
|
||||||
|
@ -158,10 +164,14 @@ en:
|
||||||
public: Public
|
public: Public
|
||||||
push_subscription_expires: PuSH subscription expires
|
push_subscription_expires: PuSH subscription expires
|
||||||
redownload: Refresh profile
|
redownload: Refresh profile
|
||||||
|
redownloaded_msg: Successfully refreshed %{username}'s profile from origin
|
||||||
reject: Reject
|
reject: Reject
|
||||||
reject_all: Reject all
|
reject_all: Reject all
|
||||||
|
rejected_msg: Successfully rejected %{username}'s sign-up application
|
||||||
remove_avatar: Remove avatar
|
remove_avatar: Remove avatar
|
||||||
remove_header: Remove header
|
remove_header: Remove header
|
||||||
|
removed_avatar_msg: Successfully removed %{username}'s avatar image
|
||||||
|
removed_header_msg: Successfully removed %{username}'s header image
|
||||||
resend_confirmation:
|
resend_confirmation:
|
||||||
already_confirmed: This user is already confirmed
|
already_confirmed: This user is already confirmed
|
||||||
send: Resend confirmation email
|
send: Resend confirmation email
|
||||||
|
@ -182,18 +192,23 @@ en:
|
||||||
show:
|
show:
|
||||||
created_reports: Made reports
|
created_reports: Made reports
|
||||||
targeted_reports: Reported by others
|
targeted_reports: Reported by others
|
||||||
silence: Silence
|
silence: Limit
|
||||||
silenced: Silenced
|
silenced: Limited
|
||||||
statuses: Statuses
|
statuses: Statuses
|
||||||
subscribe: Subscribe
|
subscribe: Subscribe
|
||||||
suspended: Suspended
|
suspended: Suspended
|
||||||
|
suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable but it will not recover any data it previously had.
|
||||||
|
suspension_reversible_hint_html: The account has been suspended, and the data will be fully removed on %{date}. Until then, the account can be restored without any ill effects. If you wish to remove all of the account's data immediately, you can do so below.
|
||||||
time_in_queue: Waiting in queue %{time}
|
time_in_queue: Waiting in queue %{time}
|
||||||
title: Accounts
|
title: Accounts
|
||||||
unconfirmed_email: Unconfirmed email
|
unconfirmed_email: Unconfirmed email
|
||||||
undo_silenced: Undo silence
|
undo_silenced: Undo silence
|
||||||
undo_suspension: Undo suspension
|
undo_suspension: Undo suspension
|
||||||
|
unsilenced_msg: Successfully unlimited %{username}'s account
|
||||||
unsubscribe: Unsubscribe
|
unsubscribe: Unsubscribe
|
||||||
|
unsuspended_msg: Successfully unsuspended %{username}'s account
|
||||||
username: Username
|
username: Username
|
||||||
|
view_domain: View summary for domain
|
||||||
warn: Warn
|
warn: Warn
|
||||||
web: Web
|
web: Web
|
||||||
whitelisted: Allowed for federation
|
whitelisted: Allowed for federation
|
||||||
|
@ -1304,9 +1319,9 @@ en:
|
||||||
title: Sign in attempt
|
title: Sign in attempt
|
||||||
warning:
|
warning:
|
||||||
explanation:
|
explanation:
|
||||||
disable: While your account is frozen, your account data remains intact, but you cannot perform any actions until it is unlocked.
|
disable: You can no longer login to your account or use it in any other way, but your profile and other data remains intact.
|
||||||
silence: While your account is limited, only people who are already following you will see your toots on this server, and you may be excluded from various public listings. However, others may still manually follow you.
|
silence: You can still use your account but only people who are already following you will see your toots on this server, and you may be excluded from various public listings. However, others may still manually follow you.
|
||||||
suspend: Your account has been suspended, and all of your toots and your uploaded media files have been irreversibly removed from this server, and servers where you had followers.
|
suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed, but we will retain some data to prevent you from evading the suspension.
|
||||||
get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}.
|
get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}.
|
||||||
review_server_policies: Review server policies
|
review_server_policies: Review server policies
|
||||||
statuses: 'Specifically, for:'
|
statuses: 'Specifically, for:'
|
||||||
|
|
|
@ -90,10 +90,10 @@ en:
|
||||||
text: Custom warning
|
text: Custom warning
|
||||||
type: Action
|
type: Action
|
||||||
types:
|
types:
|
||||||
disable: Disable login
|
disable: Freeze
|
||||||
none: Do nothing
|
none: Send a warning
|
||||||
silence: Silence
|
silence: Limit
|
||||||
suspend: Suspend and irreversibly delete account data
|
suspend: Suspend
|
||||||
warning_preset_id: Use a warning preset
|
warning_preset_id: Use a warning preset
|
||||||
announcement:
|
announcement:
|
||||||
all_day: All-day event
|
all_day: All-day event
|
||||||
|
|
|
@ -232,7 +232,7 @@ Rails.application.routes.draw do
|
||||||
|
|
||||||
resources :report_notes, only: [:create, :destroy]
|
resources :report_notes, only: [:create, :destroy]
|
||||||
|
|
||||||
resources :accounts, only: [:index, :show] do
|
resources :accounts, only: [:index, :show, :destroy] do
|
||||||
member do
|
member do
|
||||||
post :enable
|
post :enable
|
||||||
post :unsilence
|
post :unsilence
|
||||||
|
@ -466,7 +466,7 @@ Rails.application.routes.draw do
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :admin do
|
namespace :admin do
|
||||||
resources :accounts, only: [:index, :show] do
|
resources :accounts, only: [:index, :show, :destroy] do
|
||||||
member do
|
member do
|
||||||
post :enable
|
post :enable
|
||||||
post :unsilence
|
post :unsilence
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
class CreateAccountDeletionRequests < ActiveRecord::Migration[5.2]
|
||||||
|
def change
|
||||||
|
create_table :account_deletion_requests do |t|
|
||||||
|
t.references :account, foreign_key: { on_delete: :cascade }
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
10
db/schema.rb
10
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2020_06_30_190544) do
|
ActiveRecord::Schema.define(version: 2020_09_08_193330) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -36,6 +36,13 @@ ActiveRecord::Schema.define(version: 2020_06_30_190544) do
|
||||||
t.index ["conversation_id"], name: "index_account_conversations_on_conversation_id"
|
t.index ["conversation_id"], name: "index_account_conversations_on_conversation_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "account_deletion_requests", force: :cascade do |t|
|
||||||
|
t.bigint "account_id"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id"], name: "index_account_deletion_requests_on_account_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "account_domain_blocks", force: :cascade do |t|
|
create_table "account_domain_blocks", force: :cascade do |t|
|
||||||
t.string "domain"
|
t.string "domain"
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
|
@ -926,6 +933,7 @@ ActiveRecord::Schema.define(version: 2020_06_30_190544) do
|
||||||
add_foreign_key "account_aliases", "accounts", on_delete: :cascade
|
add_foreign_key "account_aliases", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_conversations", "accounts", on_delete: :cascade
|
add_foreign_key "account_conversations", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_conversations", "conversations", on_delete: :cascade
|
add_foreign_key "account_conversations", "conversations", on_delete: :cascade
|
||||||
|
add_foreign_key "account_deletion_requests", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
|
add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
|
||||||
add_foreign_key "account_identity_proofs", "accounts", on_delete: :cascade
|
add_foreign_key "account_identity_proofs", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify
|
add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify
|
||||||
|
|
|
@ -87,7 +87,7 @@ module Mastodon
|
||||||
say('Use --force to reattach it anyway and delete the other user')
|
say('Use --force to reattach it anyway and delete the other user')
|
||||||
return
|
return
|
||||||
elsif account.user.present?
|
elsif account.user.present?
|
||||||
account.user.destroy!
|
DeleteAccountService.new.call(account, reserve_email: false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
say("Deleting user with #{account.statuses_count} statuses, this might take a while...")
|
say("Deleting user with #{account.statuses_count} statuses, this might take a while...")
|
||||||
SuspendAccountService.new.call(account, reserve_email: false)
|
DeleteAccountService.new.call(account, reserve_email: false)
|
||||||
say('OK', :green)
|
say('OK', :green)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
processed, = parallelize_with_progress(scope) do |account|
|
processed, = parallelize_with_progress(scope) do |account|
|
||||||
SuspendAccountService.new.call(account, reserve_username: false, skip_side_effects: true) unless options[:dry_run]
|
DeleteAccountService.new.call(account, reserve_username: false, skip_side_effects: true) unless options[:dry_run]
|
||||||
end
|
end
|
||||||
|
|
||||||
DomainBlock.where(domain: domains).destroy_all unless options[:dry_run]
|
DomainBlock.where(domain: domains).destroy_all unless options[:dry_run]
|
||||||
|
|
|
@ -199,9 +199,10 @@ RSpec.describe Auth::RegistrationsController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
subject do
|
subject do
|
||||||
|
inviter = Fabricate(:user, confirmed_at: 2.days.ago)
|
||||||
Setting.registrations_mode = 'approved'
|
Setting.registrations_mode = 'approved'
|
||||||
request.headers["Accept-Language"] = accept_language
|
request.headers["Accept-Language"] = accept_language
|
||||||
invite = Fabricate(:invite, max_uses: nil, expires_at: 1.hour.from_now)
|
invite = Fabricate(:invite, user: inviter, max_uses: nil, expires_at: 1.hour.from_now)
|
||||||
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code, agreement: 'true' } }
|
post :create, params: { user: { account_attributes: { username: 'test' }, email: 'test@example.com', password: '12345678', password_confirmation: '12345678', 'invite_code': invite.code, agreement: 'true' } }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ require 'rails_helper'
|
||||||
describe ApplicationController, type: :controller do
|
describe ApplicationController, type: :controller do
|
||||||
controller do
|
controller do
|
||||||
include ExportControllerConcern
|
include ExportControllerConcern
|
||||||
|
|
||||||
def index
|
def index
|
||||||
send_export_file
|
send_export_file
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Fabricator(:account_deletion_request) do
|
||||||
|
account
|
||||||
|
end
|
|
@ -0,0 +1,4 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe AccountDeletionRequest, type: :model do
|
||||||
|
end
|
|
@ -29,7 +29,7 @@ RSpec.describe Invite, type: :model do
|
||||||
|
|
||||||
it 'returns false when invite creator has been disabled' do
|
it 'returns false when invite creator has been disabled' do
|
||||||
invite = Fabricate(:invite, max_uses: nil, expires_at: nil)
|
invite = Fabricate(:invite, max_uses: nil, expires_at: nil)
|
||||||
SuspendAccountService.new.call(invite.user.account)
|
invite.user.account.suspend!
|
||||||
expect(invite.valid_for_use?).to be false
|
expect(invite.valid_for_use?).to be false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
require 'rails_helper'
|
require 'rails_helper'
|
||||||
|
|
||||||
RSpec.describe SuspendAccountService, type: :service do
|
RSpec.describe DeleteAccountService, type: :service do
|
||||||
describe '#call on local account' do
|
describe '#call on local account' do
|
||||||
before do
|
before do
|
||||||
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
|
stub_request(:post, "https://alice.com/inbox").to_return(status: 201)
|
Reference in New Issue