Browse Source

Use Notify rather than Gmail for devise emails (#241)

* Use Notify rather than Gmail for devise emails

* Remove Devise mailer views

* Use real account template IDs
pull/246/head
baarkerlounger 3 years ago committed by GitHub
parent
commit
fb32267bc5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Gemfile
  2. 4
      Gemfile.lock
  3. 49
      app/mailers/devise_notify_mailer.rb
  4. 8
      app/views/devise/mailer/_password_change_forgotten.html.erb
  5. 6
      app/views/devise/mailer/_password_change_initial.html.erb
  6. 5
      app/views/devise/mailer/confirmation_instructions.html.erb
  7. 7
      app/views/devise/mailer/email_changed.html.erb
  8. 3
      app/views/devise/mailer/password_change.html.erb
  9. 5
      app/views/devise/mailer/reset_password_instructions.html.erb
  10. 7
      app/views/devise/mailer/unlock_instructions.html.erb
  11. 3
      config/initializers/devise.rb
  12. 21
      spec/features/organisation_spec.rb
  13. 29
      spec/features/user_spec.rb
  14. 2
      spec/request_helper.rb
  15. 19
      spec/requests/auth/passwords_controller_spec.rb
  16. 8
      spec/requests/users_controller_spec.rb

2
Gemfile

@ -19,6 +19,8 @@ gem "bootsnap", ">= 1.4.4", require: false
gem "govuk-components" gem "govuk-components"
# GOV UK component form builder DSL # GOV UK component form builder DSL
gem "govuk_design_system_formbuilder" gem "govuk_design_system_formbuilder"
# GOV UK Notify
gem "notifications-ruby-client"
# Turbo and Stimulus # Turbo and Stimulus
gem "hotwire-rails" gem "hotwire-rails"
# Soft delete ActiveRecords objects # Soft delete ActiveRecords objects

4
Gemfile.lock

@ -186,6 +186,7 @@ GEM
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
json-schema (2.8.1) json-schema (2.8.1)
addressable (>= 2.4) addressable (>= 2.4)
jwt (2.3.0)
kaminari (1.2.2) kaminari (1.2.2)
activesupport (>= 4.1.0) activesupport (>= 4.1.0)
kaminari-actionview (= 1.2.2) kaminari-actionview (= 1.2.2)
@ -234,6 +235,8 @@ GEM
racc (~> 1.4) racc (~> 1.4)
nokogiri (1.13.1-x86_64-linux) nokogiri (1.13.1-x86_64-linux)
racc (~> 1.4) racc (~> 1.4)
notifications-ruby-client (5.3.0)
jwt (>= 1.5, < 3)
orm_adapter (0.5.0) orm_adapter (0.5.0)
overcommit (0.58.0) overcommit (0.58.0)
childprocess (>= 0.6.3, < 5) childprocess (>= 0.6.3, < 5)
@ -435,6 +438,7 @@ DEPENDENCIES
hotwire-rails hotwire-rails
json-schema json-schema
listen (~> 3.3) listen (~> 3.3)
notifications-ruby-client
overcommit (>= 0.37.0) overcommit (>= 0.37.0)
pg (~> 1.1) pg (~> 1.1)
postcodes_io postcodes_io

49
app/mailers/devise_notify_mailer.rb

@ -0,0 +1,49 @@
class DeviseNotifyMailer < Devise::Mailer
require "notifications/client"
RESET_PASSWORD_TEMPLATE_ID = "2c410c19-80a7-481c-a531-2bcb3264f8e6".freeze
SET_PASSWORD_TEMPLATE_ID = "257460a6-6616-4640-a3f9-17c3d73d9e91".freeze
def notify_client
@notify_client ||= ::Notifications::Client.new(ENV["GOVUK_NOTIFY_API_KEY"])
end
def host
@host ||= ENV["APP_HOST"]
end
def send_email(email, template_id, personalisation)
notify_client.send_email(
email_address: email,
template_id: template_id,
personalisation: personalisation,
)
end
def reset_password_instructions(record, token, _opts = {})
template_id = record.last_sign_in_at ? RESET_PASSWORD_TEMPLATE_ID : SET_PASSWORD_TEMPLATE_ID
personalisation = {
name: record.name,
email: record.email,
organisation: record.organisation.name,
link: "https://#{host}/users/password/edit?reset_password_token=#{token}",
}
send_email(record.email, template_id, personalisation)
end
# def confirmation_instructions(record, token, _opts = {})
# super
# end
#
# def unlock_instructions(record, token, opts = {})
# super
# end
#
# def email_changed(record, opts = {})
# super
# end
#
# def password_change(record, opts = {})
# super
# end
end

8
app/views/devise/mailer/_password_change_forgotten.html.erb

@ -1,8 +0,0 @@
<p>Hello <%= @resource.email %>!</p>
<p>Someone has requested a link to change your password. You can do this through the link below.</p>
<p><%= govuk_link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>
<p>If you didn't request this, please ignore this email.</p>
<p>Your password won't change until you access the link above and create a new one.</p>

6
app/views/devise/mailer/_password_change_initial.html.erb

@ -1,6 +0,0 @@
<p>Hello <%= @resource.name %>!</p>
<p>An account has been created for you to submit CORE data on behalf of <%= @resource.organisation.name %>.</p>
<p>Your username is <%= @resource.email %>, use the link below to set your password.
<p><%= govuk_link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %></p>

5
app/views/devise/mailer/confirmation_instructions.html.erb

@ -1,5 +0,0 @@
<p>Welcome <%= @email %>!</p>
<p>You can confirm your account email through the link below:</p>
<p><%= govuk_link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %></p>

7
app/views/devise/mailer/email_changed.html.erb

@ -1,7 +0,0 @@
<p>Hello <%= @email %>!</p>
<% if @resource.try(:unconfirmed_email?) %>
<p>We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.</p>
<% else %>
<p>We're contacting you to notify you that your email has been changed to <%= @resource.email %>.</p>
<% end %>

3
app/views/devise/mailer/password_change.html.erb

@ -1,3 +0,0 @@
<p>Hello <%= @resource.email %>!</p>
<p>We're contacting you to notify you that your password has been changed.</p>

5
app/views/devise/mailer/reset_password_instructions.html.erb

@ -1,5 +0,0 @@
<% if @resource.last_sign_in_at.nil? %>
<%= render partial: "password_change_initial" %>
<% else %>
<%= render partial: "password_change_forgotten" %>
<% end %>

7
app/views/devise/mailer/unlock_instructions.html.erb

@ -1,7 +0,0 @@
<p>Hello <%= @resource.email %>!</p>
<p>Your account has been locked due to an excessive number of unsuccessful sign in attempts.</p>
<p>Click the link below to unlock your account:</p>
<p><%= govuk_link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %></p>

3
config/initializers/devise.rb

@ -24,10 +24,11 @@ Devise.setup do |config|
# Configure the e-mail address which will be shown in Devise::Mailer, # Configure the e-mail address which will be shown in Devise::Mailer,
# note that it will be overwritten if you use your own mailer class # note that it will be overwritten if you use your own mailer class
# with default "from" parameter. # with default "from" parameter.
config.mailer_sender = ENV["CORE_EMAIL_USERNAME"] # config.mailer_sender = ENV["CORE_EMAIL_USERNAME"]
# Configure the class responsible to send e-mails. # Configure the class responsible to send e-mails.
# config.mailer = 'Devise::Mailer' # config.mailer = 'Devise::Mailer'
config.mailer = "DeviseNotifyMailer"
# Configure the parent class responsible to send e-mails. # Configure the parent class responsible to send e-mails.
# config.parent_mailer = 'ActionMailer::Base' # config.parent_mailer = 'ActionMailer::Base'

21
spec/features/organisation_spec.rb

@ -5,8 +5,15 @@ RSpec.describe "User Features" do
include Helpers include Helpers
let(:organisation) { user.organisation } let(:organisation) { user.organisation }
let(:org_id) { organisation.id } let(:org_id) { organisation.id }
let(:set_password_template_id) { DeviseNotifyMailer::SET_PASSWORD_TEMPLATE_ID }
let(:notify_client) { double(Notifications::Client) }
let(:reset_password_token) { "MCDH5y6Km-U7CFPgAMVS" }
before do before do
allow_any_instance_of(DeviseNotifyMailer).to receive(:notify_client).and_return(notify_client)
allow_any_instance_of(DeviseNotifyMailer).to receive(:host).and_return("test.com")
allow_any_instance_of(User).to receive(:set_reset_password_token).and_return(reset_password_token)
allow(notify_client).to receive(:send_email).and_return(true)
sign_in user sign_in user
end end
@ -39,7 +46,19 @@ RSpec.describe "User Features" do
fill_in("user[name]", with: "New User") fill_in("user[name]", with: "New User")
fill_in("user[email]", with: "new_user@example.com") fill_in("user[email]", with: "new_user@example.com")
choose("user-role-data-provider-field") choose("user-role-data-provider-field")
expect { click_button("Continue") }.to change { ActionMailer::Base.deliveries.count }.by(1) expect(notify_client).to receive(:send_email).with(
{
email_address: "new_user@example.com",
template_id: set_password_template_id,
personalisation: {
name: "New User",
email: "new_user@example.com",
organisation: organisation.name,
link: "https://test.com/users/password/edit?reset_password_token=#{reset_password_token}",
},
},
)
click_button("Continue")
expect(page).to have_current_path("/organisations/#{org_id}/users") expect(page).to have_current_path("/organisations/#{org_id}/users")
expect(User.last.role).to eq("data_provider") expect(User.last.role).to eq("data_provider")
end end

29
spec/features/user_spec.rb

@ -1,6 +1,17 @@
require "rails_helper" require "rails_helper"
RSpec.describe "User Features" do RSpec.describe "User Features" do
let!(:user) { FactoryBot.create(:user) } let!(:user) { FactoryBot.create(:user, last_sign_in_at: Time.zone.now) }
let(:reset_password_template_id) { DeviseNotifyMailer::RESET_PASSWORD_TEMPLATE_ID }
let(:notify_client) { double(Notifications::Client) }
let(:reset_password_token) { "MCDH5y6Km-U7CFPgAMVS" }
before do
allow_any_instance_of(DeviseNotifyMailer).to receive(:notify_client).and_return(notify_client)
allow_any_instance_of(DeviseNotifyMailer).to receive(:host).and_return("test.com")
allow(notify_client).to receive(:send_email).and_return(true)
allow_any_instance_of(User).to receive(:set_reset_password_token).and_return(reset_password_token)
end
context "A user navigating to case logs" do context "A user navigating to case logs" do
it " is required to log in" do it " is required to log in" do
visit("/logs") visit("/logs")
@ -66,10 +77,22 @@ RSpec.describe "User Features" do
expect(page).to have_current_path("/confirmations/reset?email=idontexist%40example.com") expect(page).to have_current_path("/confirmations/reset?email=idontexist%40example.com")
end end
it " is sent a reset password email" do it " is sent a reset password email via Notify" do
expect(notify_client).to receive(:send_email).with(
{
email_address: user.email,
template_id: reset_password_template_id,
personalisation: {
name: user.name,
email: user.email,
organisation: user.organisation.name,
link: "https://test.com/users/password/edit?reset_password_token=#{reset_password_token}",
},
},
)
visit("/users/password/new") visit("/users/password/new")
fill_in("user[email]", with: user.email) fill_in("user[email]", with: user.email)
expect { click_button("Send email") }.to change { ActionMailer::Base.deliveries.count }.by(1) click_button("Send email")
end end
end end

2
spec/request_helper.rb

@ -5,5 +5,7 @@ module RequestHelper
WebMock.disable_net_connect!(allow_localhost: true) WebMock.disable_net_connect!(allow_localhost: true)
WebMock.stub_request(:get, /api.postcodes.io/) WebMock.stub_request(:get, /api.postcodes.io/)
.to_return(status: 200, body: "{\"status\":404,\"error\":\"Postcode not found\"}", headers: {}) .to_return(status: 200, body: "{\"status\":404,\"error\":\"Postcode not found\"}", headers: {})
WebMock.stub_request(:post, /api.notifications.service.gov.uk\/v2\/notifications\/email/)
.to_return(status: 200, body: "", headers: {})
end end
end end

19
spec/requests/auth/passwords_controller_spec.rb

@ -4,6 +4,12 @@ require_relative "../../support/devise"
RSpec.describe Auth::PasswordsController, type: :request do RSpec.describe Auth::PasswordsController, type: :request do
let(:params) { { user: { email: email } } } let(:params) { { user: { email: email } } }
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:notify_client) { double(Notifications::Client) }
before do
allow_any_instance_of(DeviseNotifyMailer).to receive(:notify_client).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true)
end
context "when a password reset is requested for a valid email" do context "when a password reset is requested for a valid email" do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
@ -32,19 +38,6 @@ RSpec.describe Auth::PasswordsController, type: :request do
end end
end end
context "when a password reset is requested the email" do
let(:user) { FactoryBot.create(:user, last_sign_in_at: Time.zone.now) }
let(:email) { user.email }
it "should contain the correct email" do
post "/users/password", params: params
follow_redirect!
email_ascii_content = ActionMailer::Base.deliveries.last.body.raw_source
email_content = email_ascii_content.encode("ASCII", "UTF-8", undef: :replace)
expect(email_content).to match(email)
end
end
context "#Update - reset password" do context "#Update - reset password" do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:token) { user.send(:set_reset_password_token) } let(:token) { user.send(:set_reset_password_token) }

8
spec/requests/user_controller_spec.rb → spec/requests/users_controller_spec.rb

@ -1,12 +1,18 @@
require "rails_helper" require "rails_helper"
RSpec.describe "password_reset", type: :request do RSpec.describe UsersController, type: :request do
let(:user) { FactoryBot.create(:user) } let(:user) { FactoryBot.create(:user) }
let(:unauthorised_user) { FactoryBot.create(:user) } let(:unauthorised_user) { FactoryBot.create(:user) }
let(:headers) { { "Accept" => "text/html" } } let(:headers) { { "Accept" => "text/html" } }
let(:page) { Capybara::Node::Simple.new(response.body) } let(:page) { Capybara::Node::Simple.new(response.body) }
let(:new_value) { "new test name" } let(:new_value) { "new test name" }
let(:params) { { id: user.id, user: { name: new_value } } } let(:params) { { id: user.id, user: { name: new_value } } }
let(:notify_client) { double(Notifications::Client) }
before do
allow_any_instance_of(DeviseNotifyMailer).to receive(:notify_client).and_return(notify_client)
allow(notify_client).to receive(:send_email).and_return(true)
end
context "a not signed in user" do context "a not signed in user" do
describe "#show" do describe "#show" do
Loading…
Cancel
Save