From f6afd66d1e083d3e728735820381550e811ff431 Mon Sep 17 00:00:00 2001 From: CatalinVoineag <11318084+CatalinVoineag@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:38:36 +0000 Subject: [PATCH] One login auth We want to move our candidates to use one login when sigin up/in to our service. This commit adds the basic implementation for users to login or create a candidate account in apply using one login. --- app/controllers/one_login_controller.rb | 58 +++++++++ app/models/one_login_user.rb | 50 +++++++ .../create_account_or_sign_in.html.erb | 50 ++++--- app/views/layouts/_header.html.erb | 3 +- config/application.rb | 2 +- config/initializers/omniauth.rb | 8 ++ config/locales/en.yml | 1 + config/routes.rb | 8 +- lib/omniauth/onelogin_setup.rb | 39 ++++++ .../govuk_one_login_openid_connect.rb | 27 ++++ spec/factories/one_login_auth.rb | 7 + spec/models/one_login_user_spec.rb | 68 ++++++++++ spec/requests/one_login_controller_spec.rb | 122 ++++++++++++++++++ spec/support/test_helpers/one_login_helper.rb | 16 +++ .../candidate_signs_in_spec.rb | 92 +++++++++++++ 15 files changed, 529 insertions(+), 22 deletions(-) create mode 100644 app/controllers/one_login_controller.rb create mode 100644 app/models/one_login_user.rb create mode 100644 lib/omniauth/onelogin_setup.rb create mode 100644 lib/omniauth/strategies/govuk_one_login_openid_connect.rb create mode 100644 spec/factories/one_login_auth.rb create mode 100644 spec/models/one_login_user_spec.rb create mode 100644 spec/requests/one_login_controller_spec.rb create mode 100644 spec/support/test_helpers/one_login_helper.rb create mode 100644 spec/system/candidate_interface/one_login_signup/candidate_signs_in_spec.rb diff --git a/app/controllers/one_login_controller.rb b/app/controllers/one_login_controller.rb new file mode 100644 index 00000000000..4bfd8109c5c --- /dev/null +++ b/app/controllers/one_login_controller.rb @@ -0,0 +1,58 @@ +class OneLoginController < ApplicationController + before_action :one_login_enabled + before_action :set_sentry_context, only: :callback + + def callback + auth = request.env['omniauth.auth'] + session[:onelogin_id_token] = auth&.credentials&.id_token + candidate = OneLoginUser.authentificate(auth) + + sign_in(candidate, scope: :candidate) + candidate.update!(last_signed_in_at: Time.zone.now) + + redirect_to candidate_interface_interstitial_path + rescue OneLoginUser::Error => e + session[:one_login_error] = e.message + redirect_to auth_onelogin_sign_out_path + end + + def sign_out + id_token = session[:onelogin_id_token] + one_login_error = session[:one_login_error] + reset_session + + session[:one_login_error] = one_login_error + # Go back to one login to sign out the user on their end as well + redirect_to logout_onelogin_path(id_token_hint: id_token) + end + + def sign_out_complete + if session[:one_login_error].present? + Sentry.capture_message(session[:one_login_error], level: :error) + redirect_to internal_server_error_path + else + redirect_to candidate_interface_create_account_or_sign_in_path + end + end + + def failure + session[:one_login_error] = "One login failure with #{params[:message]} " \ + "for onelogin_id_token: #{session[:onelogin_id_token]}" + + redirect_to auth_onelogin_sign_out_path + end + +private + + def one_login_enabled + return if FeatureFlag.active?(:one_login_candidate_sign_in) + + redirect_to root_path + end + + def set_sentry_context + Sentry.set_extras( + omniauth_hash: request.env['omniauth.auth'].to_h, + ) + end +end diff --git a/app/models/one_login_user.rb b/app/models/one_login_user.rb new file mode 100644 index 00000000000..f41cbccc2ed --- /dev/null +++ b/app/models/one_login_user.rb @@ -0,0 +1,50 @@ +class OneLoginUser + class Error < StandardError; end + attr_reader :email_address, :token + + def initialize(omniauth_object) + @email_address = omniauth_object.info.email + @token = omniauth_object.uid + end + + def self.authentificate(omniauth_auth) + new(omniauth_auth).authentificate + end + + def authentificate + one_login_auth = OneLoginAuth.find_by(token:) + existing_candidate = Candidate.find_by(email_address:) + + return candidate_with_one_login(one_login_auth) if one_login_auth + return existing_candidate_without_one_login(existing_candidate) if existing_candidate + + created_candidate + end + +private + + def candidate_with_one_login(one_login_auth) + one_login_auth.update!(email_address:) + one_login_auth.candidate + end + + def existing_candidate_without_one_login(existing_candidate) + if existing_candidate.one_login_auth.present? && existing_candidate.one_login_auth.token != token + raise( + Error, + "Candidate #{existing_candidate.id} has a different one login " \ + "token than the user trying to login. Token used to auth #{token}", + ) + end + + existing_candidate.create_one_login_auth!(token:, email_address:) + existing_candidate + end + + def created_candidate + candidate = Candidate.create!(email_address:) + candidate.create_one_login_auth!(token:, email_address:) + + candidate + end +end diff --git a/app/views/candidate_interface/start_page/create_account_or_sign_in.html.erb b/app/views/candidate_interface/start_page/create_account_or_sign_in.html.erb index 350a6fddd86..021b76df73d 100644 --- a/app/views/candidate_interface/start_page/create_account_or_sign_in.html.erb +++ b/app/views/candidate_interface/start_page/create_account_or_sign_in.html.erb @@ -4,31 +4,43 @@
+ <%= t('govuk.one_login_account_guidance') %> +
+ + <%= govuk_button_to(t('continue'), '/auth/onelogin') %> + <% else %> + <%= form_with( + model: @create_account_or_sign_in_form, + url: candidate_interface_create_account_or_sign_in_path(providerCode: params[:providerCode], courseCode: params[:courseCode]), + method: :post, + ) do |f| %> + <%= f.govuk_error_summary %> + +- You can usually start applying for teacher training in October, the - year before your course starts. Courses can fill up quickly, so apply - as soon as you can. - <%= govuk_link_to 'Read how the application process works', candidate_interface_guidance_path %>. -
++ You can usually start applying for teacher training in October, the + year before your course starts. Courses can fill up quickly, so apply + as soon as you can. + <%= govuk_link_to 'Read how the application process works', candidate_interface_guidance_path %>. +
+ <% end %>