Browse Source
* Confirmable * Remove obsolete rake task * Skip confirmation for inactive users * Send beta onboarding template if migrated from Softwire * Default controller * Use correct link * Redirect confirmation to set password * Confirm account within 3 days * Only redirect to set password if not previously set * Rubocop * Confirm factory bot users * Set password condition * Changing email requires reconfirming * No need to explicitly trigger email, devise does that for us now * Remove flash banner * Mock notify * Mock in the right spec * Test redirect and text * User is confirmable * Rubocop * Redirect to url so we don't bypass authenticity token * Update content * Add test for resend invite flow * Update link to resend confirmation email * Rename password reset resend confirmation partial * Expired link error page * Remove resend confirmation link * Update seed * Expory contact * Time zone Co-authored-by: Paul Robert Lloyd <me+git@paulrobertlloyd.com>pull/619/head
27 changed files with 214 additions and 103 deletions
@ -0,0 +1,20 @@ |
|||||||
|
class Auth::ConfirmationsController < Devise::ConfirmationsController |
||||||
|
# GET /resource/confirmation?confirmation_token=abcdef |
||||||
|
def show |
||||||
|
self.resource = resource_class.confirm_by_token(params[:confirmation_token]) |
||||||
|
yield resource if block_given? |
||||||
|
|
||||||
|
if resource.errors.empty? |
||||||
|
if resource.sign_in_count.zero? |
||||||
|
token = resource.send(:set_reset_password_token) |
||||||
|
redirect_to "#{edit_user_password_url}?reset_password_token=#{token}&confirmation=true" |
||||||
|
else |
||||||
|
respond_with_navigational(resource) { redirect_to after_confirmation_path_for(resource_name, resource) } |
||||||
|
end |
||||||
|
elsif resource.errors.map(&:type).include?(:confirmation_period_expired) |
||||||
|
render "devise/confirmations/expired" |
||||||
|
else |
||||||
|
respond_with_navigational(resource.errors, status: :unprocessable_entity) { render :new } |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,11 @@ |
|||||||
|
<% content_for :title, "Your invitation link has expired" %> |
||||||
|
|
||||||
|
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
<h1 class="govuk-heading-l"> |
||||||
|
<%= content_for(:title) %> |
||||||
|
</h1> |
||||||
|
|
||||||
|
<p class="govuk-body">Contact the helpdesk to request a new one.</p> |
||||||
|
</div> |
||||||
|
</div> |
@ -1,15 +1,32 @@ |
|||||||
<h2>Resend confirmation instructions</h2> |
<% content_for :title, "Resend invitation link" %> |
||||||
|
|
||||||
|
<% content_for :before_content do %> |
||||||
|
<%= govuk_back_link( |
||||||
|
text: "Back", |
||||||
|
href: :back, |
||||||
|
) %> |
||||||
|
<% end %> |
||||||
|
|
||||||
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> |
<%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> |
||||||
<%= render "devise/shared/error_messages", resource: resource %> |
<div class="govuk-grid-row"> |
||||||
|
<div class="govuk-grid-column-two-thirds"> |
||||||
|
<%= f.govuk_error_summary %> |
||||||
|
|
||||||
|
<h1 class="govuk-heading-l"> |
||||||
|
<%= content_for(:title) %> |
||||||
|
</h1> |
||||||
|
|
||||||
|
<p class="govuk-body">Enter your email address to get a new invitation link.</p> |
||||||
|
|
||||||
<%= f.govuk_email_field :email, |
<%= f.govuk_email_field :email, |
||||||
label: { text: "Email address" }, |
label: { text: "Email address" }, |
||||||
autocomplete: "email", |
autocomplete: "email", |
||||||
spellcheck: "false", |
spellcheck: "false", |
||||||
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> |
value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> |
||||||
|
|
||||||
<%= f.govuk_submit "Resend confirmation instructions" %> |
<%= f.govuk_submit "Send email" %> |
||||||
|
</div> |
||||||
|
</div> |
||||||
<% end %> |
<% end %> |
||||||
|
|
||||||
<%= render "devise/shared/links" %> |
<%= render "devise/shared/links" %> |
||||||
|
@ -0,0 +1,11 @@ |
|||||||
|
class AddConfirmableUsers < ActiveRecord::Migration[7.0] |
||||||
|
def change |
||||||
|
change_table :users, bulk: true do |t| |
||||||
|
t.column :confirmation_token, :string |
||||||
|
t.column :confirmed_at, :datetime |
||||||
|
t.column :confirmation_sent_at, :datetime |
||||||
|
t.string :unconfirmed_email |
||||||
|
end |
||||||
|
add_index :users, :confirmation_token, unique: true |
||||||
|
end |
||||||
|
end |
@ -1,22 +0,0 @@ |
|||||||
namespace :onboarding_emails do |
|
||||||
desc "Send onboarding emails to private beta users" |
|
||||||
task :send, %i[organisation_id] => :environment do |_task, args| |
|
||||||
organisation_id = args[:organisation_id] |
|
||||||
host = ENV["APP_HOST"] |
|
||||||
raise "Organisation id must be provided" unless organisation_id |
|
||||||
raise "Host is not set" unless host |
|
||||||
|
|
||||||
organisation = Organisation.find(organisation_id) |
|
||||||
raise "Organisation #{organisation_id} does not exist" unless organisation |
|
||||||
|
|
||||||
organisation.users.each do |user| |
|
||||||
next unless URI::MailTo::EMAIL_REGEXP.match?(user.email) |
|
||||||
|
|
||||||
onboarding_template_id = "b48bc2cd-5887-4611-8296-d0ab3ed0e7fd".freeze |
|
||||||
token = user.send(:set_reset_password_token) |
|
||||||
url = "#{host}/account/password/edit?reset_password_token=#{token}" |
|
||||||
personalisation = { name: user.name || user.email, link: url } |
|
||||||
DeviseNotifyMailer.new.send_email(user.email, onboarding_template_id, personalisation) |
|
||||||
end |
|
||||||
end |
|
||||||
end |
|
@ -1,41 +0,0 @@ |
|||||||
require "rails_helper" |
|
||||||
require "rake" |
|
||||||
|
|
||||||
describe "rake onboarding_emails:send", type: task do |
|
||||||
subject(:task) { Rake::Task["onboarding_emails:send"] } |
|
||||||
|
|
||||||
context "when onboarding a new organisation to private beta" do |
|
||||||
let!(:user) { FactoryBot.create(:user) } |
|
||||||
let(:notify_client) { instance_double(Notifications::Client) } |
|
||||||
let(:devise_notify_mailer) { DeviseNotifyMailer.new } |
|
||||||
let(:reset_password_token) { "MCDH5y6Km-U7CFPgAMVS" } |
|
||||||
let(:host) { "http://localhost:3000" } |
|
||||||
|
|
||||||
before do |
|
||||||
Rake.application.rake_require("tasks/onboarding_emails") |
|
||||||
Rake::Task.define_task(:environment) |
|
||||||
task.reenable |
|
||||||
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) |
|
||||||
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) |
|
||||||
allow(notify_client).to receive(:send_email).and_return(true) |
|
||||||
allow(Devise.token_generator).to receive(:generate).and_return(reset_password_token) |
|
||||||
allow(ENV).to receive(:[]) |
|
||||||
allow(ENV).to receive(:[]).with("APP_HOST").and_return(host) |
|
||||||
end |
|
||||||
|
|
||||||
it "can send the onboarding emails" do |
|
||||||
expect(notify_client).to receive(:send_email).with( |
|
||||||
{ |
|
||||||
email_address: user.email, |
|
||||||
template_id: "b48bc2cd-5887-4611-8296-d0ab3ed0e7fd", |
|
||||||
personalisation: { |
|
||||||
name: user.name, |
|
||||||
link: "#{host}/account/password/edit?reset_password_token=#{reset_password_token}", |
|
||||||
}, |
|
||||||
}, |
|
||||||
) |
|
||||||
|
|
||||||
task.invoke(user.organisation.id) |
|
||||||
end |
|
||||||
end |
|
||||||
end |
|
@ -0,0 +1,46 @@ |
|||||||
|
require "rails_helper" |
||||||
|
require_relative "../../support/devise" |
||||||
|
|
||||||
|
RSpec.describe Auth::ConfirmationsController, type: :request do |
||||||
|
let(:page) { Capybara::Node::Simple.new(response.body) } |
||||||
|
let(:notify_client) { instance_double(Notifications::Client) } |
||||||
|
let(:devise_notify_mailer) { DeviseNotifyMailer.new } |
||||||
|
let(:user) { FactoryBot.create(:user, :data_provider, sign_in_count: 0, confirmed_at: nil) } |
||||||
|
|
||||||
|
before do |
||||||
|
allow(DeviseNotifyMailer).to receive(:new).and_return(devise_notify_mailer) |
||||||
|
allow(devise_notify_mailer).to receive(:notify_client).and_return(notify_client) |
||||||
|
allow(notify_client).to receive(:send_email).and_return(true) |
||||||
|
end |
||||||
|
|
||||||
|
context "when a confirmation link is clicked by a new user" do |
||||||
|
before do |
||||||
|
user.send_confirmation_instructions |
||||||
|
get "/account/confirmation?confirmation_token=#{user.confirmation_token}" |
||||||
|
end |
||||||
|
|
||||||
|
it "marks the user as confirmed" do |
||||||
|
expect(user.reload.confirmed_at).to be_a(Time) |
||||||
|
end |
||||||
|
|
||||||
|
it "redirects to the set password page" do |
||||||
|
follow_redirect! |
||||||
|
expect(page).to have_content(I18n.t("user.create_password")) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
context "when the token has expired" do |
||||||
|
let(:period) { Devise::TimeInflector.time_ago_in_words(User.confirm_within.ago) } |
||||||
|
|
||||||
|
before do |
||||||
|
user.send_confirmation_instructions |
||||||
|
allow(User).to receive(:find_first_by_auth_conditions).and_return(user) |
||||||
|
allow(user).to receive(:confirmation_period_expired?).and_return(true) |
||||||
|
get "/account/confirmation?confirmation_token=#{user.confirmation_token}" |
||||||
|
end |
||||||
|
|
||||||
|
it "shows the error page" do |
||||||
|
expect(page).to have_content("Your invitation link has expired") |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Loading…
Reference in new issue