diff --git a/.env.example b/.env.example index 629b0d7bc..5f7182348 100644 --- a/.env.example +++ b/.env.example @@ -82,6 +82,8 @@ E2E=true # Deployment environment ENVIRONMENT= +GOV_ONE_LOGIN_ENABLED=true + # Base URI for Gov One Login requests GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb index 75df54064..8cf771764 100644 --- a/app/controllers/users/omniauth_callbacks_controller.rb +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -1,10 +1,14 @@ +# Controller handling OmniAuth callbacks for user authentication. class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController + + # This method is called by Devise after successful Gov One Login authentication + # @return [nil] def openid_connect - return error_redirect unless valid_params? + return error_redirect unless valid_params? || Rails.application.gov_one_login_enabled? session.delete(:gov_one_auth_state) - auth_service = GovOneAuthService.new(params['code']) + auth_service = GovOneAuthService.new(code: params['code']) tokens_response = auth_service.tokens return error_redirect unless valid_tokens?(tokens_response) @@ -39,7 +43,7 @@ def valid_user_info?(user_info_response) user_info_response.present? && user_info_response['email'].present? end - # @return [void] + # @return [nil] def error_redirect flash[:alert] = 'There was a problem signing in. Please try again.' redirect_to root_path @@ -47,12 +51,10 @@ def error_redirect # @return [User] def find_or_create_user(email, id) - existing_user = User.find_by(email: email) - if User.find_by(email: email) - existing_user.update!(gov_one_id: id) - elsif User.find_by(gov_one_id: id) - existing_user = User.find_by(gov_one_id: id) + existing_user = User.find_by(email: email) || User.find_by(gov_one_id: id) + if existing_user existing_user.update!(email: email) + existing_user.update!(gov_one_id: id) if existing_user.gov_one_id.nil? else return User.create_from_gov_one(email: email, gov_one_id: id) end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e25200506..b8f45ea20 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,28 +1,35 @@ module ApplicationHelper - CONFIG_SUMMARY = [ - ['Rails version', Rails.version], - ['Ruby version', RUBY_VERSION], - ['GOV.UK Frontend', JSON.parse(File.read(Rails.root.join('package.json'))).dig('dependencies', 'govuk-frontend').tr('^', '')], - ].freeze - # @return [String] def navigation - render(HeaderComponent.new(classes: 'dfe-header noprint', container_classes: %w[dfe-header-f-header-flex], navigation_label: 'Primary navigation')) do |header| - header.with_navigation_item(text: 'Home', href: root_path, classes: %w[dfe-header__navigation-item]) + govuk_header(classes: 'noprint') do |header| + header.with_navigation_item(text: 'Home', href: root_path) + header.with_custom_logo { custom_logo } if user_signed_in? - header.with_action_link(text: 'My Account', href: user_path, options: { inverse: true }) - header.with_action_link(text: 'Sign out', href: destroy_user_session_path, options: { id: 'sign-out-desktop', data: { turbo_method: :get }, inverse: true }) - header.with_navigation_item(text: 'My modules', href: my_modules_path, classes: %w[dfe-header__navigation-item]) - header.with_navigation_item(text: 'Learning log', href: user_notes_path, classes: %w[dfe-header__navigation-items]) if current_user.course_started? - header.with_navigation_item(text: 'My account', href: user_path, classes: ['dfe-header__navigation-item dfe-header-f-mob']) - header.with_navigation_item(text: 'Sign out', href: destroy_user_session_path, options: { data: { turbo_method: :get } }, classes: ['dfe-header__navigation-item dfe-header-f-mob'], html_attributes: { id: 'sign-out-f-mob' }) + header.with_navigation_item(text: 'My modules', href: my_modules_path) + header.with_navigation_item(text: 'Learning log', href: user_notes_path) if current_user.course_started? + header.with_navigation_item(text: 'My account', href: user_path) + header.with_navigation_item(text: 'Sign out', href: destroy_user_session_path, options: { data: { turbo_method: :get } }) else - header.with_action_link(text: 'Sign in', href: new_user_session_path, options: { inverse: true }) - header.with_navigation_item(text: 'Sign in', href: new_user_session_path, classes: ['dfe-header__navigation-item dfe-header-f-mob']) + header.with_navigation_item(text: 'Sign in', href: new_user_session_path) end end end + # @return [String] + def custom_logo + [ + image_tag('crest.png', alt: 'Department for Education homepage', class: 'govuk-header__logotype-crown-fallback-image'), + content_tag(:span, 'Department for Education | ', class: 'govuk-header__logotype-text'), + content_tag(:span, service_name, class: 'govuk-header__product-name'), + ].join.html_safe + end + + CONFIG_SUMMARY = [ + ['Rails version', Rails.version], + ['Ruby version', RUBY_VERSION], + ['GOV.UK Frontend', JSON.parse(File.read(Rails.root.join('package.json'))).dig('dependencies', 'govuk-frontend').tr('^', '')], + ].freeze + # @return [String] def configuration_summary_list rows = diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb index 7f8e39118..bfda10fd8 100644 --- a/app/services/gov_one_auth_service.rb +++ b/app/services/gov_one_auth_service.rb @@ -1,8 +1,9 @@ +# Service for interacting with Gov One Login + class GovOneAuthService - # @param code [String] - def initialize(code) - @code = code - end + extend Dry::Initializer + + option :code, Types::String # @return [Hash] def tokens @@ -61,6 +62,7 @@ def build_http(uri) http end + # @return [Hash] def jwks discovery_url = "#{ENV['GOV_ONE_BASE_URI']}/.well-known/jwks.json" uri = URI.parse(discovery_url) diff --git a/config/application.rb b/config/application.rb index 39171dea3..215cec3ef 100644 --- a/config/application.rb +++ b/config/application.rb @@ -109,6 +109,11 @@ def debug? Types::Params::Bool[ENV.fetch('DEBUG', false)] end + # @return [Boolean] + def gov_one_login_enabled? + Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN_ENABLED', false)] + end + # @return [ActiveSupport::TimeWithZone] def public_beta_launch_date Time.zone.local(2023, 2, 9, 15, 0, 0) diff --git a/config/routes.rb b/config/routes.rb index 9de3c4b5c..289c4f67a 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,7 +5,7 @@ get 'my-modules', to: 'learning#show' # @see User#course get 'about-training', to: 'training/modules#index', as: :course_overview - get 'gov-one/info', to: 'gov_one#info' + get 'gov-one/info', to: 'gov_one#info' if Rails.application.gov_one_login_enabled? get '/404', to: 'errors#not_found', via: :all get '/422', to: 'errors#unprocessable_entity', via: :all diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb index 3d9ce3c56..7cd6cb351 100644 --- a/spec/services/gov_one_auth_service_spec.rb +++ b/spec/services/gov_one_auth_service_spec.rb @@ -3,7 +3,7 @@ RSpec.describe GovOneAuthService do let(:code) { 'mock_code' } let(:mock_response) { instance_double('response') } - let(:auth_service) { described_class.new(code) } + let(:auth_service) { described_class.new(code: code) } let(:mock_http) { instance_double('Sentry::Net::HTTP') } before do