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] %>