Add confirmation step for email changes (#6071)
* Add confirmation step for email changes This adds a confirmation step for email changes of existing users. Like the initial account confirmation, a confirmation link is sent to the new address. Additionally, a notification is sent to the existing address when the change is initiated. This message includes instruction to reset the password immediately or to contact the instance admin if the change was not initiated by the account owner. Fixes #3871 * Add review fixes
This commit is contained in:
		
				
					committed by
					
						 Eugen Rochko
						Eugen Rochko
					
				
			
			
				
	
			
			
			
						parent
						
							b6af88192f
						
					
				
				
					commit
					04ecf44c2f
				
			
							
								
								
									
										2
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								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' | ||||
|   | ||||
| @@ -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) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
							
								
								
									
										15
									
								
								app/views/user_mailer/email_changed.en.html.erb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/views/user_mailer/email_changed.en.html.erb
									
									
									
									
									
										Normal file
									
								
							| @@ -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> | ||||
							
								
								
									
										13
									
								
								app/views/user_mailer/email_changed.en.text.erb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/views/user_mailer/email_changed.en.text.erb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -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> | ||||
| @@ -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 | ||||
| @@ -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] | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user