diff --git a/Gemfile b/Gemfile index 16df1c376b7f1ebeae26bae8d199200d6b388314..a5bf2daffb7f172433eea1176085a6b73ca3bc0d 100644 --- a/Gemfile +++ b/Gemfile @@ -28,7 +28,7 @@ gem 'browser' gem 'charlock_holmes', '~> 0.7.5' gem 'iso-639' gem 'cld3', '~> 3.2.0' -gem 'devise', '~> 4.2' +gem 'devise', '~> 4.3' gem 'devise-two-factor', '~> 3.0' gem 'doorkeeper', '~> 4.2' gem 'fast_blank', '~> 1.0' diff --git a/Gemfile.lock b/Gemfile.lock index 46c87d7f789c589dadf0b3f60989d402cab78471..1b7af06a490d24ff9ff444ec6e5ada52ef85223c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -561,7 +561,7 @@ DEPENDENCIES charlock_holmes (~> 0.7.5) cld3 (~> 3.2.0) climate_control (~> 0.2) - devise (~> 4.2) + devise (~> 4.3) devise-two-factor (~> 3.0) doorkeeper (~> 4.2) dotenv-rails (~> 2.2) diff --git a/app/controllers/auth/registrations_controller.rb b/app/controllers/auth/registrations_controller.rb index da0b6512f28afcf464b1c02f982f00967971051b..b8ff4e54f28b4d4f3e5970dbeadfdfe8db691f97 100644 --- a/app/controllers/auth/registrations_controller.rb +++ b/app/controllers/auth/registrations_controller.rb @@ -37,6 +37,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController new_user_session_path end + def after_update_path_for(_resource) + edit_user_registration_path + end + def check_enabled_registrations redirect_to root_path if single_user_mode? || !allowed_registrations? end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb index 5a062dc257413c89e803839fd1a70c24b1c89bff..7821be32b2e3c193a85ff77d5e48f3b3323dc7ab 100644 --- a/app/mailers/user_mailer.rb +++ b/app/mailers/user_mailer.rb @@ -13,7 +13,9 @@ class UserMailer < Devise::Mailer return if @resource.disabled? I18n.with_locale(@resource.locale || I18n.default_locale) do - mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, subject: I18n.t('devise.mailer.confirmation_instructions.subject', instance: @instance) + mail to: @resource.unconfirmed_email.blank? ? @resource.email : @resource.unconfirmed_email, + subject: I18n.t(@resource.pending_reconfirmation? ? 'devise.mailer.reconfirmation_instructions.subject' : 'devise.mailer.confirmation_instructions.subject', instance: @instance), + template_name: @resource.pending_reconfirmation? ? 'reconfirmation_instructions' : 'confirmation_instructions' end end @@ -39,4 +41,15 @@ class UserMailer < Devise::Mailer mail to: @resource.email, subject: I18n.t('devise.mailer.password_change.subject') end end + + def email_changed(user, **) + @resource = user + @instance = Rails.configuration.x.local_domain + + return if @resource.disabled? + + I18n.with_locale(@resource.locale || I18n.default_locale) do + mail to: @resource.email, subject: I18n.t('devise.mailer.email_changed.subject') + end + end end diff --git a/app/models/user.rb b/app/models/user.rb index 3ce6517a65b1c00112c8afc2fe8d114e382358cd..a82a7d28a0e6928783e99424c6d177ca43ac69bd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -41,12 +41,15 @@ class User < ApplicationRecord ACTIVE_DURATION = 14.days - devise :registerable, :recoverable, - :rememberable, :trackable, :validatable, :confirmable, - :two_factor_authenticatable, :two_factor_backupable, - otp_secret_encryption_key: ENV['OTP_SECRET'], + devise :two_factor_authenticatable, + otp_secret_encryption_key: ENV['OTP_SECRET'] + + devise :two_factor_backupable, otp_number_of_backup_codes: 10 + devise :registerable, :recoverable, :rememberable, :trackable, :validatable, + :confirmable + belongs_to :account, inverse_of: :user, required: true belongs_to :invite, counter_cache: :uses accepts_nested_attributes_for :account diff --git a/app/views/user_mailer/email_changed.en.html.erb b/app/views/user_mailer/email_changed.en.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..c10680086562a2ec01867fff76bed8a54ec2e736 --- /dev/null +++ b/app/views/user_mailer/email_changed.en.html.erb @@ -0,0 +1,15 @@ +<p>Hello <%= @resource.email %>!</p> + +<% if @resource&.unconfirmed_email? %> + <p>We're contacting you to notify you that the email you use on <%= @instance %> is being changed to <%= @resource.unconfirmed_email %>.</p> +<% else %> + <p>We're contacting you to notify you that the email you use on <%= @instance %> has been changed to <%= @resource.email %>.</p> +<% end %> + +<p> + If you did not change your email, it is likely that someone has gained access to your account. Please change your password immediately or contact the instance admin if you're locked out of your account. +</p> + +<p>Sincerely,<p> + +<p>The <%= @instance %> team</p> diff --git a/app/views/user_mailer/email_changed.en.text.erb b/app/views/user_mailer/email_changed.en.text.erb new file mode 100644 index 0000000000000000000000000000000000000000..9719724611846cd533cba34f8ecc21966bce3efe --- /dev/null +++ b/app/views/user_mailer/email_changed.en.text.erb @@ -0,0 +1,13 @@ +Hello <%= @resource.email %>! + +<% if @resource&.unconfirmed_email? %> +We're contacting you to notify you that the email you use on <%= @instance %> is being changed to <%= @resource.unconfirmed_email %>. +<% else %> +We're contacting you to notify you that the email you use on <%= @instance %> has been changed to <%= @resource.email %>. +<% end %> + +If you did not change your email, it is likely that someone has gained access to your account. Please change your password immediately or contact the instance admin if you're locked out of your account. + +Sincerely, + +The <%= @instance %> team diff --git a/app/views/user_mailer/reconfirmation_instructions.en.html.erb b/app/views/user_mailer/reconfirmation_instructions.en.html.erb new file mode 100644 index 0000000000000000000000000000000000000000..31866a3c84e4dfb4dd22193bb1826d25afe7c61e --- /dev/null +++ b/app/views/user_mailer/reconfirmation_instructions.en.html.erb @@ -0,0 +1,15 @@ +<p>Hello <%= @resource.unconfirmed_email %>!</p> + +<p>You requested a change to the email address you use on <%= @instance %>.</p> + +<p>To confirm your new email, please click on the following link:<br> +<%= link_to 'Confirm my email address', confirmation_url(@resource, confirmation_token: @token) %></p> + +<p>If the above link did not work, copy and paste this URL into your address bar: <br> +<span><%= confirmation_url(@resource, confirmation_token: @token) %></span> + +<p>Please also check out our <%= link_to 'terms and conditions', terms_url %>.</p> + +<p>Sincerely,<p> + +<p>The <%= @instance %> team</p> diff --git a/app/views/user_mailer/reconfirmation_instructions.en.text.erb b/app/views/user_mailer/reconfirmation_instructions.en.text.erb new file mode 100644 index 0000000000000000000000000000000000000000..c1c735b3ac9ede3ba7f3bdc38bbcc447f93b5027 --- /dev/null +++ b/app/views/user_mailer/reconfirmation_instructions.en.text.erb @@ -0,0 +1,12 @@ +Hello <%= @resource.unconfirmed_email %>! + +You requested a change to the email address you use on <%= @instance %>. + +To confirm your new email, please click on the following link: +<%= confirmation_url(@resource, confirmation_token: @token) %> + +Please also check out our terms and conditions <%= terms_url %> + +Sincerely, + +The <%= @instance %> team diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 64c4e12ff4c8a8697bbc9e5c9547e12f4d9ddb71..07912c28b861fa298e122a905a8b9151c06d5e64 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -137,6 +137,9 @@ Devise.setup do |config| # Setup a pepper to generate the encrypted password. # config.pepper = '104d16705f794923e77c5e5167b52452d00646dc952a2d30b541c24086e647012c7b9625f253c51912e455981e503446772973d5f1638631196c819d7137fad4' + # Send a notification to the original email when the user's email is changed. + config.send_email_changed_notification = true + # Send a notification email when the user's password is changed config.send_password_change_notification = true @@ -160,7 +163,7 @@ Devise.setup do |config| # initial account confirmation) to be applied. Requires additional unconfirmed_email # db field (see migrations). Until confirmed, new email is stored in # unconfirmed_email column, and copied to email column on successful confirmation. - config.reconfirmable = false + config.reconfirmable = true # Defines which key will be used when confirming an account # config.confirmation_keys = [:email] diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 586c5349d730e8dcbf7b46212f716778b5bcae2d..c5ae583ff0f8e91d194e0091774a2305bcc7b114 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -18,8 +18,12 @@ en: mailer: confirmation_instructions: subject: 'Mastodon: Confirmation instructions for %{instance}' + email_changed: + subject: 'Mastodon: Email changed' password_change: subject: 'Mastodon: Password changed' + reconfirmation_instructions: + subject: 'Mastodon: Confirm email for %{instance}' reset_password_instructions: subject: 'Mastodon: Reset password instructions' unlock_instructions: diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb index 1f6d4401543a931f72da7679573ac63d6ca33a5e..9f17993e0efb3ef1fa24a8be95029f79910dbf57 100644 --- a/spec/mailers/user_mailer_spec.rb +++ b/spec/mailers/user_mailer_spec.rb @@ -33,6 +33,20 @@ describe UserMailer, type: :mailer do instance: Rails.configuration.x.local_domain end + describe 'reconfirmation_instructions' do + let(:mail) { UserMailer.confirmation_instructions(receiver, 'spec') } + + it 'renders reconfirmation instructions' do + receiver.update!(email: 'new-email@example.com', locale: nil) + expect(mail.body.encoded).to include 'new-email@example.com' + expect(mail.body.encoded).to include 'spec' + expect(mail.body.encoded).to include Rails.configuration.x.local_domain + expect(mail.subject).to eq I18n.t('devise.mailer.reconfirmation_instructions.subject', + instance: Rails.configuration.x.local_domain, + locale: I18n.default_locale) + end + end + describe 'reset_password_instructions' do let(:mail) { UserMailer.reset_password_instructions(receiver, 'spec') } @@ -57,4 +71,16 @@ describe UserMailer, type: :mailer do include_examples 'localized subject', 'devise.mailer.password_change.subject' end + + describe 'email_changed' do + let(:mail) { UserMailer.email_changed(receiver) } + + it 'renders email change notification' do + receiver.update!(locale: nil) + expect(mail.body.encoded).to include receiver.email + end + + include_examples 'localized subject', + 'devise.mailer.email_changed.subject' + end end