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
34 changes: 34 additions & 0 deletions app/controllers/email_verifications_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class EmailVerificationsController < ApplicationController
skip_before_action :ensure_user_signed_in, only: [:show]

def show
@verification = API::EmailVerification.verify_email(tokened: params[:token])

@message = if @verification
'You’ve successfully verified your email. You can now continue to your account.'
else
'Verification link is invalid or has expired.'
end
rescue StandardError => e
Rails.logger.warn("Email verification failed: \\#{e.class} - \\#{e.message}")
@message = 'Verification link is invalid or has expired.'
end

def resend_email
if API::User.update_email(email: params[:email]).any?
redirect_to user_detail_path, notice: 'A new verification email has been sent to your new email address.'
end
rescue JSONAPI::Consumer::Errors::ConnectionError
redirect_to user_detail_path, alert: 'There was a problem connecting to the email service. Please try again later.'
end

def cancel_pending_email_change
if API::EmailVerification.delete_pending_email_change
redirect_to user_detail_path, notice: 'Your pending email change has been cancelled.'
else
redirect_to user_detail_path, alert: 'There was a problem cancelling your pending email change. Please try again.'
end
rescue JSONAPI::Consumer::Errors::ConnectionError
redirect_to user_detail_path, alert: 'There was a problem connecting to the email service. Please try again later.'
end
end
38 changes: 38 additions & 0 deletions app/controllers/user_details_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
class UserDetailsController < ApplicationController
def show
@suppliers = API::Supplier.all
@email_verification = API::EmailVerification.email_verification_pending?
@user_logs = API::User.user_auth_logs
end

def edit; end
Expand All @@ -20,4 +22,40 @@ def update
flash.now[:alert] = 'There was a problem connecting to the user service. Please try again later.'
render :edit, status: :service_unavailable
end

def edit_email; end

def update_email
new_email = params[:email]
email_confirmation = params[:email_confirmation]

error_message = email_update_validation(new_email, email_confirmation)
if error_message
flash.now[:alert] = error_message
return render :edit_email, status: :unprocessable_entity
end

api_response = API::User.update_email(email: new_email)

if api_response.any?
redirect_to user_detail_path,
notice: 'You must verify your new email address. Please check your inbox for a verification email.'
else
flash.now[:alert] = 'There was a problem updating your email address. Please try again.'
render :edit_email, status: :unprocessable_entity
end
rescue JSONAPI::Consumer::Errors::ConnectionError
flash.now[:alert] = 'There was a problem connecting to the user service. Please try again later.'
render :edit_email, status: :service_unavailable
end

private

def email_update_validation(email, confirmation)
return 'Please enter and confirm your email address.' if email.blank? || confirmation.blank?
return 'Email addresses do not match. Please try again.' unless email == confirmation
return 'Please enter a valid email address.' unless email =~ URI::MailTo::EMAIL_REGEXP

nil
end
end
29 changes: 29 additions & 0 deletions app/models/api/email_verification.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# app/models/api/email_verification.rb
module API
class EmailVerification < Base
custom_endpoint :verify_token, on: :collection, request_method: :post
custom_endpoint :active_verification, on: :collection, request_method: :get
custom_endpoint :cancel_pending_email_change, on: :collection, request_method: :delete

def self.verify_email(tokened:)
response = verify_token({ token: tokened })
response&.any?
rescue JSONAPI::Consumer::Errors::NotFound, JSONAPI::Consumer::Errors::ConnectionError
false
end

def self.email_verification_pending?
response = active_verification
response&.first
rescue JSONAPI::Consumer::Errors::NotFound, JSONAPI::Consumer::Errors::ConnectionError
nil
end

def self.delete_pending_email_change
cancel_pending_email_change
true
rescue JSONAPI::Consumer::Errors::NotFound, JSONAPI::Consumer::Errors::ConnectionError
false
end
end
end
2 changes: 2 additions & 0 deletions app/models/api/user.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module API
class User < Base
custom_endpoint :update_name, on: :collection, request_method: :patch
custom_endpoint :update_email, on: :collection, request_method: :patch
custom_endpoint :user_auth_logs, on: :collection, request_method: :get
end
end
6 changes: 6 additions & 0 deletions app/views/email_verifications/show.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
- page_title 'Email Verification'
%div{ class: "govuk-panel govuk-panel--confirmation govuk-!-margin-top-6 govuk-!-margin-bottom-6" }
%h1.govuk-panel__title
Email Verification
%div.govuk-panel__body
= @message
2 changes: 2 additions & 0 deletions app/views/errors/internal_server_error.json.jbuilder
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
json.error 'Internal Server Error'
json.message 'Sorry, something went wrong. Please try again later.'
2 changes: 2 additions & 0 deletions app/views/sessions/verify_email.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h1>Verify your email address</h1>
<p>Your email address is not verified. Please check your inbox for a verification email and follow the instructions to verify your account before continuing.</p>
26 changes: 26 additions & 0 deletions app/views/user_details/edit_email.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
- page_title 'Change your email address'

.govuk-grid-row
.govuk-grid-column-two-thirds
%h1.govuk-heading-xl Change your email address

- if flash[:alert].present?
.govuk-error-summary{role: "alert", tabindex: "-1"}
.govuk-error-summary__title
%h2.govuk-error-summary__title There is a problem
.govuk-error-summary__body
%ul.govuk-list.govuk-error-summary__list
%li= flash[:alert]

= form_with url: update_email_user_detail_path, method: :patch, local: true do |form|
.govuk-form-group
= form.label :email, 'Email address', class: 'govuk-label'
= form.email_field :email, class: 'govuk-input'

.govuk-form-group
= form.label :email_confirmation, 'Confirm email address', class: 'govuk-label'
= form.email_field :email_confirmation, class: 'govuk-input'

%p{class: 'govuk-!-margin-top-7'}
= form.submit 'Update email', class: 'govuk-button'
= link_to 'Cancel', user_detail_path, class: 'govuk-link govuk-!-margin-left-3 govuk-!-font-size-19'
37 changes: 37 additions & 0 deletions app/views/user_details/show.html.haml
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
- page_title 'User profile'

- if @email_verification&.token.present?
.govuk-notification-banner{"aria-labelledby" => "govuk-notification-banner-title", "data-module" => "govuk-notification-banner", :role => "region"}
.govuk-notification-banner__header
%h2#govuk-notification-banner-title.govuk-notification-banner__title
Important
.govuk-notification-banner__content
%p.govuk-notification-banner__heading
Verify your new email address
= @email_verification.new_email
= succeed "." do
= link_to 'Resend email', resend_email_verification_path(email: @email_verification.new_email), method: :post, class: 'govuk-notification-banner__link'
.govuk-button-group
= button_to 'Cancel', cancel_pending_email_change_path, method: :post, data: { confirm: 'Are you sure you want to cancel your pending email change?' }, class: 'govuk-button govuk-button--secondary'

.govuk-grid-row
.govuk-grid-column-full
%h1.govuk-heading-xl
Expand All @@ -19,10 +33,33 @@
%td.govuk-table__cell Email
%td.govuk-table__cell= current_user.email
%td.govuk-table__cell
= link_to 'Change email', edit_email_user_detail_path, class: 'govuk-link'
%tr.govuk-table__row
%td.govuk-table__cell User since
%td.govuk-table__cell= user_created_date(current_user).to_fs(:default)
%td.govuk-table__cell
- if @user_logs.present?
%tr.govuk-table__row
%td.govuk-table__cell User log(s)
%td.govuk-table__cell
- @user_logs.each_with_index do |log, i|
%ul.govuk-list.govuk-list
%li
%strong Date:
= Time.parse(log.date).utc.strftime("%d %B %Y, %l:%M %p")
%li
%strong IP:
= log.ip
%li
%strong User Agent:
= log.user_agent
%li
%strong Device:
= log.isMobile ? "Mobile" : "Desktop"
- unless i == @user_logs.size - 1
%hr.govuk-section-break.govuk-section-break--visible

%td.govuk-table__cell
%tr.govuk-table__row
%td.govuk-table__cell Supplier(s)
%td.govuk-table__cell
Expand Down
10 changes: 9 additions & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,16 +41,24 @@

resources :release_notes, only: %i[index]

resource :user_detail, only: %i[show edit update]
resource :user_detail, only: %i[show edit update] do
get :edit_email
patch :update_email
end

match '/auth/:provider/callback', to: 'sessions#create', via: %i[get post]
match '/auth/failure', to: 'errors#auth_failure', via: %i[get post]
get '/sign_out', to: 'sessions#destroy', as: :sign_out
get '/verify_email', to: 'sessions#verify_email', as: :verify_email
get '/style_guide', to: 'styleguide#index'
get '/support', to: 'support#index'
get '/support/frameworks', to: 'support#frameworks'
get '/cookie-settings', to: 'home#cookie_settings'
get '/cookie-policy', to: 'home#cookie_policy'
get '/accessibility', to: 'accessibility#index'
get '/check', to: 'check#index'
get '/email/verification/:token', to: 'email_verifications#show', as: :email_verification
post '/email/verification/resend', to: 'email_verifications#resend_email', as: :resend_email_verification
post '/email/verification/cancel_pending_email_change', to: 'email_verifications#cancel_pending_email_change',
as: :cancel_pending_email_change
end
2 changes: 2 additions & 0 deletions spec/features/user_can_view_profile_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
mock_user_with_multiple_suppliers_endpoint!
mock_notifications_endpoint!
mock_suppliers_endpoint!
mock_email_verification_pending_endpoint!
mock_user_auth_logs_endpoint!

visit '/'
click_button 'sign-in'
Expand Down
6 changes: 6 additions & 0 deletions spec/features/users_can_sign_in_and_out_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
mock_incomplete_tasks_endpoint!
mock_notifications_endpoint!
mock_user_endpoint!
mock_email_verification_pending_endpoint!
mock_user_auth_logs_endpoint!

visit '/tasks'

Expand Down Expand Up @@ -38,6 +40,8 @@
mock_unstarted_tasks_endpoint!
mock_incomplete_tasks_endpoint!
mock_user_endpoint!
mock_email_verification_pending_endpoint!
mock_user_auth_logs_endpoint!

click_on 'sign-in'

Expand All @@ -51,6 +55,8 @@
mock_incomplete_tasks_endpoint!
mock_notifications_endpoint!
mock_user_endpoint!
mock_email_verification_pending_endpoint!
mock_user_auth_logs_endpoint!

visit '/'
click_on 'sign-in'
Expand Down
2 changes: 2 additions & 0 deletions spec/requests/user_details_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
mock_notifications_endpoint!
mock_user_with_multiple_suppliers_endpoint!
mock_suppliers_endpoint!
mock_email_verification_pending_endpoint!
mock_user_auth_logs_endpoint!
end

describe 'visiting the user profile page' do
Expand Down
8 changes: 8 additions & 0 deletions spec/support/api_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,14 @@ def mock_update_user_name_endpoint_connection_error!
.to_raise(Faraday::ConnectionFailed.new('Connection failed'))
end

def mock_email_verification_pending_endpoint!(result = nil)
allow(API::EmailVerification).to receive(:email_verification_pending?).and_return(result)
end

def mock_user_auth_logs_endpoint!(logs = [])
allow(API::User).to receive(:user_auth_logs).and_return(logs)
end

private

def json_headers
Expand Down