diff --git a/app/controllers/users/email_addresses/confirmations_controller.rb b/app/controllers/users/email_addresses/confirmations_controller.rb
index 5e5d8f0ed..10ad9ffed 100644
--- a/app/controllers/users/email_addresses/confirmations_controller.rb
+++ b/app/controllers/users/email_addresses/confirmations_controller.rb
@@ -12,7 +12,7 @@ class Users::EmailAddresses::ConfirmationsController < ApplicationController
user = User.change_email_address_using_token(token)
terminate_session if Current.session
- start_new_session_for user.reload.identity
+ start_new_session_for user.identity
redirect_to edit_user_url(script_name: user.account.slug, id: user)
end
diff --git a/app/mailers/identity_mailer.rb b/app/mailers/identity_mailer.rb
new file mode 100644
index 000000000..1e988f2b0
--- /dev/null
+++ b/app/mailers/identity_mailer.rb
@@ -0,0 +1,7 @@
+class IdentityMailer < ApplicationMailer
+ def email_change_confirmation(email_address:, token:, user:)
+ @token = token
+ @user = user
+ mail to: email_address, subject: "Confirm your new email address"
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 6b985c2df..6d0bba141 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1,5 +1,5 @@
class User < ApplicationRecord
- include Accessor, Assignee, Attachable, Configurable,
+ include Accessor, Assignee, Attachable, Configurable, EmailAddressChangeable,
Mentionable, Named, Notifiable, Role, Searcher, Watcher
include Timelined # Depends on Accessor
diff --git a/app/models/user/email_address_changeable.rb b/app/models/user/email_address_changeable.rb
new file mode 100644
index 000000000..80e194231
--- /dev/null
+++ b/app/models/user/email_address_changeable.rb
@@ -0,0 +1,57 @@
+module User::EmailAddressChangeable
+ EMAIL_CHANGE_TOKEN_PURPOSE = "change_email_address"
+ EMAIL_CHANGE_TOKEN_EXPIRATION = 30.minutes
+
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def change_email_address_using_token(token)
+ parsed_token = SignedGlobalID.parse(token, for: EMAIL_CHANGE_TOKEN_PURPOSE)
+ user = parsed_token&.find
+
+ if parsed_token.nil?
+ raise ArgumentError, "The token is invalid"
+ elsif user.nil?
+ raise ArgumentError, "The user no longer exists"
+ elsif user.identity.email_address != parsed_token.params.fetch("old_email_address")
+ raise ArgumentError, "The token was generated for a different email address"
+ else
+ new_email_address = parsed_token.params.fetch("new_email_address")
+ user.change_email_address(new_email_address)
+ end
+ end
+ end
+
+ def send_email_address_change_confirmation(new_email_address)
+ token = generate_email_address_change_token(
+ to: new_email_address,
+ expires_in: EMAIL_CHANGE_TOKEN_EXPIRATION
+ )
+
+ IdentityMailer.email_change_confirmation(
+ email_address: new_email_address,
+ token: token,
+ user: self
+ ).deliver_later
+ end
+
+ def change_email_address(new_email_address)
+ transaction do
+ new_identity = Identity.find_or_create_by!(email_address: new_email_address)
+ update!(identity: new_identity)
+ end
+
+ self
+ end
+
+ private
+ def generate_email_address_change_token(from: identity.email_address, to:, **options)
+ options = options.reverse_merge(
+ for: EMAIL_CHANGE_TOKEN_PURPOSE,
+ old_email_address: from,
+ new_email_address: to,
+ )
+
+ to_sgid(**options).to_s
+ end
+end
diff --git a/app/views/mailers/identity_mailer/email_change_confirmation.html.erb b/app/views/mailers/identity_mailer/email_change_confirmation.html.erb
index f16c866ab..43782efb1 100644
--- a/app/views/mailers/identity_mailer/email_change_confirmation.html.erb
+++ b/app/views/mailers/identity_mailer/email_change_confirmation.html.erb
@@ -1,6 +1,6 @@
Confirm your email address change
-<%= link_to "Yes use use this email address", email_address_confirmation_url(user_id: @user.id, email_address_token: @token), class: "btn" %>
+<%= link_to "Yes use use this email address", user_email_address_confirmation_url(user_id: @user.id, email_address_token: @token), class: "btn" %>
If you didn’t request this change, you can ignore this email. Your email address WILL NOT be changed unless you hit the button.
diff --git a/app/views/mailers/identity_mailer/email_change_confirmation.text.erb b/app/views/mailers/identity_mailer/email_change_confirmation.text.erb
index b6ecd33c0..39988263d 100644
--- a/app/views/mailers/identity_mailer/email_change_confirmation.text.erb
+++ b/app/views/mailers/identity_mailer/email_change_confirmation.text.erb
@@ -3,6 +3,6 @@ Confirm your email address change
Hit the link below to use this email address in Fizzy:
-<%= email_address_confirmation_url(user_id: @user.id, email_address_token: @token) %>
+<%= user_email_address_confirmation_url(user_id: @user.id, email_address_token: @token) %>
If you didn’t request this change, you can ignore this email. Your email address WILL NOT be changed unless you hit the button.
diff --git a/app/views/users/email_addresses/confirmations/show.html.erb b/app/views/users/email_addresses/confirmations/show.html.erb
index 3410a9f74..ed6e24f72 100644
--- a/app/views/users/email_addresses/confirmations/show.html.erb
+++ b/app/views/users/email_addresses/confirmations/show.html.erb
@@ -8,7 +8,7 @@
Just a sec while we confirm your new email address.