diff --git a/app/controllers/settings/migration/redirects_controller.rb b/app/controllers/settings/migration/redirects_controller.rb new file mode 100644 index 00000000000..6e5b72ffba2 --- /dev/null +++ b/app/controllers/settings/migration/redirects_controller.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +class Settings::Migration::RedirectsController < Settings::BaseController + layout 'admin' + + before_action :authenticate_user! + before_action :require_not_suspended! + + skip_before_action :require_functional! + + def new + @redirect = Form::Redirect.new + end + + def create + @redirect = Form::Redirect.new(resource_params.merge(account: current_account)) + + if @redirect.valid_with_challenge?(current_user) + current_account.update!(moved_to_account: @redirect.target_account) + ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) + redirect_to settings_migration_path, notice: I18n.t('migrations.moved_msg', acct: current_account.moved_to_account.acct) + else + render :new + end + end + + def destroy + if current_account.moved_to_account_id.present? + current_account.update!(moved_to_account: nil) + ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) + end + + redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg') + end + + private + + def resource_params + params.require(:form_redirect).permit(:acct, :current_password, :current_username) + end + + def require_not_suspended! + forbidden if current_account.suspended? + end +end diff --git a/app/controllers/settings/migrations_controller.rb b/app/controllers/settings/migrations_controller.rb index 90092c69256..00bde1d616b 100644 --- a/app/controllers/settings/migrations_controller.rb +++ b/app/controllers/settings/migrations_controller.rb @@ -27,15 +27,6 @@ class Settings::MigrationsController < Settings::BaseController end end - def cancel - if current_account.moved_to_account_id.present? - current_account.update!(moved_to_account: nil) - ActivityPub::UpdateDistributionWorker.perform_async(current_account.id) - end - - redirect_to settings_migration_path, notice: I18n.t('migrations.cancelled_msg') - end - helper_method :on_cooldown? private diff --git a/app/models/account_migration.rb b/app/models/account_migration.rb index e2c2cb08507..681b5b2cd0b 100644 --- a/app/models/account_migration.rb +++ b/app/models/account_migration.rb @@ -47,8 +47,7 @@ class AccountMigration < ApplicationRecord end def acct=(val) - val = val.to_s.strip - super(val.start_with?('@') ? val[1..-1] : val) + super(val.to_s.strip.gsub(/\A@/, '')) end private diff --git a/app/models/form/redirect.rb b/app/models/form/redirect.rb new file mode 100644 index 00000000000..a7961f8e8aa --- /dev/null +++ b/app/models/form/redirect.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +class Form::Redirect + include ActiveModel::Model + + attr_accessor :account, :target_account, :current_password, + :current_username + + attr_reader :acct + + validates :acct, presence: true, domain: { acct: true } + validate :validate_target_account + + def valid_with_challenge?(current_user) + if current_user.encrypted_password.present? + errors.add(:current_password, :invalid) unless current_user.valid_password?(current_password) + else + errors.add(:current_username, :invalid) unless account.username == current_username + end + + return false unless errors.empty? + + set_target_account + valid? + end + + def acct=(val) + @acct = val.to_s.strip.gsub(/\A@/, '') + end + + private + + def set_target_account + @target_account = ResolveAccountService.new.call(acct) + rescue Goldfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error + # Validation will take care of it + end + + def validate_target_account + if target_account.nil? + errors.add(:acct, I18n.t('migrations.errors.not_found')) + else + errors.add(:acct, I18n.t('migrations.errors.already_moved')) if account.moved_to_account_id.present? && account.moved_to_account_id == target_account.id + errors.add(:acct, I18n.t('migrations.errors.move_to_self')) if account.id == target_account.id + end + end +end diff --git a/app/views/auth/registrations/edit.html.haml b/app/views/auth/registrations/edit.html.haml index 885171c58b9..a155c75c987 100644 --- a/app/views/auth/registrations/edit.html.haml +++ b/app/views/auth/registrations/edit.html.haml @@ -30,7 +30,18 @@ = render 'sessions' +%hr.spacer/ + +%h3= t('auth.migrate_account') +%p.muted-hint= t('auth.migrate_account_html', path: settings_migration_path) + +%hr.spacer/ + +%h3= t('migrations.incoming_migrations') +%p.muted-hint= t('migrations.incoming_migrations_html', path: settings_aliases_path) + - if open_deletion? && !current_account.suspended? %hr.spacer/ + %h3= t('auth.delete_account') %p.muted-hint= t('auth.delete_account_html', path: settings_delete_path) diff --git a/app/views/settings/migration/redirects/new.html.haml b/app/views/settings/migration/redirects/new.html.haml new file mode 100644 index 00000000000..017450f4b94 --- /dev/null +++ b/app/views/settings/migration/redirects/new.html.haml @@ -0,0 +1,27 @@ +- content_for :page_title do + = t('settings.migrate') + += simple_form_for @redirect, url: settings_migration_redirect_path do |f| + %p.hint= t('migrations.warning.before') + + %ul.hint + %li.warning-hint= t('migrations.warning.redirect') + %li.warning-hint= t('migrations.warning.other_data') + %li.warning-hint= t('migrations.warning.disabled_account') + + %hr.spacer/ + + = render 'shared/error_messages', object: @redirect + + .fields-row + .fields-row__column.fields-group.fields-row__column-6 + = f.input :acct, wrapper: :with_block_label, input_html: { autocapitalize: 'none', autocorrect: 'off' }, label: t('simple_form.labels.account_migration.acct'), hint: t('simple_form.hints.account_migration.acct') + + .fields-row__column.fields-group.fields-row__column-6 + - if current_user.encrypted_password.present? + = f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true + - else + = f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true + + .actions + = f.button :button, t('migrations.set_redirect'), type: :submit, class: 'button button--destructive' diff --git a/app/views/settings/migrations/show.html.haml b/app/views/settings/migrations/show.html.haml index 1e5c47726f3..078eaebc6c1 100644 --- a/app/views/settings/migrations/show.html.haml +++ b/app/views/settings/migrations/show.html.haml @@ -12,28 +12,32 @@ %p.hint= t('migrations.cancel_explanation') - %p.hint= link_to t('migrations.cancel'), cancel_settings_migration_path, data: { method: :post } + %p.hint= link_to t('migrations.cancel'), settings_migration_redirect_path, data: { method: :delete } - else %p.hint %span.positive-hint= t('migrations.not_redirecting') %hr.spacer/ -%h3= t 'migrations.proceed_with_move' +%h3= t('auth.migrate_account') = simple_form_for @migration, url: settings_migration_path do |f| - if on_cooldown? - %span.warning-hint= t('migrations.on_cooldown', count: ((@cooldown.cooldown_at - Time.now.utc) / 1.day.seconds).ceil) + %p.hint + %span.warning-hint= t('migrations.on_cooldown', count: ((@cooldown.cooldown_at - Time.now.utc) / 1.day.seconds).ceil) - else %p.hint= t('migrations.warning.before') %ul.hint %li.warning-hint= t('migrations.warning.followers') + %li.warning-hint= t('migrations.warning.redirect') %li.warning-hint= t('migrations.warning.other_data') %li.warning-hint= t('migrations.warning.backreference_required') %li.warning-hint= t('migrations.warning.cooldown') %li.warning-hint= t('migrations.warning.disabled_account') + %p.hint= t('migrations.warning.only_redirect_html', path: new_settings_migration_redirect_path) + %hr.spacer/ = render 'shared/error_messages', object: @migration diff --git a/config/locales/en.yml b/config/locales/en.yml index ee798e87f13..1e7d0701b51 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -831,13 +831,16 @@ en: past_migrations: Past migrations proceed_with_move: Move followers redirecting_to: Your account is redirecting to %{acct}. + set_redirect: Set redirect warning: backreference_required: The new account must first be configured to back-reference this one before: 'Before proceeding, please read these notes carefully:' cooldown: After moving there is a cooldown period during which you will not be able to move again disabled_account: Your current account will not be fully usable afterwards. However, you will have access to data export as well as re-activation. followers: This action will move all followers from the current account to the new account + only_redirect_html: Alternatively, you can only put up a redirect on your profile. other_data: No other data will be moved automatically + redirect: Your current account's profile will be updated with a redirect notice and be excluded from searches moderation: title: Moderation notification_mailer: diff --git a/config/routes.rb b/config/routes.rb index 37e0cbdee8a..f1a69cf5c53 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -134,11 +134,10 @@ Rails.application.routes.draw do end resource :delete, only: [:show, :destroy] + resource :migration, only: [:show, :create] - resource :migration, only: [:show, :create] do - collection do - post :cancel - end + namespace :migration do + resource :redirect, only: [:new, :create, :destroy] end resources :aliases, only: [:index, :create, :destroy]