From 56c41d45eb0efcb51e25f7c4adbeac8fe794f32f Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Fri, 14 Nov 2025 08:33:00 +0100 Subject: [PATCH] Reinstate email changes --- .../confirmations_controller.rb | 2 +- app/mailers/identity_mailer.rb | 7 +++ app/models/user.rb | 2 +- app/models/user/email_address_changeable.rb | 57 +++++++++++++++++++ .../email_change_confirmation.html.erb | 2 +- .../email_change_confirmation.text.erb | 2 +- .../confirmations/show.html.erb | 2 +- .../sessions/menus_controller_test.rb | 6 +- .../confirmations_controller_test.rb | 35 ++++++++++++ .../users/email_addresses_controller_test.rb | 48 ++++++++++++++++ test/helpers/application_helper_test.rb | 8 +-- .../previews/identity_mailer_preview.rb | 13 +++++ test/models/identity_test.rb | 5 +- .../user/email_address_changeable_test.rb | 55 ++++++++++++++++++ 14 files changed, 230 insertions(+), 14 deletions(-) create mode 100644 app/mailers/identity_mailer.rb create mode 100644 app/models/user/email_address_changeable.rb create mode 100644 test/controllers/users/email_addresses/confirmations_controller_test.rb create mode 100644 test/controllers/users/email_addresses_controller_test.rb create mode 100644 test/mailers/previews/identity_mailer_preview.rb create mode 100644 test/models/user/email_address_changeable_test.rb 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.

- <%= form_with url: email_address_confirmation_path(user_id: @user.id), method: :post, data: { controller: "form auto-submit" } do |form| %> + <%= form_with url: user_email_address_confirmation_path(user_id: @user.id), method: :post, data: { controller: "form auto-submit" } do |form| %> <%= form.hidden_field :email_address_token, value: params[:email_address_token] %>