Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions app/mailers/devise_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ def confirmation_instructions(record, token, opts = {})
@record = record
@token = token
@user = record
@reconfirmation = record.pending_reconfirmation?

opts[:subject] = "AWBW Portal: Welcome instructions for #{record.full_name}"
@mail = super
opts[:subject] = if @reconfirmation
"AWBW Portal: Confirm your new email address"
else
"AWBW Portal: Welcome instructions for #{record.full_name}"
end
@mail = super
end

def unlock_instructions(record, token, opts = {})
Expand Down
10 changes: 10 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ def gallery_assets # method needed for idea_submitted_fyi mailer
[]
end

# Override Devise to always send confirmation to the pending email when present.
# Devise's default checks `pending_reconfirmation?` but can still route to the
# current email in some flows. This ensures the confirmation always targets the
# unconfirmed (new) email address.
def send_confirmation_instructions
generate_confirmation_token! unless @raw_confirmation_token
target = unconfirmed_email.presence || email
send_devise_notification(:confirmation_instructions, @raw_confirmation_token, to: target)
end

def set_welcome_instructions_token!
loop do
self.welcome_instructions_token = Devise.friendly_token
Expand Down
157 changes: 108 additions & 49 deletions app/views/devise/mailer/confirmation_instructions.html.erb
Original file line number Diff line number Diff line change
@@ -1,50 +1,109 @@
<h1>Welcome to the AWBW Portal!</h1>

<div style="text-align: left;">
<p>
Hello <strong><%= @user.first_name_or_email %></strong>,
</p>

<p>
We invite you to join our Portal. Set your password below to log in and unlock our full library of curriculum and resources. Until you're logged in, only our public content will be visible.
</p>
</div>

<div style="
background-color:#f3f4f6;
border-radius:6px;
padding:12px;
margin:16px 0;
">
<p>
To get started, click the button below to set your password:
</p>

<p>
<%= link_to "Set your password",
confirm_url(confirmation_token: @token),
target: "_blank",
style: "display:inline-block;
padding:10px 16px;
background:#2563eb;
color:#ffffff;
text-decoration:none;
border-radius:6px;
font-weight:bold;" %>
</p>
</div>

<div style="text-align: left;">
<p>
This invitation link will expire in 30 days and can only be used once.
</p>

<p>
If you have any questions, please visit our
<%= link_to "Contact us", contact_us_url %> page to connect with our staff.
</p>
</div>

<% content_for :fallback_url do %>
<%= confirm_url(confirmation_token: @token) %>
<% if @reconfirmation %>
<h1>Confirm your new email</h1>

<div style="text-align: left;">
<p>
Hello <strong><%= @user.first_name_or_email %></strong>,
</p>

<p>
We received a request to change your AWBW Portal email address
to <strong><%= @user.unconfirmed_email %></strong>.
</p>
</div>

<div style="
background-color:#f3f4f6;
border-radius:6px;
padding:12px;
margin:16px 0;
">
<p>
Click the button below to confirm this change:
</p>

<p>
<%= link_to "Confirm new email",
confirm_url(confirmation_token: @token),
target: "_blank",
style: "display:inline-block;
padding:10px 16px;
background:#2563eb;
color:#ffffff;
text-decoration:none;
border-radius:6px;
font-weight:bold;" %>
</p>
</div>

<div style="text-align: left;">
<p>
If you did not request this change, you can safely ignore this email.
Your email address will remain unchanged.
</p>

<p>
This link will expire in <%= distance_of_time_in_words(Devise.confirm_within) %>.
</p>

<p>
If you have any questions, please visit our
<%= link_to "Contact us", contact_us_url %> page to connect with our staff.
</p>
</div>

<% content_for :fallback_url do %>
<%= confirm_url(confirmation_token: @token) %>
<% end %>
<% else %>
<h1>Welcome to the AWBW Portal!</h1>

<div style="text-align: left;">
<p>
Hello <strong><%= @user.first_name_or_email %></strong>,
</p>

<p>
We invite you to join our Portal. Set your password below to log in and unlock our full library of curriculum and resources. Until you're logged in, only our public content will be visible.
</p>
</div>

<div style="
background-color:#f3f4f6;
border-radius:6px;
padding:12px;
margin:16px 0;
">
<p>
To get started, click the button below to set your password:
</p>

<p>
<%= link_to "Set your password",
confirm_url(confirmation_token: @token),
target: "_blank",
style: "display:inline-block;
padding:10px 16px;
background:#2563eb;
color:#ffffff;
text-decoration:none;
border-radius:6px;
font-weight:bold;" %>
</p>
</div>

<div style="text-align: left;">
<p>
This invitation link will expire in 30 days and can only be used once.
</p>

<p>
If you have any questions, please visit our
<%= link_to "Contact us", contact_us_url %> page to connect with our staff.
</p>
</div>

<% content_for :fallback_url do %>
<%= confirm_url(confirmation_token: @token) %>
<% end %>
<% end %>
19 changes: 19 additions & 0 deletions app/views/devise/mailer/confirmation_instructions.text.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
<% if @reconfirmation %>
Confirm your new email
======================

Hello <%= @user.first_name_or_email %>,

We received a request to change your AWBW Portal email address to <%= @user.unconfirmed_email %>.

Confirm this change:
<%= confirm_url(confirmation_token: @token) %>

If you did not request this change, you can safely ignore this email. Your email address will remain unchanged.

This link will expire in <%= distance_of_time_in_words(Devise.confirm_within) %>.

If you have any questions, please visit our Contact us page to connect with our staff:
<%= contact_us_url %>
<% else %>
Welcome to the AWBW Portal!
==========================

Expand All @@ -12,3 +30,4 @@ This invitation link will expire in 30 days and can only be used once.

If you have any questions regarding this invitation, please visit our Contact us page to connect with our staff:
<%= contact_us_url %>
<% end %>
44 changes: 44 additions & 0 deletions spec/mailers/devise_mailer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,50 @@
# so user_unlock_url route doesn't exist and the view can't render
end

describe "#confirmation_instructions" do
context "when sending for initial signup (no pending reconfirmation)" do
it "uses the welcome subject line" do
mail = described_class.confirmation_instructions(user, token)

expect(mail.subject).to eq("AWBW Portal: Welcome instructions for #{user.full_name}")
end

it "includes welcome copy and password setup CTA" do
mail = described_class.confirmation_instructions(user, token)

expect(mail.body.encoded).to include("Welcome to the AWBW Portal!")
expect(mail.body.encoded).to include("Set your password")
end
end

context "when sending for email change (reconfirmation)" do
before do
user.skip_confirmation_notification!
user.update!(email: "new@example.com")
end

it "uses the email change subject line" do
mail = described_class.confirmation_instructions(user, token)

expect(mail.subject).to eq("AWBW Portal: Confirm your new email address")
end

it "includes email change copy instead of welcome copy" do
mail = described_class.confirmation_instructions(user, token)

expect(mail.body.encoded).to include("Confirm your new email")
expect(mail.body.encoded).not_to include("Welcome to the AWBW Portal!")
expect(mail.body.encoded).not_to include("Set your password")
end

it "shows the new email address in the body" do
mail = described_class.confirmation_instructions(user, token)

expect(mail.body.encoded).to include(user.unconfirmed_email)
end
end
end

describe "#create_notification_record" do
let(:notification) { build(:notification) }

Expand Down
44 changes: 44 additions & 0 deletions spec/models/user_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -370,4 +370,48 @@
user.save!
end
end

describe "#send_confirmation_instructions" do
let(:mock_mail) { double(deliver_later: true, deliver: true) }

context "when there is no pending email change" do
let(:user) { create(:user, confirmed_at: nil) }

before do
user # force creation before stubbing so on-create confirmation isn't counted
allow(DeviseMailer).to receive(:confirmation_instructions).and_return(mock_mail)
end

it "sends to the current email" do
user.send_confirmation_instructions

expect(DeviseMailer).to have_received(:confirmation_instructions)
.with(user, anything, hash_including(to: user.email))
end
end

context "when there is a pending email change" do
let(:user) { create(:user) }
let(:new_email) { "pending@example.com" }

before do
user.update_columns(unconfirmed_email: new_email)
allow(DeviseMailer).to receive(:confirmation_instructions).and_return(mock_mail)
end

it "sends to the pending email" do
user.send_confirmation_instructions

expect(DeviseMailer).to have_received(:confirmation_instructions)
.with(user, anything, hash_including(to: new_email))
end

it "does not send to the current email" do
user.send_confirmation_instructions

expect(DeviseMailer).not_to have_received(:confirmation_instructions)
.with(user, anything, hash_including(to: user.email))
end
end
end
end
13 changes: 9 additions & 4 deletions spec/services/user_services/process_email_change_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,24 @@
end

context "with send_confirmation true" do
it "sends confirmation instructions" do
expect(user).to receive(:send_confirmation_instructions)
let(:mock_mail) { double(deliver_later: true, deliver: true) }

before do
allow(DeviseMailer).to receive(:confirmation_instructions).and_return(mock_mail)
end

it "sends confirmation to the pending email" do
described_class.call(
user: user,
send_confirmation: true,
current_user: admin
)

expect(DeviseMailer).to have_received(:confirmation_instructions)
.with(user, anything, hash_including(to: "new@example.com"))
end

it "includes confirmation message in summary" do
allow(user).to receive(:send_confirmation_instructions)

result = described_class.call(
user: user,
send_confirmation: true,
Expand Down
Loading