Reinstate email changes
This commit is contained in:
committed by
Mike Dalessio
parent
4d8b3ed719
commit
56c41d45eb
@@ -12,7 +12,7 @@ class Users::EmailAddresses::ConfirmationsController < ApplicationController
|
|||||||
user = User.change_email_address_using_token(token)
|
user = User.change_email_address_using_token(token)
|
||||||
|
|
||||||
terminate_session if Current.session
|
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)
|
redirect_to edit_user_url(script_name: user.account.slug, id: user)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
class User < ApplicationRecord
|
class User < ApplicationRecord
|
||||||
include Accessor, Assignee, Attachable, Configurable,
|
include Accessor, Assignee, Attachable, Configurable, EmailAddressChangeable,
|
||||||
Mentionable, Named, Notifiable, Role, Searcher, Watcher
|
Mentionable, Named, Notifiable, Role, Searcher, Watcher
|
||||||
include Timelined # Depends on Accessor
|
include Timelined # Depends on Accessor
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<p class="subtitle">Confirm your email address change</p>
|
<p class="subtitle">Confirm your email address change</p>
|
||||||
|
|
||||||
<%= 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" %>
|
||||||
|
|
||||||
<p class="margin-block-start-double">If you didn’t request this change, you can ignore this email. Your email address WILL NOT be changed unless you hit the button.</p>
|
<p class="margin-block-start-double">If you didn’t request this change, you can ignore this email. Your email address WILL NOT be changed unless you hit the button.</p>
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ Confirm your email address change
|
|||||||
|
|
||||||
Hit the link below to use this email address in Fizzy:
|
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.
|
If you didn’t request this change, you can ignore this email. Your email address WILL NOT be changed unless you hit the button.
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<p class="margin-none">Just a sec while we confirm your new email address.</p>
|
<p class="margin-none">Just a sec while we confirm your new email address.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<%= 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] %>
|
<%= form.hidden_field :email_address_token, value: params[:email_address_token] %>
|
||||||
|
|
||||||
<button type="submit" class="btn btn--link center" data-form-target="submit">
|
<button type="submit" class="btn btn--link center" data-form-target="submit">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ class Sessions::MenusControllerTest < ActionDispatch::IntegrationTest
|
|||||||
@identity = identities(:kevin)
|
@identity = identities(:kevin)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "show with no memberships" do
|
test "show with no account" do
|
||||||
sign_in_as @identity
|
sign_in_as @identity
|
||||||
@identity.users.delete_all
|
@identity.users.delete_all
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ class Sessions::MenusControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_response :success, "Renders an empty menu"
|
assert_response :success, "Renders an empty menu"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "show with exactly one membership" do
|
test "show with exactly one account" do
|
||||||
sign_in_as @identity
|
sign_in_as @identity
|
||||||
@identity.users.delete_all
|
@identity.users.delete_all
|
||||||
account = Account.create!(external_account_id: 9999991, name: "Test Account")
|
account = Account.create!(external_account_id: 9999991, name: "Test Account")
|
||||||
@@ -30,7 +30,7 @@ class Sessions::MenusControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_redirected_to root_url(script_name: "/9999991")
|
assert_redirected_to root_url(script_name: "/9999991")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "show with multiple memeberships" do
|
test "show with multiple accounts" do
|
||||||
sign_in_as @identity
|
sign_in_as @identity
|
||||||
@identity.users.delete_all
|
@identity.users.delete_all
|
||||||
account1 = Account.create!(external_account_id: 9999992, name: "37signals")
|
account1 = Account.create!(external_account_id: 9999992, name: "37signals")
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class Users::EmailAddresses::ConfirmationsControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
setup do
|
||||||
|
@user = users(:david)
|
||||||
|
@new_email = "newemail@example.com"
|
||||||
|
@token = @user.send(:generate_email_address_change_token, to: @new_email)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "show" do
|
||||||
|
untenanted do
|
||||||
|
get user_email_address_confirmation_path(user_id: @user.id, email_address_token: @token)
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create" do
|
||||||
|
old_email = @user.identity.email_address
|
||||||
|
|
||||||
|
untenanted do
|
||||||
|
post user_email_address_confirmation_path(user_id: @user.id, email_address_token: @token)
|
||||||
|
|
||||||
|
assert_equal @new_email, @user.reload.identity.email_address
|
||||||
|
assert_redirected_to edit_user_url(script_name: @user.account.slug, id: @user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create with invalid token" do
|
||||||
|
untenanted do
|
||||||
|
assert_raises(ArgumentError) do
|
||||||
|
post user_email_address_confirmation_path(user_id: @user.id, email_address_token: "invalid")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class Users::EmailAddressesControllerTest < ActionDispatch::IntegrationTest
|
||||||
|
include ActionMailer::TestHelper
|
||||||
|
|
||||||
|
setup do
|
||||||
|
sign_in_as :david
|
||||||
|
@user = users(:david)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "new" do
|
||||||
|
untenanted do
|
||||||
|
get new_user_email_address_path(@user)
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create" do
|
||||||
|
untenanted do
|
||||||
|
assert_emails 1 do
|
||||||
|
post user_email_addresses_path(@user), params: { email_address: "newemail@example.com" }
|
||||||
|
end
|
||||||
|
assert_response :success
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create with existing email in same account" do
|
||||||
|
existing_user = users(:kevin)
|
||||||
|
existing_email = existing_user.identity.email_address
|
||||||
|
|
||||||
|
untenanted do
|
||||||
|
post user_email_addresses_path(@user), params: { email_address: existing_email }
|
||||||
|
assert_redirected_to new_user_email_address_path(@user)
|
||||||
|
assert_equal "You already have a user in this account with that email address", flash[:alert]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create for other user" do
|
||||||
|
other_user = users(:kevin)
|
||||||
|
|
||||||
|
untenanted do
|
||||||
|
assert_no_emails do
|
||||||
|
post user_email_addresses_path(other_user), params: { email_address: "newemail@example.com" }
|
||||||
|
end
|
||||||
|
assert_response :not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -18,13 +18,13 @@ class ApplicationHelperTest < ActionView::TestCase
|
|||||||
assert_select parse(page_title_tag), "title", text: "Holodeck | Fizzy"
|
assert_select parse(page_title_tag), "title", text: "Holodeck | Fizzy"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "page_title_tag on tenanted page when user has a single membership" do
|
test "page_title_tag on tenanted page when user has a single account" do
|
||||||
Current.session = sessions(:david)
|
Current.session = sessions(:david)
|
||||||
|
|
||||||
assert_select parse(page_title_tag), "title", text: "Fizzy"
|
assert_select parse(page_title_tag), "title", text: "Fizzy"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "page_title_tag on tenanted page when user has multiple memberships" do
|
test "page_title_tag on tenanted page when user has multiple accounts" do
|
||||||
Current.session = sessions(:david)
|
Current.session = sessions(:david)
|
||||||
other_account = Account.create!(external_account_id: "dangling-tenant", name: "Other Account")
|
other_account = Account.create!(external_account_id: "dangling-tenant", name: "Other Account")
|
||||||
identities(:david).users.create!(account: other_account, name: "David")
|
identities(:david).users.create!(account: other_account, name: "David")
|
||||||
@@ -32,14 +32,14 @@ class ApplicationHelperTest < ActionView::TestCase
|
|||||||
assert_select parse(page_title_tag), "title", text: "37signals | Fizzy"
|
assert_select parse(page_title_tag), "title", text: "37signals | Fizzy"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "page_title_tag on tenanted page with a page title when user has a single membership" do
|
test "page_title_tag on tenanted page with a page title when user has a single account" do
|
||||||
Current.session = sessions(:david)
|
Current.session = sessions(:david)
|
||||||
@page_title = "Holodeck"
|
@page_title = "Holodeck"
|
||||||
|
|
||||||
assert_select parse(page_title_tag), "title", text: "Holodeck | Fizzy"
|
assert_select parse(page_title_tag), "title", text: "Holodeck | Fizzy"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "page_title_tag on tenanted page with a page title when user has multiple memberships" do
|
test "page_title_tag on tenanted page with a page title when user has multiple account" do
|
||||||
Current.session = sessions(:david)
|
Current.session = sessions(:david)
|
||||||
other_account = Account.create!(external_account_id: "dangling-tenant", name: "Other Account")
|
other_account = Account.create!(external_account_id: "dangling-tenant", name: "Other Account")
|
||||||
identities(:david).users.create!(account: other_account, name: "David")
|
identities(:david).users.create!(account: other_account, name: "David")
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class IdentityMailerPreview < ActionMailer::Preview
|
||||||
|
def email_change_confirmation
|
||||||
|
new_email_address = "new.email@example.com"
|
||||||
|
user = User.all.sample
|
||||||
|
token = user.send(:generate_email_address_change_token, to: new_email_address)
|
||||||
|
|
||||||
|
IdentityMailer.email_change_confirmation(
|
||||||
|
email_address: new_email_address,
|
||||||
|
token: token,
|
||||||
|
user: user
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -19,7 +19,7 @@ class IdentityTest < ActiveSupport::TestCase
|
|||||||
assert_not Identity.new(email_address: "test@example.com").staff?
|
assert_not Identity.new(email_address: "test@example.com").staff?
|
||||||
end
|
end
|
||||||
|
|
||||||
test "join creates membership and user for account" do
|
test "join" do
|
||||||
identity = identities(:david)
|
identity = identities(:david)
|
||||||
account = accounts(:initech)
|
account = accounts(:initech)
|
||||||
|
|
||||||
@@ -27,7 +27,8 @@ class IdentityTest < ActiveSupport::TestCase
|
|||||||
identity.join(account)
|
identity.join(account)
|
||||||
end
|
end
|
||||||
|
|
||||||
user = account.users.find_by(identity: identity)
|
user = account.users.find_by!(identity: identity)
|
||||||
|
|
||||||
assert_not_nil user
|
assert_not_nil user
|
||||||
assert_equal identity, user.identity
|
assert_equal identity, user.identity
|
||||||
assert_equal identity.email_address, user.name
|
assert_equal identity.email_address, user.name
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
require "test_helper"
|
||||||
|
|
||||||
|
class User::EmailAddressChangeableTest < ActiveSupport::TestCase
|
||||||
|
include ActionMailer::TestHelper
|
||||||
|
|
||||||
|
setup do
|
||||||
|
@identity = identities(:kevin)
|
||||||
|
@user = @identity.users.find_by!(account: accounts("37s"))
|
||||||
|
@new_email = "newart@example.com"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "send_email_address_change_confirmation" do
|
||||||
|
assert_emails 1 do
|
||||||
|
@user.send_email_address_change_confirmation(@new_email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_email_address" do
|
||||||
|
old_identity = @identity
|
||||||
|
|
||||||
|
assert_difference -> { Identity.count }, +1 do
|
||||||
|
@user.change_email_address(@new_email)
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_equal @new_email, @user.reload.identity.email_address
|
||||||
|
assert_not old_identity.reload.users.exists?(id: @user.id)
|
||||||
|
assert_equal @new_email, @user.reload.identity.email_address
|
||||||
|
|
||||||
|
assert_no_difference -> { Identity.count } do
|
||||||
|
@user.change_email_address(identities(:david).email_address)
|
||||||
|
end
|
||||||
|
assert_equal identities(:david).email_address, @user.reload.identity.email_address
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_email_address_using_token" do
|
||||||
|
token = @user.send(:generate_email_address_change_token, to: @new_email)
|
||||||
|
|
||||||
|
User.change_email_address_using_token(token)
|
||||||
|
|
||||||
|
assert_equal @new_email, @user.reload.identity.email_address
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_email_address_using_token with invalid token" do
|
||||||
|
assert_raises(ArgumentError, match: /invalid/) do
|
||||||
|
User.change_email_address_using_token("invalid_token")
|
||||||
|
end
|
||||||
|
|
||||||
|
token = @user.send(:generate_email_address_change_token, to: @new_email)
|
||||||
|
@identity.update!(email_address: "different@example.com")
|
||||||
|
|
||||||
|
assert_raises(ArgumentError, match: /different email address/) do
|
||||||
|
User.change_email_address_using_token(token)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user