From ba2c3052fe258faac8c5c0f312d83d0c0dc684a9 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 20 Oct 2023 16:17:21 +0100
Subject: [PATCH 001/122] wip

---
 .gitignore                                    |  4 ++
 Gemfile                                       |  3 +
 Gemfile.lock                                  | 61 +++++++++++++++++
 .../users/omniauth_callbacks_controller.rb    | 23 +++++++
 app/helpers/application_helper.rb             |  8 +--
 app/helpers/gov_one_helper.rb                 | 11 ++++
 app/models/user.rb                            | 10 ++-
 app/services/gov_one_auth_service.rb          | 65 +++++++++++++++++++
 app/views/home/index.html.slim                |  6 +-
 app/views/user/show.html.slim                 |  8 ---
 config/initializers/devise.rb                 | 19 +++++-
 config/routes.rb                              |  3 +
 .../omniauth_callbacks_controller_spec.rb     | 48 ++++++++++++++
 .../users/sessions_controller_spec.rb         | 49 +++++++-------
 spec/rails_helper.rb                          |  2 +
 spec/services/gov_one_auth_service_spec.rb    | 52 +++++++++++++++
 16 files changed, 328 insertions(+), 44 deletions(-)
 create mode 100644 app/controllers/users/omniauth_callbacks_controller.rb
 create mode 100644 app/helpers/gov_one_helper.rb
 create mode 100644 app/services/gov_one_auth_service.rb
 create mode 100644 spec/controllers/users/omniauth_callbacks_controller_spec.rb
 create mode 100644 spec/services/gov_one_auth_service_spec.rb

diff --git a/.gitignore b/.gitignore
index 8ca8af16b..ba33dc4ad 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,6 +57,10 @@ localhost.crt
 localhost.key
 nscacert.pem
 
+# gov one login keys
+private_key.pem
+public_key.pem
+
 # Yarn files
 .yarn/*
 
diff --git a/Gemfile b/Gemfile
index d37479bdd..d6a013d5b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -37,6 +37,9 @@ gem 'bootsnap', require: false
 
 # User authentication
 gem 'devise'
+gem 'jwt'
+gem 'omniauth_openid_connect'
+gem 'omniauth-rails_csrf_protection'
 
 # HTML abstraction markup language
 gem 'slim-rails'
diff --git a/Gemfile.lock b/Gemfile.lock
index e20b78f6b..e3f0b4985 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -68,11 +68,13 @@ GEM
       tzinfo (~> 2.0)
     addressable (2.8.5)
       public_suffix (>= 2.0.2, < 6.0)
+    aes_key_wrap (1.1.0)
     ahoy_matey (5.0.1)
       activesupport (>= 6.1)
       device_detector (>= 1)
       safely_block (>= 0.4)
     ast (2.4.2)
+    attr_required (1.0.1)
     backports (3.24.1)
     base64 (0.1.1)
     bcrypt (3.1.19)
@@ -80,6 +82,7 @@ GEM
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
       rouge (>= 1.0.0)
+    bindata (2.4.15)
     bindex (0.8.1)
     binding_of_caller (1.0.0)
       debug_inspector (>= 0.0.1)
@@ -180,6 +183,8 @@ GEM
       base64
       faraday-net_http (>= 2.0, < 3.1)
       ruby2_keywords (>= 0.0.4)
+    faraday-follow_redirects (0.3.0)
+      faraday (>= 1, < 3)
     faraday-net_http (3.0.2)
     ffi (1.16.3)
     ffi-compiler (1.0.1)
@@ -310,6 +315,12 @@ GEM
     jsbundling-rails (1.2.1)
       railties (>= 6.0.0)
     json (2.6.3)
+    json-jwt (1.16.3)
+      activesupport (>= 4.2)
+      aes_key_wrap
+      bindata
+      faraday (~> 2.0)
+      faraday-follow_redirects
     jwt (2.7.1)
     kramdown (2.4.0)
       rexml
@@ -358,6 +369,29 @@ GEM
       racc (~> 1.4)
     notifications-ruby-client (5.4.0)
       jwt (>= 1.5, < 3)
+    omniauth (2.1.1)
+      hashie (>= 3.4.6)
+      rack (>= 2.2.3)
+      rack-protection
+    omniauth-rails_csrf_protection (1.0.1)
+      actionpack (>= 4.2)
+      omniauth (~> 2.0)
+    omniauth_openid_connect (0.7.1)
+      omniauth (>= 1.9, < 3)
+      openid_connect (~> 2.2)
+    openid_connect (2.2.0)
+      activemodel
+      attr_required (>= 1.0.0)
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      json-jwt (>= 1.16)
+      net-smtp
+      rack-oauth2 (~> 2.2)
+      swd (~> 2.0)
+      tzinfo
+      validate_email
+      validate_url
+      webfinger (~> 2.0)
     opentelemetry-api (1.2.3)
     opentelemetry-common (0.20.0)
       opentelemetry-api (~> 1.0)
@@ -588,6 +622,15 @@ GEM
     raabro (1.4.0)
     racc (1.7.1)
     rack (2.2.8)
+    rack-oauth2 (2.2.0)
+      activesupport
+      attr_required
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      json-jwt (>= 1.11.0)
+      rack (>= 2.1.0)
+    rack-protection (3.1.0)
+      rack (~> 2.2, >= 2.2.4)
     rack-proxy (0.7.7)
       rack
     rack-test (2.1.0)
@@ -758,6 +801,11 @@ GEM
     stimulus-rails (1.2.2)
       railties (>= 6.0.0)
     stringio (3.0.8)
+    swd (2.0.2)
+      activesupport (>= 3)
+      attr_required (>= 0.0.5)
+      faraday (~> 2.0)
+      faraday-follow_redirects
     temple (0.10.3)
     thor (1.2.2)
     tilt (2.3.0)
@@ -774,6 +822,12 @@ GEM
       unf_ext
     unf_ext (0.0.8.2)
     unicode-display_width (2.5.0)
+    validate_email (0.1.6)
+      activemodel (>= 3.0)
+      mail (>= 2.2.5)
+    validate_url (1.0.15)
+      activemodel (>= 3.0.0)
+      public_suffix
     view_component (3.5.0)
       activesupport (>= 5.2.0, < 8.0)
       concurrent-ruby (~> 1.0)
@@ -785,6 +839,10 @@ GEM
       activemodel (>= 6.0.0)
       bindex (>= 0.4.0)
       railties (>= 6.0.0)
+    webfinger (2.1.2)
+      activesupport
+      faraday (~> 2.0)
+      faraday-follow_redirects
     webrick (1.8.1)
     websocket (1.2.10)
     websocket-driver (0.7.6)
@@ -841,7 +899,10 @@ DEPENDENCIES
   importmap-rails
   jbuilder
   jsbundling-rails
+  jwt
   launchy
+  omniauth-rails_csrf_protection
+  omniauth_openid_connect
   pg
   pry-byebug
   pry-doc
diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
new file mode 100644
index 000000000..893603078
--- /dev/null
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -0,0 +1,23 @@
+class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
+  def openid_connect
+    code = params['code']
+
+    auth_service = GovOneAuthService.new(code)
+    tokens_response = auth_service.tokens
+    access_token = tokens_response['access_token']
+    session[:id_token] = tokens_response['id_token']
+
+    user_info_response = auth_service.user_info(access_token)
+    @email = user_info_response['email']
+
+    gov_user = find_or_create_user
+    sign_in_and_redirect gov_user if gov_user
+  end
+
+private
+
+  # @return [User]
+  def find_or_create_user
+    User.find_by(email: @email) || create_from_email(@email)
+  end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 0ff6ce268..baa259d88 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -5,14 +5,14 @@ def navigation
       header.with_navigation_item(text: 'Home', href: root_path, classes: %w[dfe-header__navigation-item])
       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_action_link(text: 'Sign out', href: logout_uri, 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: 'Sign out', href: logout_uri, options: { data: { turbo_method: :get } }, classes: ['dfe-header__navigation-item dfe-header-f-mob'], html_attributes: { id: 'sign-out-f-mob' })
       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_action_link(text: 'Sign in', href: login_uri, options: { inverse: true })
+        header.with_navigation_item(text: 'Sign in', href: login_uri, classes: ['dfe-header__navigation-item dfe-header-f-mob'])
       end
     end
   end
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
new file mode 100644
index 000000000..a8eefb2dc
--- /dev/null
+++ b/app/helpers/gov_one_helper.rb
@@ -0,0 +1,11 @@
+module GovOneHelper
+  # @return [String]
+  def login_uri
+    "#{ENV['GOV_ONE_AUTH_URI']}&nonce=#{SecureRandom.uuid}&state=#{SecureRandom.uuid}"
+  end
+
+  # @return [String]
+  def logout_uri
+    "#{ENV['GOV_ONE_SIGN_OUT_URI']}?id_token_hint=#{session[:id_token]}&post_logout_redirect_uri=#{ENV['GOV_ONE_SIGN_OUT_REDIRECT_URI']}&state=#{SecureRandom.uuid}"
+  end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index c473bb527..2cbec1b77 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -21,12 +21,20 @@ def self.dashboard_headers
     DASHBOARD_ATTRS + Training::Module.live.map { |mod| "module_#{mod.position}_time" }
   end
 
+  # @return [User]
+  def self.create_from_email(email)
+    user = User.new(email: email, confirmed_at: Time.zone.now)
+    user.save(validate: false)
+    user
+  end
+
   # Include default devise modules. Others available are:
   # :timeoutable, :trackable, :recoverable and :omniauthable
   attr_accessor :context
 
   devise :database_authenticatable, :registerable, :recoverable,
-         :validatable, :rememberable, :confirmable, :lockable, :timeoutable
+         :validatable, :rememberable, :confirmable, :lockable, :timeoutable,
+         :omniauthable, omniauth_providers: [:openid_connect]
 
   has_many :responses
   has_many :user_answers
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
new file mode 100644
index 000000000..ff86c9765
--- /dev/null
+++ b/app/services/gov_one_auth_service.rb
@@ -0,0 +1,65 @@
+class GovOneAuthService
+  # @param code [String]
+  def initialize(code)
+    @code = code
+  end
+
+  # @return [Hash]
+  def tokens
+    body = {
+      grant_type: 'authorization_code',
+      code: @code,
+      redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
+      client_assertion_type: ENV['GOV_ONE_CLIENT_ASSERTION_TYPE'],
+      client_assertion: jwt_assertion,
+    }
+
+    token_uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/token")
+    http = build_http(token_uri)
+
+    token_request = Net::HTTP::Post.new(token_uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })
+    token_request.set_form_data(body)
+    token_response = http.request(token_request)
+
+    JSON.parse(token_response.body)
+  end
+
+  # @param access_token [String]
+  # @return [Hash]
+  def user_info(access_token)
+    userinfo_uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/userinfo")
+    http = build_http(userinfo_uri)
+
+    userinfo_request = Net::HTTP::Get.new(userinfo_uri.path, { 'Authorization' => "Bearer #{access_token}" })
+    userinfo_response = http.request(userinfo_request)
+
+    JSON.parse(userinfo_response.body)
+  end
+
+private
+
+  # @param uri [URI]
+  # @return [Net::HTTP]
+  def build_http(uri)
+    http = Net::HTTP.new(uri.host, uri.port)
+    http.use_ssl = true
+    http
+  end
+
+  # @return [String]
+  def jwt_assertion
+    rsa_public = OpenSSL::PKey::RSA.new(File.read('public_key.pem'))
+    rsa_private = OpenSSL::PKey::RSA.new(File.read('private_key.pem'))
+
+    payload = {
+      aud: "#{ENV['GOV_ONE_BASE_URI']}/token",
+      iss: ENV['GOV_ONE_CLIENT_ID'],
+      sub: ENV['GOV_ONE_CLIENT_ID'],
+      exp: Time.now.to_i + 5 * 60,
+      jti: SecureRandom.uuid,
+      iat: Time.now.to_i,
+    }
+
+    JWT.encode payload, rsa_private, 'RS256'
+  end
+end
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index f7bc8011c..b9a694e94 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -13,7 +13,7 @@
       .govuk-grid-row
         .govuk-grid-column-two-thirds
           = content_resource 'home.hero', service_name: service_name
-
+          
           = govuk_button_link_to course_overview_path, class: 'govuk-button--start govuk-!-margin-bottom-4' do
             - if current_user
               | Learn more
@@ -37,6 +37,4 @@
         = content_resource 'home.login'
 
         .govuk-button-group
-          = govuk_button_link_to 'Sign in', new_user_session_path
-          .white-space-pre-wrap= ' or '
-          = govuk_link_to 'create an account', new_user_registration_path
+          = govuk_button_link_to 'Sign in with Gov One Login', login_uri
diff --git a/app/views/user/show.html.slim b/app/views/user/show.html.slim
index 6febd1cfe..8ec8ad705 100644
--- a/app/views/user/show.html.slim
+++ b/app/views/user/show.html.slim
@@ -14,14 +14,6 @@
         - row.with_key { 'Name' }
         - row.with_value(text: current_user.name, classes: ['data-hj-suppress'])
         - row.with_action(text: t('links.change'), href: edit_registration_name_path, visually_hidden_text: 'name', html_attributes: { id: :edit_name_registration })
-      - your_details.with_row do |row|
-        - row.with_key { 'Email' }
-        - row.with_value(text:current_user.email, classes: ['data-hj-suppress'])
-        - row.with_action(text: t('links.change'), href: edit_email_user_path, visually_hidden_text: 'email', html_attributes: { id: :edit_email_user })
-      - your_details.with_row do |row|
-        - row.with_key { 'Password' }
-        - row.with_value { t('my_account.password_changed', date: current_user.password_last_changed) }
-        - row.with_action(text: t('links.change'), href: edit_password_user_path, visually_hidden_text: 'password', html_attributes: { id: :edit_password_user })
 
     = govuk_summary_list do |other_details|
       - other_details.with_row do |row|
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index f7ca631b4..cfbf68e79 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -306,9 +306,22 @@ def i18n_message(default = nil)
   config.sign_out_via = :get
 
   # ==> OmniAuth
-  # Add a new OmniAuth provider. Check the wiki for more information on setting
-  # up on your models and hooks.
-  # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo'
+  config.omniauth :openid_connect, {
+    name: :gov_one,
+    scope: %i[openid email],
+    response_type: :code,
+    client_options: {
+      port: 443,
+      scheme: 'https',
+      host: 'oidc.integration.account.gov.uk',
+      identifier: ENV['GOV_ONE_CLIENT_ID'],
+      secret: ENV['GOV_ONE_CLIENT_SECRET'],
+      redirect_uri: 'users/auth/openid_connect/callback',
+    },
+    authorize_params: {
+      prompt: 'login',
+    },
+  }
 
   # ==> Warden configuration
   # If you want to use other strategies, that are not supported by Devise, or
diff --git a/config/routes.rb b/config/routes.rb
index e149d70fe..d1b01e2e5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -18,6 +18,7 @@
                confirmations: 'confirmations',
                passwords: 'passwords',
                registrations: 'registrations',
+               omniauth_callbacks: 'users/omniauth_callbacks',
              },
              path_names: {
                sign_in: 'sign-in',
@@ -31,6 +32,7 @@
     get 'check_session_timeout', to: 'timeout#check'
     get 'extend_session', to: 'timeout#extend'
     get 'users/timeout', to: 'timeout#timeout_user'
+    get '/users/sign_out', to: 'users/sessions#destroy'
   end
 
   namespace :registration do
@@ -52,6 +54,7 @@
     get 'check-email-confirmation'
     get 'check-email-password-reset'
     get 'edit-training-emails'
+    devise_for :users, controllers: { omniauth_callbacks: 'controllers/users/omniauth_callbacks' }
     patch 'update-training-emails'
 
     resource :close_account, only: %i[new update show], path: 'close' do
diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
new file mode 100644
index 000000000..85875a3a9
--- /dev/null
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -0,0 +1,48 @@
+require 'rails_helper'
+
+RSpec.describe Users::OmniauthCallbacksController, type: :controller do
+
+  before do
+    @request.env['devise.mapping'] = Devise.mappings[:user]
+  end
+  describe '#openid_connect' do
+    let(:auth_service) { instance_double(GovOneAuthService) }
+    let(:access_token) { 'mock_access_token' }
+    let(:id_token) { 'mock_id_token' }
+    let(:email) { 'test@example.com' }
+
+    before do
+      allow(GovOneAuthService).to receive(:new).and_return(auth_service)
+      allow(auth_service).to receive(:tokens).and_return('access_token' => access_token, 'id_token' => id_token)
+      allow(auth_service).to receive(:user_info).with(access_token).and_return('email' => email)
+    end
+
+    it 'sets session id_token and finds or creates the user' do
+      get :openid_connect, params: { 'code' => 'mock_code' }
+
+      expect(session[:id_token]).to eq(id_token)
+      expect(assigns(:email)).to eq(email)
+      expect(response).to redirect_to(root_path)
+    end
+
+    it 'signs in and redirects the user if found or created' do
+      user = create(:user, email: email) # Assuming you have a User model and FactoryBot
+
+      allow(controller).to receive(:find_or_create_user).and_return(user)
+
+      get :openid_connect, params: { 'code' => 'mock_code' }
+
+      expect(response).to redirect_to(root_path) # Adjust the path based on your actual redirect path
+      expect(subject.current_user).to eq(user)
+    end
+
+    it 'does not sign in if user is not found or created' do
+      allow(controller).to receive(:find_or_create_user).and_return(nil)
+
+      get :openid_connect, params: { 'code' => 'mock_code' }
+
+      expect(response).to redirect_to(root_path) # Adjust the path based on your actual redirect path
+      expect(subject.current_user).to be_nil
+    end
+  end
+end
diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb
index da99a8ef8..efad75a26 100644
--- a/spec/controllers/users/sessions_controller_spec.rb
+++ b/spec/controllers/users/sessions_controller_spec.rb
@@ -1,46 +1,47 @@
 require 'rails_helper'
 
-RSpec.describe Users::SessionsController, type: :controller do
+RSpec.describe Users::OmniauthCallbacksController, type: :controller do
   before do
     request.env['devise.mapping'] = Devise.mappings[:user]
-    post :create, params: { user: { email: user.email, password: user.password } }
+    get :openid_connect, params: { 'code' => 'mock_code' }
   end
 
-  let(:user) { create :user, :registered }
+  let(:auth_service) { instance_double(GovOneAuthService) }
+  let(:access_token) { 'mock_access_token' }
+  let(:id_token) { 'mock_id_token' }
+  let(:email) { 'test@example.com' }
 
-  context 'when registration is complete' do
-    it 'continues to their training overview' do
-      expect(response).to redirect_to '/my-modules'
-    end
+  before do
+    allow(GovOneAuthService).to receive(:new).and_return(auth_service)
+    allow(auth_service).to receive(:tokens).and_return('access_token' => access_token, 'id_token' => id_token)
+    allow(auth_service).to receive(:user_info).with(access_token).and_return('email' => email)
   end
 
-  context 'when new feature alert is enabled' do
-    let(:user) do
-      create :user, :registered, display_whats_new: true
-    end
+  context 'when user is found or created' do
+    let(:user) { create(:user, email: email) }
 
-    it 'redirects to /whats-new' do
-      expect(response).to redirect_to '/whats-new'
+    before do
+      allow(controller).to receive(:find_or_create_user).and_return(user)
     end
-  end
 
-  context 'when email preferences are not defined' do
-    let(:user) do
-      create :user, :registered, training_emails: nil
+    it 'sets session id_token' do
+      expect(session[:id_token]).to eq(id_token)
     end
 
-    it 'redirects to email preferences form' do
-      expect(response).to redirect_to '/email-preferences'
+    it 'signs in the user and redirects to root path' do
+      expect(response).to redirect_to(root_path)
+      expect(subject.current_user).to eq(user)
     end
   end
 
-  context 'when registered during private beta' do
-    let(:user) do
-      create :user, :registered, registration_complete: false, private_beta_registration_complete: true
+  context 'when user is not found or created' do
+    before do
+      allow(controller).to receive(:find_or_create_user).and_return(nil)
     end
 
-    it 'redirects to new registration' do
-      expect(response).to redirect_to '/new-registration'
+    it 'does not sign in and redirects to root path' do
+      expect(response).to redirect_to(sign_in_path)
+      expect(subject.current_user).to be_nil
     end
   end
 end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 26f221ef7..da2af60c4 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -69,4 +69,6 @@
 
   # enable ShowMeTheCookies
   config.include ShowMeTheCookies, type: :system
+
+  config.include Devise::Test::ControllerHelpers, type: :controller
 end
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
new file mode 100644
index 000000000..79a6af1d8
--- /dev/null
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -0,0 +1,52 @@
+require 'rails_helper'
+
+RSpec.describe GovOneAuthService, type: :service do
+  let(:code) { 'mock_code' }
+  let(:access_token) { 'mock_access_token' }
+  let(:id_token) { 'mock_id_token' }
+  let(:user_info) { { 'email' => 'test@example.com' } }
+
+  before do
+    allow(ENV).to receive(:[]).with('GOV_ONE_REDIRECT_URI').and_return('http://example.com/callback')
+    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION_TYPE').and_return('urn:ietf:params:oauth:client-assertion-type:jwt-bearer')
+    allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('http://gov-one-example.com')
+    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('your_client_id')
+
+    allow(File).to receive(:read).and_call_original
+    allow(File).to receive(:read).with('public_key.pem').and_return('mock_public_key_content')
+    allow(File).to receive(:read).with('private_key.pem').and_return('mock_private_key_content')
+
+    allow(SecureRandom).to receive(:uuid).and_return('mock_uuid')
+
+    @auth_service = described_class.new(code)
+  end
+
+  describe '#tokens' do
+    it 'returns a hash with access_token and id_token' do
+      allow(@auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
+
+      token_response = { 'access_token' => access_token, 'id_token' => id_token }
+
+      http_double = instance_double(Net::HTTP, request: instance_double(Net::HTTPResponse, body: token_response.to_json))
+      allow(@auth_service).to receive(:build_http).and_return(http_double)
+
+      result = @auth_service.tokens
+
+      expect(result).to be_a(Hash)
+      expect(result['access_token']).to eq(access_token)
+      expect(result['id_token']).to eq(id_token)
+    end
+  end
+
+  describe '#user_info' do
+    it 'returns user information hash' do
+      http_double = instance_double(Net::HTTP, request: instance_double(Net::HTTPResponse, body: user_info.to_json))
+      allow(@auth_service).to receive(:build_http).and_return(http_double)
+
+      result = @auth_service.user_info(access_token)
+
+      expect(result).to be_a(Hash)
+      expect(result['email']).to eq('test@example.com')
+    end
+  end
+end

From 1bb01925970611b05256df02439f151b715592a7 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 24 Oct 2023 13:22:12 +0100
Subject: [PATCH 002/122] add option for gov one login

---
 Gemfile.lock                                  |  8 ++
 .../users/omniauth_callbacks_controller.rb    | 30 +++++-
 app/models/user.rb                            |  2 +-
 app/services/gov_one_auth_service.rb          | 13 ++-
 app/views/home/index.html.slim                |  5 +
 config/application.rb                         |  3 +
 config/credentials/development.yml.enc        |  2 +-
 config/credentials/production.yml.enc         |  2 +-
 config/credentials/test.yml.enc               |  2 +-
 config/initializers/devise.rb                 |  1 -
 .../omniauth_callbacks_controller_spec.rb     | 48 +++++-----
 .../users/sessions_controller_spec.rb         | 49 +++++-----
 spec/services/gov_one_auth_service_spec.rb    | 93 ++++++++++---------
 13 files changed, 150 insertions(+), 108 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index e3f0b4985..c7e8547b8 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -122,6 +122,8 @@ GEM
       contentful_model (~> 1.0)
       rails (>= 4.2)
       redcarpet (~> 3.2)
+    crack (0.4.5)
+      rexml
     crass (1.0.6)
     cssbundling-rails (1.3.3)
       railties (>= 6.0.0)
@@ -284,6 +286,7 @@ GEM
     grover (1.1.5)
       combine_pdf (~> 1.0)
       nokogiri (~> 1.0)
+    hashdiff (1.0.1)
     hashie (5.0.0)
     html-attributes-utils (1.0.2)
       activesupport (>= 6.1.4.4)
@@ -843,6 +846,10 @@ GEM
       activesupport
       faraday (~> 2.0)
       faraday-follow_redirects
+    webmock (3.19.1)
+      addressable (>= 2.8.0)
+      crack (>= 0.3.2)
+      hashdiff (>= 0.4.0, < 2.0.0)
     webrick (1.8.1)
     websocket (1.2.10)
     websocket-driver (0.7.6)
@@ -929,6 +936,7 @@ DEPENDENCIES
   turbo-rails
   tzinfo-data
   web-console
+  webmock
   yard-junk
 
 RUBY VERSION
diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 893603078..7214ae173 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -4,12 +4,20 @@ def openid_connect
 
     auth_service = GovOneAuthService.new(code)
     tokens_response = auth_service.tokens
+    if tokens_response.empty? || tokens_response['access_token'].blank? || tokens_response['id_token'].blank?
+      flash[:alert] = 'There was a problem signing in. Please try again.'
+      redirect_to root_path and return
+    end
+
     access_token = tokens_response['access_token']
     session[:id_token] = tokens_response['id_token']
 
     user_info_response = auth_service.user_info(access_token)
+    if user_info_response.empty? || user_info_response['email'].blank?
+      flash[:alert] = 'There was a problem signing in. Please try again.'
+      redirect_to root_path and return
+    end
     @email = user_info_response['email']
-
     gov_user = find_or_create_user
     sign_in_and_redirect gov_user if gov_user
   end
@@ -18,6 +26,24 @@ def openid_connect
 
   # @return [User]
   def find_or_create_user
-    User.find_by(email: @email) || create_from_email(@email)
+    User.find_by(email: @email) || User.create_from_email(@email)
+  end
+
+  def after_sign_in_path_for(resource)
+    if resource.registration_complete?
+      if resource.display_whats_new?
+        resource.display_whats_new = false
+        resource.save!
+        static_path('whats-new')
+      elsif !resource.email_preferences_complete?
+        static_path('email-preferences')
+      else
+        my_modules_path
+      end
+    elsif resource.private_beta_registration_complete?
+      static_path('new-registration')
+    else
+      edit_registration_name_path
+    end
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 2cbec1b77..03dfc932f 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -24,7 +24,7 @@ def self.dashboard_headers
   # @return [User]
   def self.create_from_email(email)
     user = User.new(email: email, confirmed_at: Time.zone.now)
-    user.save(validate: false)
+    user.save!(validate: false)
     user
   end
 
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index ff86c9765..79a1c733a 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -22,6 +22,9 @@ def tokens
     token_response = http.request(token_request)
 
     JSON.parse(token_response.body)
+  rescue StandardError => e
+    Rails.logger.error "GovOneAuthService.tokens: #{e.message}"
+    {}
   end
 
   # @param access_token [String]
@@ -34,6 +37,9 @@ def user_info(access_token)
     userinfo_response = http.request(userinfo_request)
 
     JSON.parse(userinfo_response.body)
+  rescue StandardError => e
+    Rails.logger.error "GovOneAuthService.user_info: #{e.message}"
+    {}
   end
 
 private
@@ -48,16 +54,15 @@ def build_http(uri)
 
   # @return [String]
   def jwt_assertion
-    rsa_public = OpenSSL::PKey::RSA.new(File.read('public_key.pem'))
-    rsa_private = OpenSSL::PKey::RSA.new(File.read('private_key.pem'))
+    rsa_private = OpenSSL::PKey::RSA.new(Rails.application.config.gov_one_private_key)
 
     payload = {
       aud: "#{ENV['GOV_ONE_BASE_URI']}/token",
       iss: ENV['GOV_ONE_CLIENT_ID'],
       sub: ENV['GOV_ONE_CLIENT_ID'],
-      exp: Time.now.to_i + 5 * 60,
+      exp: Time.zone.now.to_i + 5 * 60,
       jti: SecureRandom.uuid,
-      iat: Time.now.to_i,
+      iat: Time.zone.now.to_i,
     }
 
     JWT.encode payload, rsa_private, 'RS256'
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index b9a694e94..e222c47db 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -37,4 +37,9 @@
         = content_resource 'home.login'
 
         .govuk-button-group
+        .govuk-button-group
+          = govuk_button_link_to 'Sign in', new_user_session_path
+          .white-space-pre-wrap= ' or '
+          = govuk_link_to 'create an account', new_user_registration_path
+          .white-space-pre-wrap= ' or '
           = govuk_button_link_to 'Sign in with Gov One Login', login_uri
diff --git a/config/application.rb b/config/application.rb
index eb6040602..7e88832b9 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -67,6 +67,9 @@ class Application < Rails::Application
     config.contentful_management_access_token = ENV.fetch('CONTENTFUL_MANAGEMENT_TOKEN', credentials.dig(:contentful, :management_access_token)) # TODO: use service account management token
     config.contentful_environment             = ENV.fetch('CONTENTFUL_ENVIRONMENT', credentials.dig(:contentful, :environment))
 
+    # Gov one
+    config.gov_one_private_key = ENV.fetch('GOV_ONE_PRIVATE_KEY', credentials.dig(:gov_one, :private_key))
+
     # @return [Boolean]
     def live?
       ENV['WORKSPACE'].eql?('production')
diff --git a/config/credentials/development.yml.enc b/config/credentials/development.yml.enc
index 4fdfcc7f6..b4fe3c9ae 100644
--- a/config/credentials/development.yml.enc
+++ b/config/credentials/development.yml.enc
@@ -1 +1 @@
-vf7Vk4G4BoOFvXegN/ZA89CRAWeAUYToC8DhefugaS6aMoe2LNJIzdteiCKGPlQ78BfoEwxuXzW6hZabBhDfXS1TGEwV9RcIn3DcKE29bIh4A8RcLefvbm/qGTjSsJHcnIfeoh08xEqY6b0hKSyX9iPwInNSFujMgwbk4Tcj+pcWdTZ7myxbr9MZ20o5dHAUCZ1HWSLuYG8gRtwcsTL5MWCPtUMLdS2K+b3PEbfbLka+2xUnOcw4e8ailj3CDul6I824m4GJoKzNvYcDXwRZRG0LViXr4Nq7W8cRos9BH1rBA0oizEHgwHTNwpi3hdUMtmeyKaZ5fhU3X1JDCNN1T3nFFae1jHmDA2UfpFv8s4mFqvcq4lJvFnzsTsNR3oa+3/ix7222HRFPqPjtuM+KSMCo8+fGqX6RfoChGW+YrmVmDJs6DMlb0sNkkBk5CER0wuUSD2t92TDQG4mdWP0wePW5eWD71piAc98x6hc8d55H+HgWzt3wJlkV7HXYqIvISed3RKqhXWVva+c/VQA74Dd8+o0uAAWX3be3eWf0MtLNJyxp1zAG7RGF7jZ3zMp/76khw+WuTxNoFmO2qv5XwJC4AzBgaCksFrwBcjHvx+RkQ8cVjJIDk5piZYLVik5Ng7dsNdUdovj/TIot2OOn2+EzQZb+VBe48j3L039FN13CyBR8592tHRNK3mKiJz0izKiKN4v2h5kLw8nl1j0GuNaUvzsNy2OglSHGkfNR+HT6DCbmZTbc/JWziqf1VUeOdFc7/iZiLY5w0Vp4+QCQ/JQWBAPJzyLaSUdAv68Jh9xeU20YJbl97yQ3R13kVA4iRgD4x5+5sszmAvb8TmmACkBHjTShGJjsl7/R0mK/bdIlHntdgyTYmkq8F8wbHrX6wtN2t5Wdi40UelnNdATfKOaT+LkH+Z7rZK0ELJw570udk5vGnIv7vtBPDzmrY2i1u8IyNpyAfR6usgzuFTjKIO/R8g+Rhz7eQP9uSzqcs8Q7HswoHVZfzt2s5NrjkUVeTp+muYJ+n+q3Fh3rhpSDrSFA4HFCVpazM21inhF5YG/rT+1YRVdPs+sQLYTgyqtKsNqivtmKuE6I6ij7yvcwhhesMlxMVcBxNouWyvExGby711lVv8JdRZRYv16/5CKjAAHjs1k/KEwznqi7TfiNuZXzqOXY2WNBKFcPXEKbEuxcS34GXAcXmhozU+UjYJpW9XU4ZGAlMq7bZMKnGhEVWTWWi76USHSae5vSyeGNTj2NLcFeODmk+ELuNmqaajzi7t1oif+B3NPvN9+Ej2jdlj8Dyhu0Trl2lXvntEtROjbAe0+0F+Ukfyqn1QUCrzenrE19hDbio0A0JJXD6em0DAqseTV23KF3BRyQI3uqiSQNyuvHDaWP6B4xVO9JXPGXaJlz1LdqDgbdKYVmQZD4pomEUadAnk4JuRh3k8CVK6ZZltobIOFpfq8nBQlMrn/Gq52Hdo1tfhCAUD9U8Z8lMV9ubbQiu5rljVSHj7TIrHoFF3Nat8HdgPhj9N4MyguNyxuUwyXCpxupbcJ/oeaD7ZT6nLjR9s3d3WTQDkpLFfT/G4eUYWz3pDb5T3RPrmktaLL+JARiJ53XCreSQjjT9BSMLP3oXTHJyeXCsFQFt9uRNWFCvVH52DUAAw9dMqXOfTqOkVm1dSM4de1deauyR3589faAyIqryDRr1WRjVMsnZz41bHU6ZCeQ1vKrR47BjXJv5ClZlmXyupVHPwG+Kbj0aJ6o4ZcSZYIUGPYBT1bSu67nGd4N2v6EK3xzKMn0FaMFPDNO+Fdi9RJlPqycPfrAsoIBriiGzC1rHy88SGXg3jZnBWbApMatZYd1xpxYO9HuOBNy1XeUn0fYbV0qzFTvohhO3T6uKkXLKB2bBZYQWtk5q8Wr/tN1cIicCUKEqRDQgu+3cTu6k7OMTkR+HZkKIXZW/fN/ou5c9rbeaqLQgwX9az0SFEUzzDKBPVXp0yVc47MihJj+vJ3OcuILFNk8FKqAW/YKjZpm57VSru1TgTL4rb3kJwoXsVypxUz6efaZFqSSVnQTPRgIH58WENXQ3WIUXiARFzsI/cifH1zYIw6GeH7wLaXpfBWxh7Bciq8+nDx5kh/7DPwvHsPWih+3kGMeG1uie8mBUydeH+97LfJdKqV1RDCrQePTizZThczFpf0NQo5syY3ZWlbLe6jFx+OI1eJXKGZrz4EuozjALr7a/2WMOsFoVbZ/GJLbDmnrAmVSiJVAqFDrRoj207eLVbvnhkrQuA/wcf0RSBNZ4MrBw8Tq5G5mXtbLn5dzHHabwMBY/wblJW35j4CpT6r8jgZKD26o5avJdcUg1qbK3EG3NyHafzRWoOHZwGInrAwWmLvA6q/pautuEFenlzKW2Ue6rYEzDM55UrMTWN5+Blip0wOP8ZmOHDyG35mnwyCVjg035dD28gRGFONEapDvyrR6WOsMzT098tyIr/N0ZIeMSj8AimL6glxKT3c5qF5JDMFGj4lMkz8g5wforoAkqnvApFwZLi6mQI1uhkPjh5Y2vWvDgL/064V4zX0JIWBOBk9tYemgye+iAacy/dgZTeCq02amFm1EJClFj6eoQ7wp/uubi1J5eURa81S3hgoQ6vBWtXWqgZa9ZP0hoSqPRawm5LrJHtPCxM5JXksZCIFqurA5WJr9RkbDxfkIpHE2/AEYjmMv6XGWGuP3NjRC08ZRQ8tRFk+mCanoLwMoXTWYyMK9XlvhaoxIiKgkvpgeHvVYobZl7Dd2r49zSJChB6/nIcZd5WG+C0yzMPzEHSyniUdvmPctxbe6rmg2a6BmVmqAw2q24RLWSEwzPbo0XtE5QkG0OW0jkONhz0oL0qdAniq6oXBqdQ6obeMxfNDLDGNjC3ziYiHOANIMHrx+u56vUBpNgFklfIrqOqPFeL8XuqbjGieLyuUHlSv06UyA09Lf3aqk6fDcxPfuAjVkgbRDdrxmpiKVbsO4YB18A+MZkDDTio8nQcGUvXaoWUnflJ8tYHIVmBkWImkZ2PXi5gtk6UJWdtvAKuorlCqbLQbMpCJwC5a3Ua+Dh3hOoPPqvQWfHEzyvAh0iVW2hYQcqDBg55S+c1bIKR5XzzYa164ZqkvJ1KeqSa0jEJ4wUTkP3aR2t9Zzxi5cS4w2m82dywAfoOF/jkzzffvU1+Duq6Yo+8tSiGdGpX8QB1wuLRzt8AmXJ+cHzgbVu5k8oIE4YCo7s98ZJEo6FNrFm7i0t4N18eHgg6Kmupj78v902KZPMmZ3YxSIHmr2TNMaswSzYyqIfyb5jRe7eL+e1snNV97gXIzxKEHMXUQX/65PY/bknCK/0s3sanDBLJ7d9YggOYDF+Qa/gOq311t1oMBkNLNGJKLstgcpUV26kgJ3ASytTprMm1Jagb0Io09goOifL6mN/lpUSTpe69qo/xBbZxIQRKY1UDTgLKslK0uDkHaLyAI2fjZp0nSclyBQYcpd6Opy2H3j7RjAlSccvKzpYYez9LBLNJ0wu22C/J525nDPItZhJ5X4QFdZXpCjIwD69YFimnb9RBNmHdA/jxo/bgL0Tex6M96MMcv9op5aeF3MOq7rLrufBqdjI17SV7adih6N2ifZVooyYxOvmH7V6W4HK0SSUPiWNeXO1E/Jm6spAeKql/LuRMkIzRSziJylRNQMPAhwhlHMu0FuPZ6T8baOv8e4fRrQJ+JslsptvnjKsmsnQZx8DDG7fbtHCH3dXXAPlw0HU9l57PZ1VHOulQo2kFvUSK4VyNTCPDm+wCWMKIwGpgmtRfhm0Rg8giNW0LGhuzUX0M34b3dc30x8lLmTBVsYm3YE6NgJH3Pg+wmYZt2yQSapw+bGl5if+Qr3VS6YFKoJWG9e3pwENcBc78kiCk5/LrAVa3U1o6MOmZD3S9+X7Xe7I37Gc5sVeLopf3m9pMpS3QdmFTF3xtRbY4a/RfgeASBy9mlyhNcixRo949Q+dhNPwe559ZdL1wyOE9Vl6k7qJPOPoq48EKYOGgBzdQBxZs9rM7/mtS2+1ImaYfAB7SjinDDNxA80DMrJmmrmAkVdvwaMzt39zgPNMaOdkJ+LdMnxJFSnIAlXnyT3UgRYUZHFpVPy26Vsfdq61P8x0OmDnQaV6Xpz8vjhf13qoes6MeB4N/6hteJzZZ6GrKV37KT1pOg/oAelTpzwa0h9hsFrl+fplDxxAOQbJj6DBSsDj4Iz+RKaQXRalZ4Xq5fD89l1UQlvxNJWmQ==--U4dwbZHHF88MfY4i--tryRPvAAHYaJFB+11sqGZQ==
\ No newline at end of file
+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--bjV860iSph86biSM--eO2U1UjfoPVUyTzhWcIu9A==
\ No newline at end of file
diff --git a/config/credentials/production.yml.enc b/config/credentials/production.yml.enc
index dbc4d4155..a95dc679b 100644
--- a/config/credentials/production.yml.enc
+++ b/config/credentials/production.yml.enc
@@ -1 +1 @@
-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--xd1sesNNvCLgfxxi--Uw8M7itth9vCF+0j3D+j0w==
\ No newline at end of file
+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--o03/wyKMqS37wmEr--PGWkhEX04SURN12/rZeEDQ==
\ No newline at end of file
diff --git a/config/credentials/test.yml.enc b/config/credentials/test.yml.enc
index 5ee9a3078..9f2b7b14a 100644
--- a/config/credentials/test.yml.enc
+++ b/config/credentials/test.yml.enc
@@ -1 +1 @@
-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--524FMqbKrVmJ2sZ3--G4/cI/16dU0QC5Cp94Fn7g==
\ No newline at end of file
+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--F7LPrF7SEIYJasNO--J/H0MGGLiOWbI22U2DGy8g==
\ No newline at end of file
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index cfbf68e79..c50ca7eb3 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -315,7 +315,6 @@ def i18n_message(default = nil)
       scheme: 'https',
       host: 'oidc.integration.account.gov.uk',
       identifier: ENV['GOV_ONE_CLIENT_ID'],
-      secret: ENV['GOV_ONE_CLIENT_SECRET'],
       redirect_uri: 'users/auth/openid_connect/callback',
     },
     authorize_params: {
diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index 85875a3a9..2d768fef8 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -1,10 +1,10 @@
 require 'rails_helper'
 
 RSpec.describe Users::OmniauthCallbacksController, type: :controller do
-
   before do
-    @request.env['devise.mapping'] = Devise.mappings[:user]
+    request.env['devise.mapping'] = Devise.mappings[:user]
   end
+
   describe '#openid_connect' do
     let(:auth_service) { instance_double(GovOneAuthService) }
     let(:access_token) { 'mock_access_token' }
@@ -13,36 +13,30 @@
 
     before do
       allow(GovOneAuthService).to receive(:new).and_return(auth_service)
-      allow(auth_service).to receive(:tokens).and_return('access_token' => access_token, 'id_token' => id_token)
-      allow(auth_service).to receive(:user_info).with(access_token).and_return('email' => email)
+      allow(auth_service).to receive(:tokens).and_return({ 'access_token' => access_token, 'id_token' => id_token })
+      allow(auth_service).to receive(:user_info).and_return({ 'email' => email })
+      allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
     end
 
-    it 'sets session id_token and finds or creates the user' do
-      get :openid_connect, params: { 'code' => 'mock_code' }
-
-      expect(session[:id_token]).to eq(id_token)
-      expect(assigns(:email)).to eq(email)
-      expect(response).to redirect_to(root_path)
+    context 'when a User does not exist' do
+      it 'sets session id_token and creates the user' do
+        get :openid_connect, params: { 'code' => 'mock_code' }
+        expect(session[:id_token]).to eq(id_token)
+        expect(User.find_by(email: email)).to be_truthy
+        expect(response).to redirect_to(edit_registration_name_path)
+      end
     end
 
-    it 'signs in and redirects the user if found or created' do
-      user = create(:user, email: email) # Assuming you have a User model and FactoryBot
-
-      allow(controller).to receive(:find_or_create_user).and_return(user)
-
-      get :openid_connect, params: { 'code' => 'mock_code' }
-
-      expect(response).to redirect_to(root_path) # Adjust the path based on your actual redirect path
-      expect(subject.current_user).to eq(user)
-    end
-
-    it 'does not sign in if user is not found or created' do
-      allow(controller).to receive(:find_or_create_user).and_return(nil)
-
-      get :openid_connect, params: { 'code' => 'mock_code' }
+    context 'when a User exists' do
+      before do
+        create :user, :registered, email: email
+      end
 
-      expect(response).to redirect_to(root_path) # Adjust the path based on your actual redirect path
-      expect(subject.current_user).to be_nil
+      it 'sets session id_token and signs in the user' do
+        get :openid_connect, params: { 'code' => 'mock_code' }
+        expect(session[:id_token]).to eq(id_token)
+        expect(response).to redirect_to(my_modules_path)
+      end
     end
   end
 end
diff --git a/spec/controllers/users/sessions_controller_spec.rb b/spec/controllers/users/sessions_controller_spec.rb
index efad75a26..da99a8ef8 100644
--- a/spec/controllers/users/sessions_controller_spec.rb
+++ b/spec/controllers/users/sessions_controller_spec.rb
@@ -1,47 +1,46 @@
 require 'rails_helper'
 
-RSpec.describe Users::OmniauthCallbacksController, type: :controller do
+RSpec.describe Users::SessionsController, type: :controller do
   before do
     request.env['devise.mapping'] = Devise.mappings[:user]
-    get :openid_connect, params: { 'code' => 'mock_code' }
+    post :create, params: { user: { email: user.email, password: user.password } }
   end
 
-  let(:auth_service) { instance_double(GovOneAuthService) }
-  let(:access_token) { 'mock_access_token' }
-  let(:id_token) { 'mock_id_token' }
-  let(:email) { 'test@example.com' }
+  let(:user) { create :user, :registered }
 
-  before do
-    allow(GovOneAuthService).to receive(:new).and_return(auth_service)
-    allow(auth_service).to receive(:tokens).and_return('access_token' => access_token, 'id_token' => id_token)
-    allow(auth_service).to receive(:user_info).with(access_token).and_return('email' => email)
+  context 'when registration is complete' do
+    it 'continues to their training overview' do
+      expect(response).to redirect_to '/my-modules'
+    end
   end
 
-  context 'when user is found or created' do
-    let(:user) { create(:user, email: email) }
+  context 'when new feature alert is enabled' do
+    let(:user) do
+      create :user, :registered, display_whats_new: true
+    end
 
-    before do
-      allow(controller).to receive(:find_or_create_user).and_return(user)
+    it 'redirects to /whats-new' do
+      expect(response).to redirect_to '/whats-new'
     end
+  end
 
-    it 'sets session id_token' do
-      expect(session[:id_token]).to eq(id_token)
+  context 'when email preferences are not defined' do
+    let(:user) do
+      create :user, :registered, training_emails: nil
     end
 
-    it 'signs in the user and redirects to root path' do
-      expect(response).to redirect_to(root_path)
-      expect(subject.current_user).to eq(user)
+    it 'redirects to email preferences form' do
+      expect(response).to redirect_to '/email-preferences'
     end
   end
 
-  context 'when user is not found or created' do
-    before do
-      allow(controller).to receive(:find_or_create_user).and_return(nil)
+  context 'when registered during private beta' do
+    let(:user) do
+      create :user, :registered, registration_complete: false, private_beta_registration_complete: true
     end
 
-    it 'does not sign in and redirects to root path' do
-      expect(response).to redirect_to(sign_in_path)
-      expect(subject.current_user).to be_nil
+    it 'redirects to new registration' do
+      expect(response).to redirect_to '/new-registration'
     end
   end
 end
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index 79a6af1d8..0b978ee59 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -1,52 +1,55 @@
 require 'rails_helper'
-
-RSpec.describe GovOneAuthService, type: :service do
-  let(:code) { 'mock_code' }
-  let(:access_token) { 'mock_access_token' }
-  let(:id_token) { 'mock_id_token' }
-  let(:user_info) { { 'email' => 'test@example.com' } }
-
-  before do
-    allow(ENV).to receive(:[]).with('GOV_ONE_REDIRECT_URI').and_return('http://example.com/callback')
-    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION_TYPE').and_return('urn:ietf:params:oauth:client-assertion-type:jwt-bearer')
-    allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('http://gov-one-example.com')
-    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('your_client_id')
-
-    allow(File).to receive(:read).and_call_original
-    allow(File).to receive(:read).with('public_key.pem').and_return('mock_public_key_content')
-    allow(File).to receive(:read).with('private_key.pem').and_return('mock_private_key_content')
-
-    allow(SecureRandom).to receive(:uuid).and_return('mock_uuid')
-
-    @auth_service = described_class.new(code)
-  end
-
-  describe '#tokens' do
-    it 'returns a hash with access_token and id_token' do
-      allow(@auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
-
-      token_response = { 'access_token' => access_token, 'id_token' => id_token }
-
-      http_double = instance_double(Net::HTTP, request: instance_double(Net::HTTPResponse, body: token_response.to_json))
-      allow(@auth_service).to receive(:build_http).and_return(http_double)
-
-      result = @auth_service.tokens
-
-      expect(result).to be_a(Hash)
-      expect(result['access_token']).to eq(access_token)
-      expect(result['id_token']).to eq(id_token)
+# require 'webmock/rspec'
+
+RSpec.describe GovOneAuthService do
+  skip 'wip' do
+    let(:code) { 'mock_code' }
+    let(:token_payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
+
+    before do
+      allow(ENV).to receive(:[]).and_call_original
+      allow(ENV).to receive(:[]).with('GOV_ONE_REDIRECT_URI').and_return('mock_redirect_uri')
+      allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION_TYPE').and_return('mock_assertion_type')
+      allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('https://example.com')
+      allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('mock_client_id')
     end
-  end
-
-  describe '#user_info' do
-    it 'returns user information hash' do
-      http_double = instance_double(Net::HTTP, request: instance_double(Net::HTTPResponse, body: user_info.to_json))
-      allow(@auth_service).to receive(:build_http).and_return(http_double)
 
-      result = @auth_service.user_info(access_token)
+    describe '#tokens' do
+      it 'returns a hash of tokens' do
+        stub_request(:post, 'https://example.com/token')
+          .with(
+            body: {
+              grant_type: 'authorization_code',
+              code: code,
+              redirect_uri: 'mock_redirect_uri',
+              client_assertion_type: 'mock_assertion_type',
+              client_assertion: anything,
+            },
+            headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
+          )
+          .to_return(status: 200, body: token_payload.to_json)
+
+        auth_service = described_class.new(code)
+        tokens = auth_service.tokens
+        expect(tokens).to eq(token_payload)
+      end
+    end
 
-      expect(result).to be_a(Hash)
-      expect(result['email']).to eq('test@example.com')
+    describe '#user_info' do
+      let(:access_token) { 'mock_access_token' }
+      let(:user_info_payload) { { 'email' => 'test@test.com' } }
+
+      it 'returns a hash of user info' do
+        stub_request(:get, 'https://example.com/userinfo')
+          .with(
+            headers: { 'Authorization' => "Bearer #{access_token}" },
+          )
+          .to_return(status: 200, body: user_info_payload.to_json)
+
+        auth_service = described_class.new(code)
+        user_info = auth_service.user_info(access_token)
+        expect(user_info).to eq(user_info_payload)
+      end
     end
   end
 end

From b2b8263e0aface01c39b0dd0a04987fbbf967252 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 24 Oct 2023 13:28:20 +0100
Subject: [PATCH 003/122] remove existing sign in options from home page

---
 app/views/home/index.html.slim | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index 8850cb22f..73b8d82ca 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -38,8 +38,4 @@
 
         .govuk-button-group
         .govuk-button-group
-          = govuk_button_link_to 'Sign in', new_user_session_path
-          .white-space-pre-wrap= ' or '
-          = govuk_link_to 'create an account', new_user_registration_path
-          .white-space-pre-wrap= ' or '
           = govuk_button_link_to 'Sign in with Gov One Login', login_uri

From 1c0fb290ae6e35674acdc7b8f9cf53ffe2984b3c Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 27 Oct 2023 10:47:10 +0100
Subject: [PATCH 004/122] wip

---
 app/services/gov_one_auth_service.rb       |  2 +-
 spec/services/gov_one_auth_service_spec.rb | 65 ++++++++++++----------
 2 files changed, 37 insertions(+), 30 deletions(-)

diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 79a1c733a..8e6362e74 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -11,7 +11,7 @@ def tokens
       code: @code,
       redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
       client_assertion_type: ENV['GOV_ONE_CLIENT_ASSERTION_TYPE'],
-      client_assertion: jwt_assertion,
+      client_assertion: ENV['GOV_ONE_CLIENT_ASSERTION'] || jwt_assertion,
     }
 
     token_uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/token")
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index 0b978ee59..18d445fdb 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -1,8 +1,6 @@
 require 'rails_helper'
-# require 'webmock/rspec'
 
 RSpec.describe GovOneAuthService do
-  skip 'wip' do
     let(:code) { 'mock_code' }
     let(:token_payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
 
@@ -12,44 +10,53 @@
       allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION_TYPE').and_return('mock_assertion_type')
       allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('https://example.com')
       allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('mock_client_id')
+      allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION').and_return('mock_client_assertion')
     end
 
     describe '#tokens' do
       it 'returns a hash of tokens' do
-        stub_request(:post, 'https://example.com/token')
-          .with(
-            body: {
-              grant_type: 'authorization_code',
-              code: code,
-              redirect_uri: 'mock_redirect_uri',
-              client_assertion_type: 'mock_assertion_type',
-              client_assertion: anything,
-            },
-            headers: { 'Content-Type' => 'application/x-www-form-urlencoded' },
-          )
-          .to_return(status: 200, body: token_payload.to_json)
-
-        auth_service = described_class.new(code)
-        tokens = auth_service.tokens
-        expect(tokens).to eq(token_payload)
+        mock_response = double('response')
+        allow(mock_response).to receive(:body).and_return(token_payload.to_json)
+        allow(mock_response).to receive(:code).and_return(200)
+    
+        mock_net_http_post = double("Net::HTTP::Post")
+        allow(Net::HTTP).to receive(:new).and_return(mock_net_http_post)
+        allow(mock_net_http_post).to receive(:use_ssl=).with(true)
+    
+        allow(Net::HTTP::Post).to receive(:new).and_return(mock_net_http_post)
+
+        expect(mock_net_http_post).to receive(:set_form_data).with({
+          :client_assertion => "mock_client_assertion",
+          :client_assertion_type => "mock_assertion_type",
+          :code => "mock_code",
+          :grant_type => "authorization_code",
+          :redirect_uri => "mock_redirect_uri"
+        })
+        allow(mock_net_http_post).to receive(:request).and_return(mock_response)
+        expect(described_class.new(code).tokens).to eq(token_payload)
+
       end
     end
+    
 
     describe '#user_info' do
+      it 'returns a hash of user info' do
       let(:access_token) { 'mock_access_token' }
       let(:user_info_payload) { { 'email' => 'test@test.com' } }
+      mock_response = double('response')
+      allow(mock_response).to receive(:body).and_return(user_info_payload.to_json)
+      allow(mock_response).to receive(:code).and_return(200)
+
+      mock_net_http_get = double("Net::HTTP::Get")
+      allow(Net::HTTP).to receive(:new).and_return(mock_net_http_get)
+      allow(mock_net_http_get).to receive(:use_ssl=).with(true)
+
+      allow(Net::HTTP::Get).to receive(:new).and_return(mock_net_http_get)
+
+      allow(mock_net_http_get).to receive(:request).and_return(mock_response)
+      expect(described_class.new(code).user_info(access_token)).to eq(user_info_payload)
 
-      it 'returns a hash of user info' do
-        stub_request(:get, 'https://example.com/userinfo')
-          .with(
-            headers: { 'Authorization' => "Bearer #{access_token}" },
-          )
-          .to_return(status: 200, body: user_info_payload.to_json)
-
-        auth_service = described_class.new(code)
-        user_info = auth_service.user_info(access_token)
-        expect(user_info).to eq(user_info_payload)
-      end
     end
   end
+
 end

From a51cb32c7e0104f5f964fd5fbef04ff3b8511809 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 27 Oct 2023 15:52:40 +0100
Subject: [PATCH 005/122] update auth service specs

---
 app/services/gov_one_auth_service.rb       |  2 +-
 spec/services/gov_one_auth_service_spec.rb | 82 +++++++++++-----------
 2 files changed, 41 insertions(+), 43 deletions(-)

diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 8e6362e74..4e991aef7 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -48,7 +48,7 @@ def user_info(access_token)
   # @return [Net::HTTP]
   def build_http(uri)
     http = Net::HTTP.new(uri.host, uri.port)
-    http.use_ssl = true
+    http.use_ssl = true unless Rails.env.test?
     http
   end
 
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index 18d445fdb..deb011eb1 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -1,62 +1,60 @@
 require 'rails_helper'
 
 RSpec.describe GovOneAuthService do
-    let(:code) { 'mock_code' }
-    let(:token_payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
-
-    before do
-      allow(ENV).to receive(:[]).and_call_original
-      allow(ENV).to receive(:[]).with('GOV_ONE_REDIRECT_URI').and_return('mock_redirect_uri')
-      allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION_TYPE').and_return('mock_assertion_type')
-      allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('https://example.com')
-      allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('mock_client_id')
-      allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION').and_return('mock_client_assertion')
-    end
+  let(:code) { 'mock_code' }
+  let(:token_payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
+  let(:mock_response) { instance_double('response') }
+  let(:auth_service) { described_class.new(code) }
+
+  before do
+    allow(ENV).to receive(:[]).and_call_original
+    allow(ENV).to receive(:[]).with('GOV_ONE_REDIRECT_URI').and_return('mock_redirect_uri')
+    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION_TYPE').and_return('mock_assertion_type')
+    allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('https://example.com')
+    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('mock_client_id')
+    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION').and_return('mock_client_assertion')
+  end
 
-    describe '#tokens' do
+  describe '#tokens' do
+    let(:mock_net_http_post) { instance_double('Net::HTTP::Post', request: nil) }
+
+    context 'when the request is successful' do
       it 'returns a hash of tokens' do
-        mock_response = double('response')
         allow(mock_response).to receive(:body).and_return(token_payload.to_json)
         allow(mock_response).to receive(:code).and_return(200)
-    
-        mock_net_http_post = double("Net::HTTP::Post")
+        allow(mock_net_http_post).to receive(:request).and_return(mock_response)
         allow(Net::HTTP).to receive(:new).and_return(mock_net_http_post)
-        allow(mock_net_http_post).to receive(:use_ssl=).with(true)
-    
-        allow(Net::HTTP::Post).to receive(:new).and_return(mock_net_http_post)
+
+        result = auth_service.tokens
 
         expect(mock_net_http_post).to receive(:set_form_data).with({
-          :client_assertion => "mock_client_assertion",
-          :client_assertion_type => "mock_assertion_type",
-          :code => "mock_code",
-          :grant_type => "authorization_code",
-          :redirect_uri => "mock_redirect_uri"
+          client_assertion: 'mock_client_assertion',
+          client_assertion_type: 'mock_assertion_type',
+          code: 'mock_code',
+          grant_type: 'authorization_code',
+          redirect_uri: 'mock_redirect_uri',
         })
-        allow(mock_net_http_post).to receive(:request).and_return(mock_response)
-        expect(described_class.new(code).tokens).to eq(token_payload)
-
+        expect(result).to eq(token_payload)
       end
     end
-    
-
-    describe '#user_info' do
-      it 'returns a hash of user info' do
-      let(:access_token) { 'mock_access_token' }
-      let(:user_info_payload) { { 'email' => 'test@test.com' } }
-      mock_response = double('response')
-      allow(mock_response).to receive(:body).and_return(user_info_payload.to_json)
-      allow(mock_response).to receive(:code).and_return(200)
+  end
 
-      mock_net_http_get = double("Net::HTTP::Get")
-      allow(Net::HTTP).to receive(:new).and_return(mock_net_http_get)
-      allow(mock_net_http_get).to receive(:use_ssl=).with(true)
+  describe '#user_info' do
+    let(:access_token) { 'mock_access_token' }
+    let(:user_info_payload) { { 'email' => 'test@test.com' } }
+    let(:mock_net_http_get) { instance_double('Net::HTTP::Get', request: nil) }
 
-      allow(Net::HTTP::Get).to receive(:new).and_return(mock_net_http_get)
+    context 'when the request is successful' do
+      it 'returns a hash of user info' do
+        allow(mock_response).to receive(:body).and_return(user_info_payload.to_json)
+        allow(mock_response).to receive(:code).and_return(200)
+        allow(mock_net_http_get).to receive(:request).and_return(mock_response)
+        allow(Net::HTTP).to receive(:new).and_return(mock_net_http_get)
 
-      allow(mock_net_http_get).to receive(:request).and_return(mock_response)
-      expect(described_class.new(code).user_info(access_token)).to eq(user_info_payload)
+        result = auth_service.user_info(access_token)
 
+        expect(result).to eq(user_info_payload)
+      end
     end
   end
-
 end

From efd8bcf1de571459aea7660040c9d319336fa528 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 30 Oct 2023 11:53:20 +0000
Subject: [PATCH 006/122] remove user facing gov one login changes

---
 app/controllers/gov_one_controller.rb |  3 +++
 app/helpers/application_helper.rb     |  8 ++++----
 app/views/gov_one/info.html.slim      | 17 +++++++++++++++++
 app/views/home/index.html.slim        |  5 +++--
 config/locales/en.yml                 |  5 +++++
 config/routes.rb                      |  1 +
 6 files changed, 33 insertions(+), 6 deletions(-)
 create mode 100644 app/controllers/gov_one_controller.rb
 create mode 100644 app/views/gov_one/info.html.slim

diff --git a/app/controllers/gov_one_controller.rb b/app/controllers/gov_one_controller.rb
new file mode 100644
index 000000000..8c4f49d46
--- /dev/null
+++ b/app/controllers/gov_one_controller.rb
@@ -0,0 +1,3 @@
+class GovOneController < ApplicationController
+  def info; end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8f29d0dbb..e25200506 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -11,14 +11,14 @@ def navigation
       header.with_navigation_item(text: 'Home', href: root_path, classes: %w[dfe-header__navigation-item])
       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: logout_uri, options: { id: 'sign-out-desktop', data: { turbo_method: :get }, 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: logout_uri, 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: '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' })
       else
-        header.with_action_link(text: 'Sign in', href: login_uri, options: { inverse: true })
-        header.with_navigation_item(text: 'Sign in', href: login_uri, classes: ['dfe-header__navigation-item dfe-header-f-mob'])
+        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'])
       end
     end
   end
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
new file mode 100644
index 000000000..611b35e4f
--- /dev/null
+++ b/app/views/gov_one/info.html.slim
@@ -0,0 +1,17 @@
+= render 'learning/cms_debug'
+
+- content_for :page_title do
+  = html_title t('gov-one.info.title')
+
+.h1.govuk-heading-xl
+  = t('gov-one.info.title')
+
+.govuk-grid-row
+  .govuk-grid-column-one-half
+    = m('gov-one-info.body')
+
+    - if current_user
+      = govuk_button_link_to 'Sign out of Gov One Login', logout_uri
+    - else
+      = govuk_button_link_to 'Sign in with Gov One Login', login_uri
+  
\ No newline at end of file
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index 73b8d82ca..b389a981e 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -37,5 +37,6 @@
         = m('home.login')
 
         .govuk-button-group
-        .govuk-button-group
-          = govuk_button_link_to 'Sign in with Gov One Login', login_uri
+          = govuk_button_link_to 'Sign in', new_user_session_path
+          .white-space-pre-wrap= ' or '
+          = govuk_link_to 'create an account', new_user_registration_path
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 033df49ec..d8c8028b0 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -562,6 +562,11 @@ en:
 
       %{criteria}
 
+  # /gov-one/info
+  gov-one:
+    info:
+      title: Gov One Login
+
   # /settings/cookie-policy
   cookie_policy:
     title: Cookie policy
diff --git a/config/routes.rb b/config/routes.rb
index d1b01e2e5..9de3c4b5c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -5,6 +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 '/404', to: 'errors#not_found', via: :all
   get '/422', to: 'errors#unprocessable_entity', via: :all

From 2c66f3673e4c702a2739b02b32ee0d1aee943a8e Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 30 Oct 2023 15:25:06 +0000
Subject: [PATCH 007/122] update auth service specs

---
 spec/services/gov_one_auth_service_spec.rb | 40 ++++++++++++----------
 1 file changed, 22 insertions(+), 18 deletions(-)

diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index deb011eb1..175844e26 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -2,9 +2,9 @@
 
 RSpec.describe GovOneAuthService do
   let(:code) { 'mock_code' }
-  let(:token_payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
   let(:mock_response) { instance_double('response') }
   let(:auth_service) { described_class.new(code) }
+  let(:mock_http) { instance_double('Sentry::Net::HTTP') }
 
   before do
     allow(ENV).to receive(:[]).and_call_original
@@ -13,48 +13,52 @@
     allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('https://example.com')
     allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('mock_client_id')
     allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION').and_return('mock_client_assertion')
+    allow(mock_http).to receive(:request).and_return(mock_response)
+    allow(Net::HTTP).to receive(:new).and_return(mock_http)
   end
 
   describe '#tokens' do
-    let(:mock_net_http_post) { instance_double('Net::HTTP::Post', request: nil) }
+    let(:token_payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
 
     context 'when the request is successful' do
       it 'returns a hash of tokens' do
         allow(mock_response).to receive(:body).and_return(token_payload.to_json)
-        allow(mock_response).to receive(:code).and_return(200)
-        allow(mock_net_http_post).to receive(:request).and_return(mock_response)
-        allow(Net::HTTP).to receive(:new).and_return(mock_net_http_post)
 
         result = auth_service.tokens
-
-        expect(mock_net_http_post).to receive(:set_form_data).with({
-          client_assertion: 'mock_client_assertion',
-          client_assertion_type: 'mock_assertion_type',
-          code: 'mock_code',
-          grant_type: 'authorization_code',
-          redirect_uri: 'mock_redirect_uri',
-        })
         expect(result).to eq(token_payload)
       end
     end
+
+    context 'when the request is unsuccessful' do
+      it 'returns an empty hash' do
+        allow(mock_response).to receive(:body).and_return({}.to_json)
+
+        result = auth_service.tokens
+        expect(result).to eq({})
+      end
+    end
   end
 
   describe '#user_info' do
     let(:access_token) { 'mock_access_token' }
     let(:user_info_payload) { { 'email' => 'test@test.com' } }
-    let(:mock_net_http_get) { instance_double('Net::HTTP::Get', request: nil) }
 
     context 'when the request is successful' do
       it 'returns a hash of user info' do
         allow(mock_response).to receive(:body).and_return(user_info_payload.to_json)
-        allow(mock_response).to receive(:code).and_return(200)
-        allow(mock_net_http_get).to receive(:request).and_return(mock_response)
-        allow(Net::HTTP).to receive(:new).and_return(mock_net_http_get)
 
         result = auth_service.user_info(access_token)
-
         expect(result).to eq(user_info_payload)
       end
     end
+
+    context 'when the request is unsuccessful' do
+      it 'returns an empty hash' do
+        allow(mock_response).to receive(:body).and_return({}.to_json)
+
+        result = auth_service.user_info(access_token)
+        expect(result).to eq({})
+      end
+    end
   end
 end

From e2f22d579568481617ad404511095ba0d615e1a4 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 30 Oct 2023 17:00:36 +0000
Subject: [PATCH 008/122] remove mock client assertion in gov one auth service

---
 app/services/gov_one_auth_service.rb       | 2 +-
 spec/rails_helper.rb                       | 2 --
 spec/services/gov_one_auth_service_spec.rb | 1 -
 3 files changed, 1 insertion(+), 4 deletions(-)

diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 4e991aef7..14ba31376 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -11,7 +11,7 @@ def tokens
       code: @code,
       redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
       client_assertion_type: ENV['GOV_ONE_CLIENT_ASSERTION_TYPE'],
-      client_assertion: ENV['GOV_ONE_CLIENT_ASSERTION'] || jwt_assertion,
+      client_assertion: jwt_assertion,
     }
 
     token_uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/token")
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index da2af60c4..26f221ef7 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -69,6 +69,4 @@
 
   # enable ShowMeTheCookies
   config.include ShowMeTheCookies, type: :system
-
-  config.include Devise::Test::ControllerHelpers, type: :controller
 end
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index 175844e26..db2abbff8 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -12,7 +12,6 @@
     allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION_TYPE').and_return('mock_assertion_type')
     allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('https://example.com')
     allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('mock_client_id')
-    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION').and_return('mock_client_assertion')
     allow(mock_http).to receive(:request).and_return(mock_response)
     allow(Net::HTTP).to receive(:new).and_return(mock_http)
   end

From 2e6654590af0fb9defd17e94015231643dd259ae Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 31 Oct 2023 09:16:07 +0000
Subject: [PATCH 009/122] revert gemfile.lock changes

---
 Gemfile.lock | 265 ---------------------------------------------------
 1 file changed, 265 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 04f8fd737..d3a94e790 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -68,13 +68,11 @@ GEM
       tzinfo (~> 2.0)
     addressable (2.8.5)
       public_suffix (>= 2.0.2, < 6.0)
-    aes_key_wrap (1.1.0)
     ahoy_matey (5.0.1)
       activesupport (>= 6.1)
       device_detector (>= 1)
       safely_block (>= 0.4)
     ast (2.4.2)
-    attr_required (1.0.1)
     backports (3.24.1)
     base64 (0.1.1)
     bcrypt (3.1.19)
@@ -82,7 +80,6 @@ GEM
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
       rouge (>= 1.0.0)
-    bindata (2.4.15)
     bindex (0.8.1)
     binding_of_caller (1.0.0)
       debug_inspector (>= 0.0.1)
@@ -122,8 +119,6 @@ GEM
       contentful_model (~> 1.0)
       rails (>= 4.2)
       redcarpet (~> 3.2)
-    crack (0.4.5)
-      rexml
     crass (1.0.6)
     cssbundling-rails (1.3.3)
       railties (>= 6.0.0)
@@ -185,8 +180,6 @@ GEM
       base64
       faraday-net_http (>= 2.0, < 3.1)
       ruby2_keywords (>= 0.0.4)
-    faraday-follow_redirects (0.3.0)
-      faraday (>= 1, < 3)
     faraday-net_http (3.0.2)
     ffi (1.16.3)
     ffi-compiler (1.0.1)
@@ -249,7 +242,6 @@ GEM
     grover (1.1.5)
       combine_pdf (~> 1.0)
       nokogiri (~> 1.0)
-    hashdiff (1.0.1)
     hashie (5.0.0)
     html-attributes-utils (1.0.2)
       activesupport (>= 6.1.4.4)
@@ -280,12 +272,6 @@ GEM
     jsbundling-rails (1.2.1)
       railties (>= 6.0.0)
     json (2.6.3)
-    json-jwt (1.16.3)
-      activesupport (>= 4.2)
-      aes_key_wrap
-      bindata
-      faraday (~> 2.0)
-      faraday-follow_redirects
     jwt (2.7.1)
     language_server-protocol (3.17.0.3)
     launchy (2.5.2)
@@ -329,223 +315,6 @@ GEM
       racc (~> 1.4)
     notifications-ruby-client (5.4.0)
       jwt (>= 1.5, < 3)
-    omniauth (2.1.1)
-      hashie (>= 3.4.6)
-      rack (>= 2.2.3)
-      rack-protection
-    omniauth-rails_csrf_protection (1.0.1)
-      actionpack (>= 4.2)
-      omniauth (~> 2.0)
-    omniauth_openid_connect (0.7.1)
-      omniauth (>= 1.9, < 3)
-      openid_connect (~> 2.2)
-    openid_connect (2.2.0)
-      activemodel
-      attr_required (>= 1.0.0)
-      faraday (~> 2.0)
-      faraday-follow_redirects
-      json-jwt (>= 1.16)
-      net-smtp
-      rack-oauth2 (~> 2.2)
-      swd (~> 2.0)
-      tzinfo
-      validate_email
-      validate_url
-      webfinger (~> 2.0)
-    opentelemetry-api (1.2.3)
-    opentelemetry-common (0.20.0)
-      opentelemetry-api (~> 1.0)
-    opentelemetry-exporter-otlp (0.26.1)
-      google-protobuf (~> 3.14)
-      googleapis-common-protos-types (~> 1.3)
-      opentelemetry-api (~> 1.1)
-      opentelemetry-common (~> 0.20)
-      opentelemetry-sdk (~> 1.2)
-      opentelemetry-semantic_conventions
-    opentelemetry-instrumentation-action_pack (0.7.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-      opentelemetry-instrumentation-rack (~> 0.21)
-    opentelemetry-instrumentation-action_view (0.6.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-active_support (~> 0.1)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-active_job (0.6.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-active_model_serializers (0.20.1)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-active_record (0.6.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-      ruby2_keywords
-    opentelemetry-instrumentation-active_support (0.4.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-all (0.50.1)
-      opentelemetry-instrumentation-active_model_serializers (~> 0.20.1)
-      opentelemetry-instrumentation-aws_sdk (~> 0.5.0)
-      opentelemetry-instrumentation-bunny (~> 0.21.0)
-      opentelemetry-instrumentation-concurrent_ruby (~> 0.21.1)
-      opentelemetry-instrumentation-dalli (~> 0.24.1)
-      opentelemetry-instrumentation-delayed_job (~> 0.21.0)
-      opentelemetry-instrumentation-ethon (~> 0.21.1)
-      opentelemetry-instrumentation-excon (~> 0.21.1)
-      opentelemetry-instrumentation-faraday (~> 0.23.1)
-      opentelemetry-instrumentation-grape (~> 0.1.3)
-      opentelemetry-instrumentation-graphql (~> 0.26.2)
-      opentelemetry-instrumentation-gruf (~> 0.1.0)
-      opentelemetry-instrumentation-http (~> 0.23.1)
-      opentelemetry-instrumentation-http_client (~> 0.22.1)
-      opentelemetry-instrumentation-koala (~> 0.20.1)
-      opentelemetry-instrumentation-lmdb (~> 0.22.1)
-      opentelemetry-instrumentation-mongo (~> 0.22.1)
-      opentelemetry-instrumentation-mysql2 (~> 0.24.2)
-      opentelemetry-instrumentation-net_http (~> 0.22.1)
-      opentelemetry-instrumentation-pg (~> 0.25.2)
-      opentelemetry-instrumentation-que (~> 0.7.0)
-      opentelemetry-instrumentation-racecar (~> 0.3.0)
-      opentelemetry-instrumentation-rack (~> 0.23.1)
-      opentelemetry-instrumentation-rails (~> 0.28.0)
-      opentelemetry-instrumentation-rake (~> 0.2.1)
-      opentelemetry-instrumentation-rdkafka (~> 0.4.0)
-      opentelemetry-instrumentation-redis (~> 0.25.1)
-      opentelemetry-instrumentation-resque (~> 0.5.0)
-      opentelemetry-instrumentation-restclient (~> 0.22.1)
-      opentelemetry-instrumentation-ruby_kafka (~> 0.21.0)
-      opentelemetry-instrumentation-sidekiq (~> 0.25.0)
-      opentelemetry-instrumentation-sinatra (~> 0.23.1)
-      opentelemetry-instrumentation-trilogy (~> 0.56.1)
-    opentelemetry-instrumentation-aws_sdk (0.5.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-base (0.22.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-registry (~> 0.1)
-    opentelemetry-instrumentation-bunny (0.21.1)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-concurrent_ruby (0.21.1)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-dalli (0.24.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-delayed_job (0.21.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-ethon (0.21.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-excon (0.21.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-faraday (0.23.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-grape (0.1.4)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-      opentelemetry-instrumentation-rack (~> 0.21)
-    opentelemetry-instrumentation-graphql (0.26.7)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-gruf (0.1.0)
-      opentelemetry-api (>= 1.0.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-http (0.23.1)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-http_client (0.22.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-koala (0.20.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-lmdb (0.22.1)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-mongo (0.22.1)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-mysql2 (0.24.3)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-net_http (0.22.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-pg (0.25.3)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-que (0.7.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-racecar (0.3.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-rack (0.23.4)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-rails (0.28.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-action_pack (~> 0.7.0)
-      opentelemetry-instrumentation-action_view (~> 0.6.0)
-      opentelemetry-instrumentation-active_job (~> 0.6.0)
-      opentelemetry-instrumentation-active_record (~> 0.6.1)
-      opentelemetry-instrumentation-active_support (~> 0.4.1)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-rake (0.2.1)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-rdkafka (0.4.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-redis (0.25.3)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-resque (0.5.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-restclient (0.22.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-ruby_kafka (0.21.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-sidekiq (0.25.0)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-    opentelemetry-instrumentation-sinatra (0.23.2)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-common (~> 0.20.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-      opentelemetry-instrumentation-rack (~> 0.21)
-    opentelemetry-instrumentation-trilogy (0.56.3)
-      opentelemetry-api (~> 1.0)
-      opentelemetry-instrumentation-base (~> 0.22.1)
-      opentelemetry-semantic_conventions (>= 1.8.0)
-    opentelemetry-registry (0.3.0)
-      opentelemetry-api (~> 1.1)
-    opentelemetry-sdk (1.3.0)
-      opentelemetry-api (~> 1.1)
-      opentelemetry-common (~> 0.20)
-      opentelemetry-registry (~> 0.2)
-      opentelemetry-semantic_conventions
-    opentelemetry-semantic_conventions (1.10.0)
-      opentelemetry-api (~> 1.0)
     orm_adapter (0.5.0)
     os (1.1.4)
     pagy (6.1.0)
@@ -579,17 +348,6 @@ GEM
     raabro (1.4.0)
     racc (1.7.1)
     rack (2.2.8)
-    rack-oauth2 (2.2.0)
-      activesupport
-      attr_required
-      faraday (~> 2.0)
-      faraday-follow_redirects
-      json-jwt (>= 1.11.0)
-      rack (>= 2.1.0)
-    rack-protection (3.1.0)
-      rack (~> 2.2, >= 2.2.4)
-    rack-proxy (0.7.7)
-      rack
     rack-test (2.1.0)
       rack (>= 1.3)
     rails (7.0.8)
@@ -749,11 +507,6 @@ GEM
     stimulus-rails (1.2.2)
       railties (>= 6.0.0)
     stringio (3.0.8)
-    swd (2.0.2)
-      activesupport (>= 3)
-      attr_required (>= 0.0.5)
-      faraday (~> 2.0)
-      faraday-follow_redirects
     temple (0.10.3)
     thor (1.2.2)
     tilt (2.3.0)
@@ -770,12 +523,6 @@ GEM
       unf_ext
     unf_ext (0.0.8.2)
     unicode-display_width (2.5.0)
-    validate_email (0.1.6)
-      activemodel (>= 3.0)
-      mail (>= 2.2.5)
-    validate_url (1.0.15)
-      activemodel (>= 3.0.0)
-      public_suffix
     view_component (3.5.0)
       activesupport (>= 5.2.0, < 8.0)
       concurrent-ruby (~> 1.0)
@@ -787,14 +534,6 @@ GEM
       activemodel (>= 6.0.0)
       bindex (>= 0.4.0)
       railties (>= 6.0.0)
-    webfinger (2.1.2)
-      activesupport
-      faraday (~> 2.0)
-      faraday-follow_redirects
-    webmock (3.19.1)
-      addressable (>= 2.8.0)
-      crack (>= 0.3.2)
-      hashdiff (>= 0.4.0, < 2.0.0)
     webrick (1.8.1)
     websocket (1.2.10)
     websocket-driver (0.7.6)
@@ -851,10 +590,7 @@ DEPENDENCIES
   importmap-rails
   jbuilder
   jsbundling-rails
-  jwt
   launchy
-  omniauth-rails_csrf_protection
-  omniauth_openid_connect
   pg
   pry-byebug
   pry-doc
@@ -880,7 +616,6 @@ DEPENDENCIES
   turbo-rails
   tzinfo-data
   web-console
-  webmock
   yard-junk
 
 RUBY VERSION

From 4e82e2cdbc03346029df957155b36eea1247b99f Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 31 Oct 2023 09:24:09 +0000
Subject: [PATCH 010/122] reinstall dependencies

---
 Gemfile.lock | 280 +++++++++++++++++++++++++++++++--------------------
 1 file changed, 171 insertions(+), 109 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index d3a94e790..e9d0bc8eb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,78 +1,88 @@
 GEM
   remote: https://rubygems.org/
   specs:
-    actioncable (7.0.8)
-      actionpack (= 7.0.8)
-      activesupport (= 7.0.8)
+    actioncable (7.1.1)
+      actionpack (= 7.1.1)
+      activesupport (= 7.1.1)
       nio4r (~> 2.0)
       websocket-driver (>= 0.6.1)
-    actionmailbox (7.0.8)
-      actionpack (= 7.0.8)
-      activejob (= 7.0.8)
-      activerecord (= 7.0.8)
-      activestorage (= 7.0.8)
-      activesupport (= 7.0.8)
+      zeitwerk (~> 2.6)
+    actionmailbox (7.1.1)
+      actionpack (= 7.1.1)
+      activejob (= 7.1.1)
+      activerecord (= 7.1.1)
+      activestorage (= 7.1.1)
+      activesupport (= 7.1.1)
       mail (>= 2.7.1)
       net-imap
       net-pop
       net-smtp
-    actionmailer (7.0.8)
-      actionpack (= 7.0.8)
-      actionview (= 7.0.8)
-      activejob (= 7.0.8)
-      activesupport (= 7.0.8)
+    actionmailer (7.1.1)
+      actionpack (= 7.1.1)
+      actionview (= 7.1.1)
+      activejob (= 7.1.1)
+      activesupport (= 7.1.1)
       mail (~> 2.5, >= 2.5.4)
       net-imap
       net-pop
       net-smtp
-      rails-dom-testing (~> 2.0)
-    actionpack (7.0.8)
-      actionview (= 7.0.8)
-      activesupport (= 7.0.8)
-      rack (~> 2.0, >= 2.2.4)
+      rails-dom-testing (~> 2.2)
+    actionpack (7.1.1)
+      actionview (= 7.1.1)
+      activesupport (= 7.1.1)
+      nokogiri (>= 1.8.5)
+      rack (>= 2.2.4)
+      rack-session (>= 1.0.1)
       rack-test (>= 0.6.3)
-      rails-dom-testing (~> 2.0)
-      rails-html-sanitizer (~> 1.0, >= 1.2.0)
-    actiontext (7.0.8)
-      actionpack (= 7.0.8)
-      activerecord (= 7.0.8)
-      activestorage (= 7.0.8)
-      activesupport (= 7.0.8)
+      rails-dom-testing (~> 2.2)
+      rails-html-sanitizer (~> 1.6)
+    actiontext (7.1.1)
+      actionpack (= 7.1.1)
+      activerecord (= 7.1.1)
+      activestorage (= 7.1.1)
+      activesupport (= 7.1.1)
       globalid (>= 0.6.0)
       nokogiri (>= 1.8.5)
-    actionview (7.0.8)
-      activesupport (= 7.0.8)
+    actionview (7.1.1)
+      activesupport (= 7.1.1)
       builder (~> 3.1)
-      erubi (~> 1.4)
-      rails-dom-testing (~> 2.0)
-      rails-html-sanitizer (~> 1.1, >= 1.2.0)
-    activejob (7.0.8)
-      activesupport (= 7.0.8)
+      erubi (~> 1.11)
+      rails-dom-testing (~> 2.2)
+      rails-html-sanitizer (~> 1.6)
+    activejob (7.1.1)
+      activesupport (= 7.1.1)
       globalid (>= 0.3.6)
-    activemodel (7.0.8)
-      activesupport (= 7.0.8)
-    activerecord (7.0.8)
-      activemodel (= 7.0.8)
-      activesupport (= 7.0.8)
-    activestorage (7.0.8)
-      actionpack (= 7.0.8)
-      activejob (= 7.0.8)
-      activerecord (= 7.0.8)
-      activesupport (= 7.0.8)
+    activemodel (7.1.1)
+      activesupport (= 7.1.1)
+    activerecord (7.1.1)
+      activemodel (= 7.1.1)
+      activesupport (= 7.1.1)
+      timeout (>= 0.4.0)
+    activestorage (7.1.1)
+      actionpack (= 7.1.1)
+      activejob (= 7.1.1)
+      activerecord (= 7.1.1)
+      activesupport (= 7.1.1)
       marcel (~> 1.0)
-      mini_mime (>= 1.1.0)
-    activesupport (7.0.8)
+    activesupport (7.1.1)
+      base64
+      bigdecimal
       concurrent-ruby (~> 1.0, >= 1.0.2)
+      connection_pool (>= 2.2.5)
+      drb
       i18n (>= 1.6, < 2)
       minitest (>= 5.1)
+      mutex_m
       tzinfo (~> 2.0)
     addressable (2.8.5)
       public_suffix (>= 2.0.2, < 6.0)
-    ahoy_matey (5.0.1)
+    aes_key_wrap (1.1.0)
+    ahoy_matey (5.0.2)
       activesupport (>= 6.1)
       device_detector (>= 1)
       safely_block (>= 0.4)
     ast (2.4.2)
+    attr_required (1.0.1)
     backports (3.24.1)
     base64 (0.1.1)
     bcrypt (3.1.19)
@@ -80,10 +90,12 @@ GEM
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
       rouge (>= 1.0.0)
+    bigdecimal (3.1.4)
+    bindata (2.4.15)
     bindex (0.8.1)
     binding_of_caller (1.0.0)
       debug_inspector (>= 0.0.1)
-    bootsnap (1.16.0)
+    bootsnap (1.17.0)
       msgpack (~> 1.2)
     brakeman (6.0.1)
     builder (3.2.4)
@@ -99,10 +111,11 @@ GEM
       xpath (~> 3.2)
     choice (0.2.0)
     coderay (1.1.3)
-    combine_pdf (1.0.23)
+    combine_pdf (1.0.24)
       matrix
       ruby-rc4 (>= 0.1.5)
     concurrent-ruby (1.2.2)
+    connection_pool (2.4.1)
     contentful (2.17.0)
       http (> 0.8, < 6.0)
       multi_json (~> 1)
@@ -129,7 +142,7 @@ GEM
     debug_inspector (1.1.0)
     declarative (0.0.20)
     device_detector (1.1.1)
-    devise (4.9.2)
+    devise (4.9.3)
       bcrypt (~> 3.0)
       orm_adapter (~> 0.1)
       railties (>= 4.1.0)
@@ -146,6 +159,8 @@ GEM
     dotenv-rails (2.8.1)
       dotenv (= 2.8.1)
       railties (>= 3.2)
+    drb (2.1.1)
+      ruby2_keywords
     dry-core (1.0.1)
       concurrent-ruby (~> 1.0)
       zeitwerk (~> 2.6)
@@ -180,18 +195,20 @@ GEM
       base64
       faraday-net_http (>= 2.0, < 3.1)
       ruby2_keywords (>= 0.0.4)
+    faraday-follow_redirects (0.3.0)
+      faraday (>= 1, < 3)
     faraday-net_http (3.0.2)
     ffi (1.16.3)
     ffi-compiler (1.0.1)
       ffi (>= 1.0.0)
       rake
     foreman (0.87.2)
-    fugit (1.8.1)
+    fugit (1.9.0)
       et-orbi (~> 1, >= 1.2.7)
       raabro (~> 1.4)
     globalid (1.2.1)
       activesupport (>= 6.1)
-    google-apis-core (0.11.1)
+    google-apis-core (0.11.2)
       addressable (~> 2.5, >= 2.5.1)
       googleauth (>= 0.16.2, < 2.a)
       httpclient (>= 2.8.1, < 3.a)
@@ -259,11 +276,12 @@ GEM
     i18n (1.14.1)
       concurrent-ruby (~> 1.0)
     ice_nine (0.11.2)
-    importmap-rails (1.2.1)
+    importmap-rails (1.2.3)
       actionpack (>= 6.0.0)
+      activesupport (>= 6.0.0)
       railties (>= 6.0.0)
     io-console (0.6.0)
-    irb (1.8.1)
+    irb (1.8.3)
       rdoc
       reline (>= 0.3.8)
     jbuilder (2.11.5)
@@ -272,11 +290,17 @@ GEM
     jsbundling-rails (1.2.1)
       railties (>= 6.0.0)
     json (2.6.3)
+    json-jwt (1.16.3)
+      activesupport (>= 4.2)
+      aes_key_wrap
+      bindata
+      faraday (~> 2.0)
+      faraday-follow_redirects
     jwt (2.7.1)
     language_server-protocol (3.17.0.3)
     launchy (2.5.2)
       addressable (~> 2.8)
-    loofah (2.21.3)
+    loofah (2.21.4)
       crass (~> 1.0.2)
       nokogiri (>= 1.12.0)
     mail (2.8.1)
@@ -288,11 +312,11 @@ GEM
     matrix (0.4.2)
     method_source (1.0.0)
     mini_mime (1.1.5)
-    mini_portile2 (2.8.4)
     minitest (5.20.0)
     msgpack (1.7.2)
     multi_json (1.15.0)
-    net-imap (0.4.0)
+    mutex_m (0.1.2)
+    net-imap (0.4.3)
       date
       net-protocol
     net-pop (0.1.2)
@@ -302,19 +326,33 @@ GEM
     net-smtp (0.4.0)
       net-protocol
     nio4r (2.5.9)
-    nokogiri (1.15.4)
-      mini_portile2 (~> 2.8.2)
-      racc (~> 1.4)
     nokogiri (1.15.4-aarch64-linux)
       racc (~> 1.4)
-    nokogiri (1.15.4-arm64-darwin)
-      racc (~> 1.4)
-    nokogiri (1.15.4-x86_64-darwin)
-      racc (~> 1.4)
-    nokogiri (1.15.4-x86_64-linux)
-      racc (~> 1.4)
     notifications-ruby-client (5.4.0)
       jwt (>= 1.5, < 3)
+    omniauth (2.1.1)
+      hashie (>= 3.4.6)
+      rack (>= 2.2.3)
+      rack-protection
+    omniauth-rails_csrf_protection (1.0.1)
+      actionpack (>= 4.2)
+      omniauth (~> 2.0)
+    omniauth_openid_connect (0.7.1)
+      omniauth (>= 1.9, < 3)
+      openid_connect (~> 2.2)
+    openid_connect (2.2.0)
+      activemodel
+      attr_required (>= 1.0.0)
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      json-jwt (>= 1.16)
+      net-smtp
+      rack-oauth2 (~> 2.2)
+      swd (~> 2.0)
+      tzinfo
+      validate_email
+      validate_url
+      webfinger (~> 2.0)
     orm_adapter (0.5.0)
     os (1.1.4)
     pagy (6.1.0)
@@ -334,12 +372,12 @@ GEM
       yard (~> 0.9.11)
     pry-rails (0.3.9)
       pry (>= 0.10.4)
-    psych (5.1.0)
+    psych (5.1.1.1)
       stringio
     public_suffix (5.0.3)
     puma (6.4.0)
       nio4r (~> 2.0)
-    que (2.2.1)
+    que (2.3.0)
     que-scheduler (4.4.0)
       activesupport (>= 5.0)
       fugit (~> 1.1, >= 1.1.8)
@@ -347,23 +385,37 @@ GEM
       que (>= 0.14, < 3.0.0)
     raabro (1.4.0)
     racc (1.7.1)
-    rack (2.2.8)
+    rack (3.0.8)
+    rack-oauth2 (2.2.0)
+      activesupport
+      attr_required
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      json-jwt (>= 1.11.0)
+      rack (>= 2.1.0)
+    rack-protection (3.0.6)
+      rack
+    rack-session (2.0.0)
+      rack (>= 3.0.0)
     rack-test (2.1.0)
       rack (>= 1.3)
-    rails (7.0.8)
-      actioncable (= 7.0.8)
-      actionmailbox (= 7.0.8)
-      actionmailer (= 7.0.8)
-      actionpack (= 7.0.8)
-      actiontext (= 7.0.8)
-      actionview (= 7.0.8)
-      activejob (= 7.0.8)
-      activemodel (= 7.0.8)
-      activerecord (= 7.0.8)
-      activestorage (= 7.0.8)
-      activesupport (= 7.0.8)
+    rackup (2.1.0)
+      rack (>= 3)
+      webrick (~> 1.8)
+    rails (7.1.1)
+      actioncable (= 7.1.1)
+      actionmailbox (= 7.1.1)
+      actionmailer (= 7.1.1)
+      actionpack (= 7.1.1)
+      actiontext (= 7.1.1)
+      actionview (= 7.1.1)
+      activejob (= 7.1.1)
+      activemodel (= 7.1.1)
+      activerecord (= 7.1.1)
+      activestorage (= 7.1.1)
+      activesupport (= 7.1.1)
       bundler (>= 1.15.0)
-      railties (= 7.0.8)
+      railties (= 7.1.1)
     rails-dom-testing (2.2.0)
       activesupport (>= 5.0.0)
       minitest
@@ -376,31 +428,32 @@ GEM
     rails-html-sanitizer (1.6.0)
       loofah (~> 2.21)
       nokogiri (~> 1.14)
-    railties (7.0.8)
-      actionpack (= 7.0.8)
-      activesupport (= 7.0.8)
-      method_source
+    railties (7.1.1)
+      actionpack (= 7.1.1)
+      activesupport (= 7.1.1)
+      irb
+      rackup (>= 1.0.0)
       rake (>= 12.2)
-      thor (~> 1.0)
-      zeitwerk (~> 2.5)
+      thor (~> 1.0, >= 1.2.2)
+      zeitwerk (~> 2.6)
     rainbow (3.1.1)
-    rake (13.0.6)
+    rake (13.1.0)
     rdoc (6.5.0)
       psych (>= 4.0.0)
     redcarpet (3.6.0)
-    regexp_parser (2.8.1)
+    regexp_parser (2.8.2)
     reline (0.3.9)
       io-console (~> 0.5)
     representable (3.2.0)
       declarative (< 0.1.0)
       trailblazer-option (>= 0.1.1, < 0.2.0)
       uber (< 0.2.0)
-    responders (3.1.0)
+    responders (3.1.1)
       actionpack (>= 5.2)
       railties (>= 5.2)
     retriable (3.1.2)
     rexml (3.2.6)
-    rouge (4.1.3)
+    rouge (4.2.0)
     rspec-core (3.12.2)
       rspec-support (~> 3.12.0)
     rspec-expectations (3.12.3)
@@ -461,14 +514,14 @@ GEM
     ruby2_keywords (0.0.5)
     rubyzip (2.3.2)
     safely_block (0.4.0)
-    selenium-webdriver (4.13.1)
+    selenium-webdriver (4.14.0)
       rexml (~> 3.2, >= 3.2.5)
       rubyzip (>= 1.2.2, < 3.0)
       websocket (~> 1.0)
-    sentry-rails (5.11.0)
+    sentry-rails (5.12.0)
       railties (>= 5.0)
-      sentry-ruby (~> 5.11.0)
-    sentry-ruby (5.11.0)
+      sentry-ruby (~> 5.12.0)
+    sentry-ruby (5.12.0)
       concurrent-ruby (~> 1.0, >= 1.0.2)
     show_me_the_cookies (6.0.0)
       capybara (>= 2, < 4)
@@ -493,7 +546,7 @@ GEM
     slim (5.1.1)
       temple (~> 0.10.0)
       tilt (>= 2.1.0)
-    slim-rails (3.6.2)
+    slim-rails (3.6.3)
       actionpack (>= 3.1)
       railties (>= 3.1)
       slim (>= 3.0, < 6.0, != 5.0.0)
@@ -504,15 +557,20 @@ GEM
       actionpack (>= 5.2)
       activesupport (>= 5.2)
       sprockets (>= 3.0.0)
-    stimulus-rails (1.2.2)
+    stimulus-rails (1.3.0)
       railties (>= 6.0.0)
     stringio (3.0.8)
+    swd (2.0.2)
+      activesupport (>= 3)
+      attr_required (>= 0.0.5)
+      faraday (~> 2.0)
+      faraday-follow_redirects
     temple (0.10.3)
-    thor (1.2.2)
+    thor (1.3.0)
     tilt (2.3.0)
     timeout (0.4.0)
     trailblazer-option (0.1.2)
-    turbo-rails (1.4.0)
+    turbo-rails (1.5.0)
       actionpack (>= 6.0.0)
       activejob (>= 6.0.0)
       railties (>= 6.0.0)
@@ -523,6 +581,12 @@ GEM
       unf_ext
     unf_ext (0.0.8.2)
     unicode-display_width (2.5.0)
+    validate_email (0.1.6)
+      activemodel (>= 3.0)
+      mail (>= 2.2.5)
+    validate_url (1.0.15)
+      activemodel (>= 3.0.0)
+      public_suffix
     view_component (3.5.0)
       activesupport (>= 5.2.0, < 8.0)
       concurrent-ruby (~> 1.0)
@@ -534,6 +598,10 @@ GEM
       activemodel (>= 6.0.0)
       bindex (>= 0.4.0)
       railties (>= 6.0.0)
+    webfinger (2.1.2)
+      activesupport
+      faraday (~> 2.0)
+      faraday-follow_redirects
     webrick (1.8.1)
     websocket (1.2.10)
     websocket-driver (0.7.6)
@@ -550,15 +618,6 @@ GEM
 
 PLATFORMS
   aarch64-linux-musl
-  arm64-darwin-20
-  arm64-darwin-21
-  arm64-darwin-22
-  ruby
-  x86_64-darwin-19
-  x86_64-darwin-20
-  x86_64-darwin-21
-  x86_64-darwin-22
-  x86_64-linux
 
 DEPENDENCIES
   ahoy_matey
@@ -590,7 +649,10 @@ DEPENDENCIES
   importmap-rails
   jbuilder
   jsbundling-rails
+  jwt
   launchy
+  omniauth-rails_csrf_protection
+  omniauth_openid_connect
   pg
   pry-byebug
   pry-doc
@@ -622,4 +684,4 @@ RUBY VERSION
    ruby 3.2.2p53
 
 BUNDLED WITH
-   2.3.26
+   2.4.10

From 8e0d3630cfe6ece8e85b363f456fff5c5520428b Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 31 Oct 2023 09:29:23 +0000
Subject: [PATCH 011/122] remove gemfile.lock

---
 Gemfile.lock | 687 ---------------------------------------------------
 1 file changed, 687 deletions(-)
 delete mode 100644 Gemfile.lock

diff --git a/Gemfile.lock b/Gemfile.lock
deleted file mode 100644
index e9d0bc8eb..000000000
--- a/Gemfile.lock
+++ /dev/null
@@ -1,687 +0,0 @@
-GEM
-  remote: https://rubygems.org/
-  specs:
-    actioncable (7.1.1)
-      actionpack (= 7.1.1)
-      activesupport (= 7.1.1)
-      nio4r (~> 2.0)
-      websocket-driver (>= 0.6.1)
-      zeitwerk (~> 2.6)
-    actionmailbox (7.1.1)
-      actionpack (= 7.1.1)
-      activejob (= 7.1.1)
-      activerecord (= 7.1.1)
-      activestorage (= 7.1.1)
-      activesupport (= 7.1.1)
-      mail (>= 2.7.1)
-      net-imap
-      net-pop
-      net-smtp
-    actionmailer (7.1.1)
-      actionpack (= 7.1.1)
-      actionview (= 7.1.1)
-      activejob (= 7.1.1)
-      activesupport (= 7.1.1)
-      mail (~> 2.5, >= 2.5.4)
-      net-imap
-      net-pop
-      net-smtp
-      rails-dom-testing (~> 2.2)
-    actionpack (7.1.1)
-      actionview (= 7.1.1)
-      activesupport (= 7.1.1)
-      nokogiri (>= 1.8.5)
-      rack (>= 2.2.4)
-      rack-session (>= 1.0.1)
-      rack-test (>= 0.6.3)
-      rails-dom-testing (~> 2.2)
-      rails-html-sanitizer (~> 1.6)
-    actiontext (7.1.1)
-      actionpack (= 7.1.1)
-      activerecord (= 7.1.1)
-      activestorage (= 7.1.1)
-      activesupport (= 7.1.1)
-      globalid (>= 0.6.0)
-      nokogiri (>= 1.8.5)
-    actionview (7.1.1)
-      activesupport (= 7.1.1)
-      builder (~> 3.1)
-      erubi (~> 1.11)
-      rails-dom-testing (~> 2.2)
-      rails-html-sanitizer (~> 1.6)
-    activejob (7.1.1)
-      activesupport (= 7.1.1)
-      globalid (>= 0.3.6)
-    activemodel (7.1.1)
-      activesupport (= 7.1.1)
-    activerecord (7.1.1)
-      activemodel (= 7.1.1)
-      activesupport (= 7.1.1)
-      timeout (>= 0.4.0)
-    activestorage (7.1.1)
-      actionpack (= 7.1.1)
-      activejob (= 7.1.1)
-      activerecord (= 7.1.1)
-      activesupport (= 7.1.1)
-      marcel (~> 1.0)
-    activesupport (7.1.1)
-      base64
-      bigdecimal
-      concurrent-ruby (~> 1.0, >= 1.0.2)
-      connection_pool (>= 2.2.5)
-      drb
-      i18n (>= 1.6, < 2)
-      minitest (>= 5.1)
-      mutex_m
-      tzinfo (~> 2.0)
-    addressable (2.8.5)
-      public_suffix (>= 2.0.2, < 6.0)
-    aes_key_wrap (1.1.0)
-    ahoy_matey (5.0.2)
-      activesupport (>= 6.1)
-      device_detector (>= 1)
-      safely_block (>= 0.4)
-    ast (2.4.2)
-    attr_required (1.0.1)
-    backports (3.24.1)
-    base64 (0.1.1)
-    bcrypt (3.1.19)
-    better_errors (2.10.1)
-      erubi (>= 1.0.0)
-      rack (>= 0.9.0)
-      rouge (>= 1.0.0)
-    bigdecimal (3.1.4)
-    bindata (2.4.15)
-    bindex (0.8.1)
-    binding_of_caller (1.0.0)
-      debug_inspector (>= 0.0.1)
-    bootsnap (1.17.0)
-      msgpack (~> 1.2)
-    brakeman (6.0.1)
-    builder (3.2.4)
-    byebug (11.1.3)
-    capybara (3.39.2)
-      addressable
-      matrix
-      mini_mime (>= 0.1.3)
-      nokogiri (~> 1.8)
-      rack (>= 1.6.0)
-      rack-test (>= 0.6.3)
-      regexp_parser (>= 1.5, < 3.0)
-      xpath (~> 3.2)
-    choice (0.2.0)
-    coderay (1.1.3)
-    combine_pdf (1.0.24)
-      matrix
-      ruby-rc4 (>= 0.1.5)
-    concurrent-ruby (1.2.2)
-    connection_pool (2.4.1)
-    contentful (2.17.0)
-      http (> 0.8, < 6.0)
-      multi_json (~> 1)
-    contentful-management (2.13.1)
-      http (> 1.0, < 5.0)
-      json (>= 1.8, < 3.0)
-      multi_json (~> 1)
-    contentful_model (1.3.0)
-      activesupport
-      contentful (~> 2.7)
-      contentful-management (~> 2.0)
-      redcarpet
-    contentful_rails (0.5.0)
-      contentful_model (~> 1.0)
-      rails (>= 4.2)
-      redcarpet (~> 3.2)
-    crass (1.0.6)
-    cssbundling-rails (1.3.3)
-      railties (>= 6.0.0)
-    date (3.3.3)
-    debug (1.8.0)
-      irb (>= 1.5.0)
-      reline (>= 0.3.1)
-    debug_inspector (1.1.0)
-    declarative (0.0.20)
-    device_detector (1.1.1)
-    devise (4.9.3)
-      bcrypt (~> 3.0)
-      orm_adapter (~> 0.1)
-      railties (>= 4.1.0)
-      responders
-      warden (~> 1.2.3)
-    dibber (0.7.0)
-    diff-lcs (1.5.0)
-    digest-crc (0.6.5)
-      rake (>= 12.0.0, < 14.0.0)
-    docile (1.4.0)
-    domain_name (0.5.20190701)
-      unf (>= 0.0.5, < 1.0.0)
-    dotenv (2.8.1)
-    dotenv-rails (2.8.1)
-      dotenv (= 2.8.1)
-      railties (>= 3.2)
-    drb (2.1.1)
-      ruby2_keywords
-    dry-core (1.0.1)
-      concurrent-ruby (~> 1.0)
-      zeitwerk (~> 2.6)
-    dry-inflector (1.0.0)
-    dry-initializer (3.1.1)
-    dry-logic (1.5.0)
-      concurrent-ruby (~> 1.0)
-      dry-core (~> 1.0, < 2)
-      zeitwerk (~> 2.6)
-    dry-struct (1.6.0)
-      dry-core (~> 1.0, < 2)
-      dry-types (>= 1.7, < 2)
-      ice_nine (~> 0.11)
-      zeitwerk (~> 2.6)
-    dry-types (1.7.1)
-      concurrent-ruby (~> 1.0)
-      dry-core (~> 1.0)
-      dry-inflector (~> 1.0)
-      dry-logic (~> 1.4)
-      zeitwerk (~> 2.6)
-    erubi (1.12.0)
-    et-orbi (1.2.7)
-      tzinfo
-    factory_bot (6.2.1)
-      activesupport (>= 5.0.0)
-    factory_bot_rails (6.2.0)
-      factory_bot (~> 6.2.0)
-      railties (>= 5.0.0)
-    faker (3.2.1)
-      i18n (>= 1.8.11, < 2)
-    faraday (2.7.11)
-      base64
-      faraday-net_http (>= 2.0, < 3.1)
-      ruby2_keywords (>= 0.0.4)
-    faraday-follow_redirects (0.3.0)
-      faraday (>= 1, < 3)
-    faraday-net_http (3.0.2)
-    ffi (1.16.3)
-    ffi-compiler (1.0.1)
-      ffi (>= 1.0.0)
-      rake
-    foreman (0.87.2)
-    fugit (1.9.0)
-      et-orbi (~> 1, >= 1.2.7)
-      raabro (~> 1.4)
-    globalid (1.2.1)
-      activesupport (>= 6.1)
-    google-apis-core (0.11.2)
-      addressable (~> 2.5, >= 2.5.1)
-      googleauth (>= 0.16.2, < 2.a)
-      httpclient (>= 2.8.1, < 3.a)
-      mini_mime (~> 1.0)
-      representable (~> 3.0)
-      retriable (>= 2.0, < 4.a)
-      rexml
-      webrick
-    google-apis-iamcredentials_v1 (0.17.0)
-      google-apis-core (>= 0.11.0, < 2.a)
-    google-apis-storage_v1 (0.19.0)
-      google-apis-core (>= 0.9.0, < 2.a)
-    google-cloud-core (1.6.0)
-      google-cloud-env (~> 1.0)
-      google-cloud-errors (~> 1.0)
-    google-cloud-env (1.6.0)
-      faraday (>= 0.17.3, < 3.0)
-    google-cloud-errors (1.3.1)
-    google-cloud-storage (1.44.0)
-      addressable (~> 2.8)
-      digest-crc (~> 0.4)
-      google-apis-iamcredentials_v1 (~> 0.1)
-      google-apis-storage_v1 (~> 0.19.0)
-      google-cloud-core (~> 1.6)
-      googleauth (>= 0.16.2, < 2.a)
-      mini_mime (~> 1.0)
-    googleauth (1.8.1)
-      faraday (>= 0.17.3, < 3.a)
-      jwt (>= 1.4, < 3.0)
-      multi_json (~> 1.11)
-      os (>= 0.9, < 2.0)
-      signet (>= 0.16, < 2.a)
-    govuk-components (4.1.1)
-      html-attributes-utils (~> 1.0.0, >= 1.0.0)
-      pagy (~> 6.0)
-      view_component (>= 3.3, < 3.6)
-    govuk_design_system_formbuilder (4.1.1)
-      actionview (>= 6.1)
-      activemodel (>= 6.1)
-      activesupport (>= 6.1)
-      html-attributes-utils (~> 1)
-    govuk_markdown (2.0.1)
-      activesupport
-      redcarpet
-    govuk_notify_rails (2.2.0)
-      notifications-ruby-client (~> 5.1)
-      rails (>= 4.1.0)
-    grover (1.1.5)
-      combine_pdf (~> 1.0)
-      nokogiri (~> 1.0)
-    hashie (5.0.0)
-    html-attributes-utils (1.0.2)
-      activesupport (>= 6.1.4.4)
-    http (4.4.1)
-      addressable (~> 2.3)
-      http-cookie (~> 1.0)
-      http-form_data (~> 2.2)
-      http-parser (~> 1.2.0)
-    http-cookie (1.0.5)
-      domain_name (~> 0.5)
-    http-form_data (2.3.0)
-    http-parser (1.2.3)
-      ffi-compiler (>= 1.0, < 2.0)
-    httpclient (2.8.3)
-    i18n (1.14.1)
-      concurrent-ruby (~> 1.0)
-    ice_nine (0.11.2)
-    importmap-rails (1.2.3)
-      actionpack (>= 6.0.0)
-      activesupport (>= 6.0.0)
-      railties (>= 6.0.0)
-    io-console (0.6.0)
-    irb (1.8.3)
-      rdoc
-      reline (>= 0.3.8)
-    jbuilder (2.11.5)
-      actionview (>= 5.0.0)
-      activesupport (>= 5.0.0)
-    jsbundling-rails (1.2.1)
-      railties (>= 6.0.0)
-    json (2.6.3)
-    json-jwt (1.16.3)
-      activesupport (>= 4.2)
-      aes_key_wrap
-      bindata
-      faraday (~> 2.0)
-      faraday-follow_redirects
-    jwt (2.7.1)
-    language_server-protocol (3.17.0.3)
-    launchy (2.5.2)
-      addressable (~> 2.8)
-    loofah (2.21.4)
-      crass (~> 1.0.2)
-      nokogiri (>= 1.12.0)
-    mail (2.8.1)
-      mini_mime (>= 0.1.1)
-      net-imap
-      net-pop
-      net-smtp
-    marcel (1.0.2)
-    matrix (0.4.2)
-    method_source (1.0.0)
-    mini_mime (1.1.5)
-    minitest (5.20.0)
-    msgpack (1.7.2)
-    multi_json (1.15.0)
-    mutex_m (0.1.2)
-    net-imap (0.4.3)
-      date
-      net-protocol
-    net-pop (0.1.2)
-      net-protocol
-    net-protocol (0.2.1)
-      timeout
-    net-smtp (0.4.0)
-      net-protocol
-    nio4r (2.5.9)
-    nokogiri (1.15.4-aarch64-linux)
-      racc (~> 1.4)
-    notifications-ruby-client (5.4.0)
-      jwt (>= 1.5, < 3)
-    omniauth (2.1.1)
-      hashie (>= 3.4.6)
-      rack (>= 2.2.3)
-      rack-protection
-    omniauth-rails_csrf_protection (1.0.1)
-      actionpack (>= 4.2)
-      omniauth (~> 2.0)
-    omniauth_openid_connect (0.7.1)
-      omniauth (>= 1.9, < 3)
-      openid_connect (~> 2.2)
-    openid_connect (2.2.0)
-      activemodel
-      attr_required (>= 1.0.0)
-      faraday (~> 2.0)
-      faraday-follow_redirects
-      json-jwt (>= 1.16)
-      net-smtp
-      rack-oauth2 (~> 2.2)
-      swd (~> 2.0)
-      tzinfo
-      validate_email
-      validate_url
-      webfinger (~> 2.0)
-    orm_adapter (0.5.0)
-    os (1.1.4)
-    pagy (6.1.0)
-    parallel (1.23.0)
-    parser (3.2.2.4)
-      ast (~> 2.4.1)
-      racc
-    pg (1.5.4)
-    pry (0.14.2)
-      coderay (~> 1.1)
-      method_source (~> 1.0)
-    pry-byebug (3.10.1)
-      byebug (~> 11.0)
-      pry (>= 0.13, < 0.15)
-    pry-doc (1.4.0)
-      pry (~> 0.11)
-      yard (~> 0.9.11)
-    pry-rails (0.3.9)
-      pry (>= 0.10.4)
-    psych (5.1.1.1)
-      stringio
-    public_suffix (5.0.3)
-    puma (6.4.0)
-      nio4r (~> 2.0)
-    que (2.3.0)
-    que-scheduler (4.4.0)
-      activesupport (>= 5.0)
-      fugit (~> 1.1, >= 1.1.8)
-      hashie (>= 3, < 6)
-      que (>= 0.14, < 3.0.0)
-    raabro (1.4.0)
-    racc (1.7.1)
-    rack (3.0.8)
-    rack-oauth2 (2.2.0)
-      activesupport
-      attr_required
-      faraday (~> 2.0)
-      faraday-follow_redirects
-      json-jwt (>= 1.11.0)
-      rack (>= 2.1.0)
-    rack-protection (3.0.6)
-      rack
-    rack-session (2.0.0)
-      rack (>= 3.0.0)
-    rack-test (2.1.0)
-      rack (>= 1.3)
-    rackup (2.1.0)
-      rack (>= 3)
-      webrick (~> 1.8)
-    rails (7.1.1)
-      actioncable (= 7.1.1)
-      actionmailbox (= 7.1.1)
-      actionmailer (= 7.1.1)
-      actionpack (= 7.1.1)
-      actiontext (= 7.1.1)
-      actionview (= 7.1.1)
-      activejob (= 7.1.1)
-      activemodel (= 7.1.1)
-      activerecord (= 7.1.1)
-      activestorage (= 7.1.1)
-      activesupport (= 7.1.1)
-      bundler (>= 1.15.0)
-      railties (= 7.1.1)
-    rails-dom-testing (2.2.0)
-      activesupport (>= 5.0.0)
-      minitest
-      nokogiri (>= 1.6)
-    rails-erd (1.7.2)
-      activerecord (>= 4.2)
-      activesupport (>= 4.2)
-      choice (~> 0.2.0)
-      ruby-graphviz (~> 1.2)
-    rails-html-sanitizer (1.6.0)
-      loofah (~> 2.21)
-      nokogiri (~> 1.14)
-    railties (7.1.1)
-      actionpack (= 7.1.1)
-      activesupport (= 7.1.1)
-      irb
-      rackup (>= 1.0.0)
-      rake (>= 12.2)
-      thor (~> 1.0, >= 1.2.2)
-      zeitwerk (~> 2.6)
-    rainbow (3.1.1)
-    rake (13.1.0)
-    rdoc (6.5.0)
-      psych (>= 4.0.0)
-    redcarpet (3.6.0)
-    regexp_parser (2.8.2)
-    reline (0.3.9)
-      io-console (~> 0.5)
-    representable (3.2.0)
-      declarative (< 0.1.0)
-      trailblazer-option (>= 0.1.1, < 0.2.0)
-      uber (< 0.2.0)
-    responders (3.1.1)
-      actionpack (>= 5.2)
-      railties (>= 5.2)
-    retriable (3.1.2)
-    rexml (3.2.6)
-    rouge (4.2.0)
-    rspec-core (3.12.2)
-      rspec-support (~> 3.12.0)
-    rspec-expectations (3.12.3)
-      diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.12.0)
-    rspec-mocks (3.12.6)
-      diff-lcs (>= 1.2.0, < 2.0)
-      rspec-support (~> 3.12.0)
-    rspec-rails (6.0.3)
-      actionpack (>= 6.1)
-      activesupport (>= 6.1)
-      railties (>= 6.1)
-      rspec-core (~> 3.12)
-      rspec-expectations (~> 3.12)
-      rspec-mocks (~> 3.12)
-      rspec-support (~> 3.12)
-    rspec-support (3.12.1)
-    rubocop (1.55.0)
-      json (~> 2.3)
-      language_server-protocol (>= 3.17.0)
-      parallel (~> 1.10)
-      parser (>= 3.2.2.3)
-      rainbow (>= 2.2.2, < 4.0)
-      regexp_parser (>= 1.8, < 3.0)
-      rexml (>= 3.2.5, < 4.0)
-      rubocop-ast (>= 1.28.1, < 2.0)
-      ruby-progressbar (~> 1.7)
-      unicode-display_width (>= 2.4.0, < 3.0)
-    rubocop-ast (1.29.0)
-      parser (>= 3.2.1.0)
-    rubocop-capybara (2.19.0)
-      rubocop (~> 1.41)
-    rubocop-factory_bot (2.24.0)
-      rubocop (~> 1.33)
-    rubocop-govuk (4.12.0)
-      rubocop (= 1.55.0)
-      rubocop-ast (= 1.29.0)
-      rubocop-rails (= 2.20.2)
-      rubocop-rake (= 0.6.0)
-      rubocop-rspec (= 2.22.0)
-    rubocop-performance (1.19.1)
-      rubocop (>= 1.7.0, < 2.0)
-      rubocop-ast (>= 0.4.0)
-    rubocop-rails (2.20.2)
-      activesupport (>= 4.2.0)
-      rack (>= 1.1)
-      rubocop (>= 1.33.0, < 2.0)
-    rubocop-rake (0.6.0)
-      rubocop (~> 1.0)
-    rubocop-rspec (2.22.0)
-      rubocop (~> 1.33)
-      rubocop-capybara (~> 2.17)
-      rubocop-factory_bot (~> 2.22)
-    ruby-graphviz (1.2.5)
-      rexml
-    ruby-progressbar (1.13.0)
-    ruby-rc4 (0.1.5)
-    ruby2_keywords (0.0.5)
-    rubyzip (2.3.2)
-    safely_block (0.4.0)
-    selenium-webdriver (4.14.0)
-      rexml (~> 3.2, >= 3.2.5)
-      rubyzip (>= 1.2.2, < 3.0)
-      websocket (~> 1.0)
-    sentry-rails (5.12.0)
-      railties (>= 5.0)
-      sentry-ruby (~> 5.12.0)
-    sentry-ruby (5.12.0)
-      concurrent-ruby (~> 1.0, >= 1.0.2)
-    show_me_the_cookies (6.0.0)
-      capybara (>= 2, < 4)
-    signet (0.18.0)
-      addressable (~> 2.8)
-      faraday (>= 0.17.5, < 3.a)
-      jwt (>= 1.5, < 3.0)
-      multi_json (~> 1.10)
-    simplecov (0.22.0)
-      docile (~> 1.1)
-      simplecov-html (~> 0.11)
-      simplecov_json_formatter (~> 0.1)
-    simplecov-html (0.12.3)
-    simplecov_json_formatter (0.1.4)
-    site_prism (4.0.3)
-      addressable (~> 2.8)
-      capybara (~> 3.27)
-      site_prism-all_there (~> 2.0)
-    site_prism-all_there (2.0.2)
-    sitemap_generator (6.3.0)
-      builder (~> 3.0)
-    slim (5.1.1)
-      temple (~> 0.10.0)
-      tilt (>= 2.1.0)
-    slim-rails (3.6.3)
-      actionpack (>= 3.1)
-      railties (>= 3.1)
-      slim (>= 3.0, < 6.0, != 5.0.0)
-    sprockets (4.2.1)
-      concurrent-ruby (~> 1.0)
-      rack (>= 2.2.4, < 4)
-    sprockets-rails (3.4.2)
-      actionpack (>= 5.2)
-      activesupport (>= 5.2)
-      sprockets (>= 3.0.0)
-    stimulus-rails (1.3.0)
-      railties (>= 6.0.0)
-    stringio (3.0.8)
-    swd (2.0.2)
-      activesupport (>= 3)
-      attr_required (>= 0.0.5)
-      faraday (~> 2.0)
-      faraday-follow_redirects
-    temple (0.10.3)
-    thor (1.3.0)
-    tilt (2.3.0)
-    timeout (0.4.0)
-    trailblazer-option (0.1.2)
-    turbo-rails (1.5.0)
-      actionpack (>= 6.0.0)
-      activejob (>= 6.0.0)
-      railties (>= 6.0.0)
-    tzinfo (2.0.6)
-      concurrent-ruby (~> 1.0)
-    uber (0.1.0)
-    unf (0.1.4)
-      unf_ext
-    unf_ext (0.0.8.2)
-    unicode-display_width (2.5.0)
-    validate_email (0.1.6)
-      activemodel (>= 3.0)
-      mail (>= 2.2.5)
-    validate_url (1.0.15)
-      activemodel (>= 3.0.0)
-      public_suffix
-    view_component (3.5.0)
-      activesupport (>= 5.2.0, < 8.0)
-      concurrent-ruby (~> 1.0)
-      method_source (~> 1.0)
-    warden (1.2.9)
-      rack (>= 2.0.9)
-    web-console (4.2.1)
-      actionview (>= 6.0.0)
-      activemodel (>= 6.0.0)
-      bindex (>= 0.4.0)
-      railties (>= 6.0.0)
-    webfinger (2.1.2)
-      activesupport
-      faraday (~> 2.0)
-      faraday-follow_redirects
-    webrick (1.8.1)
-    websocket (1.2.10)
-    websocket-driver (0.7.6)
-      websocket-extensions (>= 0.1.0)
-    websocket-extensions (0.1.5)
-    xpath (3.2.0)
-      nokogiri (~> 1.8)
-    yard (0.9.34)
-    yard-junk (0.0.9)
-      backports (>= 3.18)
-      rainbow
-      yard
-    zeitwerk (2.6.12)
-
-PLATFORMS
-  aarch64-linux-musl
-
-DEPENDENCIES
-  ahoy_matey
-  better_errors
-  binding_of_caller
-  bootsnap
-  brakeman
-  capybara
-  contentful_rails
-  cssbundling-rails
-  debug
-  devise
-  dibber
-  dotenv-rails
-  dry-core
-  dry-inflector
-  dry-initializer
-  dry-struct
-  dry-types
-  factory_bot_rails
-  faker
-  foreman
-  google-cloud-storage
-  govuk-components
-  govuk_design_system_formbuilder
-  govuk_markdown
-  govuk_notify_rails
-  grover
-  importmap-rails
-  jbuilder
-  jsbundling-rails
-  jwt
-  launchy
-  omniauth-rails_csrf_protection
-  omniauth_openid_connect
-  pg
-  pry-byebug
-  pry-doc
-  pry-rails
-  puma
-  que-scheduler
-  rails
-  rails-erd
-  redcarpet
-  rspec-rails
-  rubocop-govuk
-  rubocop-performance
-  selenium-webdriver
-  sentry-rails
-  sentry-ruby
-  show_me_the_cookies
-  simplecov
-  site_prism
-  sitemap_generator
-  slim-rails
-  sprockets-rails
-  stimulus-rails
-  turbo-rails
-  tzinfo-data
-  web-console
-  yard-junk
-
-RUBY VERSION
-   ruby 3.2.2p53
-
-BUNDLED WITH
-   2.4.10

From e0af9edc091f26d2a4b88b5d3248ed889072ad10 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 31 Oct 2023 10:55:41 +0000
Subject: [PATCH 012/122] add finding users by gov one id token

---
 .../users/omniauth_callbacks_controller.rb    | 15 ++++++++-----
 app/helpers/gov_one_helper.rb                 |  2 +-
 app/models/user.rb                            |  4 ++--
 .../20231031094611_add_id_token_to_users.rb   |  6 ++++++
 db/schema.rb                                  |  4 +++-
 .../omniauth_callbacks_controller_spec.rb     | 21 ++++++++++++++-----
 6 files changed, 38 insertions(+), 14 deletions(-)
 create mode 100644 db/migrate/20231031094611_add_id_token_to_users.rb

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 7214ae173..816b0d61f 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -10,23 +10,28 @@ def openid_connect
     end
 
     access_token = tokens_response['access_token']
-    session[:id_token] = tokens_response['id_token']
 
     user_info_response = auth_service.user_info(access_token)
     if user_info_response.empty? || user_info_response['email'].blank?
       flash[:alert] = 'There was a problem signing in. Please try again.'
       redirect_to root_path and return
     end
-    @email = user_info_response['email']
-    gov_user = find_or_create_user
+    gov_user = find_or_create_user(user_info_response['email'], tokens_response['id_token'])
     sign_in_and_redirect gov_user if gov_user
   end
 
 private
 
   # @return [User]
-  def find_or_create_user
-    User.find_by(email: @email) || User.create_from_email(@email)
+  def find_or_create_user(email, id_token)
+    existing_user = User.find_by(email: email)
+
+    if existing_user
+      existing_user.update!(id_token: id_token)
+    else
+      existing_user = User.find_by(id_token: id_token) || User.create_from_gov_one(email: email, id_token: id_token)
+    end
+    existing_user
   end
 
   def after_sign_in_path_for(resource)
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index a8eefb2dc..aea147cfe 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -6,6 +6,6 @@ def login_uri
 
   # @return [String]
   def logout_uri
-    "#{ENV['GOV_ONE_SIGN_OUT_URI']}?id_token_hint=#{session[:id_token]}&post_logout_redirect_uri=#{ENV['GOV_ONE_SIGN_OUT_REDIRECT_URI']}&state=#{SecureRandom.uuid}"
+    "#{ENV['GOV_ONE_SIGN_OUT_URI']}?id_token_hint=#{current_user.id_token}&post_logout_redirect_uri=#{ENV['GOV_ONE_SIGN_OUT_REDIRECT_URI']}&state=#{SecureRandom.uuid}"
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 03dfc932f..24d53ab25 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -22,8 +22,8 @@ def self.dashboard_headers
   end
 
   # @return [User]
-  def self.create_from_email(email)
-    user = User.new(email: email, confirmed_at: Time.zone.now)
+  def self.create_from_gov_one(email:, id_token:)
+    user = User.new(email: email, id_token: id_token, confirmed_at: Time.zone.now)
     user.save!(validate: false)
     user
   end
diff --git a/db/migrate/20231031094611_add_id_token_to_users.rb b/db/migrate/20231031094611_add_id_token_to_users.rb
new file mode 100644
index 000000000..47b69f094
--- /dev/null
+++ b/db/migrate/20231031094611_add_id_token_to_users.rb
@@ -0,0 +1,6 @@
+class AddIdTokenToUsers < ActiveRecord::Migration[7.1]
+  def change
+    add_column :users, :id_token, :string
+    add_index :users, :id_token, unique: true
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index bc371754d..5542f54b2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema[7.0].define(version: 2023_08_23_163600) do
+ActiveRecord::Schema[7.0].define(version: 2023_10_31_094611) do
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
 
@@ -229,8 +229,10 @@
     t.string "closed_reason_custom"
     t.boolean "training_emails"
     t.boolean "early_years_emails"
+    t.string "id_token"
     t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
     t.index ["email"], name: "index_users_on_email", unique: true
+    t.index ["id_token"], name: "index_users_on_id_token", unique: true
     t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
     t.index ["unlock_token"], name: "index_users_on_unlock_token"
   end
diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index 2d768fef8..87c7ebfd8 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -19,22 +19,33 @@
     end
 
     context 'when a User does not exist' do
-      it 'sets session id_token and creates the user' do
+      it 'creates the user with the email and id_token' do
         get :openid_connect, params: { 'code' => 'mock_code' }
-        expect(session[:id_token]).to eq(id_token)
         expect(User.find_by(email: email)).to be_truthy
+        expect(User.find_by(id_token: id_token)).to be_truthy
         expect(response).to redirect_to(edit_registration_name_path)
       end
     end
 
-    context 'when a User exists' do
+    context 'when a User email exists' do
       before do
         create :user, :registered, email: email
       end
 
-      it 'sets session id_token and signs in the user' do
+      it 'updates the user id_token and signs them in' do
+        get :openid_connect, params: { 'code' => 'mock_code' }
+        expect(User.find_by(id_token: id_token)).to be_truthy
+        expect(response).to redirect_to(my_modules_path)
+      end
+    end
+
+    context 'when a User id_token exists' do
+      before do
+        create :user, :registered, id_token: id_token
+      end
+
+      it 'signs the user in' do
         get :openid_connect, params: { 'code' => 'mock_code' }
-        expect(session[:id_token]).to eq(id_token)
         expect(response).to redirect_to(my_modules_path)
       end
     end

From 426f64865e8d5a5b6f82e733082b817200e63e76 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 31 Oct 2023 13:39:24 +0000
Subject: [PATCH 013/122] verify gov one redirect state and decode id token

---
 .../users/omniauth_callbacks_controller.rb    | 55 +++++++++++++------
 app/helpers/gov_one_helper.rb                 |  7 ++-
 app/models/user.rb                            |  6 +-
 app/services/gov_one_auth_service.rb          | 18 ++++++
 ..._rename_id_token_to_gov_one_id_in_users.rb |  5 ++
 db/schema.rb                                  |  6 +-
 .../omniauth_callbacks_controller_spec.rb     | 17 ++++--
 7 files changed, 85 insertions(+), 29 deletions(-)
 create mode 100644 db/migrate/20231031132907_rename_id_token_to_gov_one_id_in_users.rb

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 816b0d61f..12eb552dc 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -1,35 +1,58 @@
 class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
   def openid_connect
-    code = params['code']
+    return error_redirect unless valid_params?
 
-    auth_service = GovOneAuthService.new(code)
+    session.delete(:gov_one_auth_state)
+
+    auth_service = GovOneAuthService.new(params['code'])
     tokens_response = auth_service.tokens
-    if tokens_response.empty? || tokens_response['access_token'].blank? || tokens_response['id_token'].blank?
-      flash[:alert] = 'There was a problem signing in. Please try again.'
-      redirect_to root_path and return
-    end
 
-    access_token = tokens_response['access_token']
+    return error_redirect unless valid_tokens?(tokens_response)
 
-    user_info_response = auth_service.user_info(access_token)
-    if user_info_response.empty? || user_info_response['email'].blank?
-      flash[:alert] = 'There was a problem signing in. Please try again.'
-      redirect_to root_path and return
-    end
-    gov_user = find_or_create_user(user_info_response['email'], tokens_response['id_token'])
+    id_token = tokens_response['id_token']
+    session[:id_token] = id_token
+    gov_one_id = auth_service.decode_id_token(id_token)[0]['sub']
+
+    user_info_response = auth_service.user_info(tokens_response['access_token'])
+    return error_redirect unless valid_user_info?(user_info_response)
+
+    gov_user = find_or_create_user(user_info_response['email'], gov_one_id)
     sign_in_and_redirect gov_user if gov_user
   end
 
 private
 
+  # @return [Boolean]
+  def valid_params?
+    params['code'].present? && params['state'].present? && params['state'] == session[:gov_one_auth_state]
+  end
+
+  # @param tokens_response [Hash]
+  # @return [Boolean]
+  def valid_tokens?(tokens_response)
+    tokens_response.present? && tokens_response['access_token'].present? && tokens_response['id_token'].present?
+  end
+
+  # @param user_info_response [Hash]
+  # @return [Boolean]
+  def valid_user_info?(user_info_response)
+    user_info_response.present? && user_info_response['email'].present?
+  end
+
+  # @return [void]
+  def error_redirect
+    flash[:alert] = 'There was a problem signing in. Please try again.'
+    redirect_to root_path
+  end
+
   # @return [User]
-  def find_or_create_user(email, id_token)
+  def find_or_create_user(email, id)
     existing_user = User.find_by(email: email)
 
     if existing_user
-      existing_user.update!(id_token: id_token)
+      existing_user.update!(gov_one_id: id)
     else
-      existing_user = User.find_by(id_token: id_token) || User.create_from_gov_one(email: email, id_token: id_token)
+      existing_user = User.find_by(gov_one_id: id) || User.create_from_gov_one(email: email, gov_one_id: id)
     end
     existing_user
   end
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index aea147cfe..afdc3da79 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -1,11 +1,14 @@
 module GovOneHelper
   # @return [String]
   def login_uri
-    "#{ENV['GOV_ONE_AUTH_URI']}&nonce=#{SecureRandom.uuid}&state=#{SecureRandom.uuid}"
+    state = SecureRandom.uuid
+    nonce = SecureRandom.uuid
+    session[:gov_one_auth_state] = state
+    "#{ENV['GOV_ONE_AUTH_URI']}&nonce=#{nonce}&state=#{state}"
   end
 
   # @return [String]
   def logout_uri
-    "#{ENV['GOV_ONE_SIGN_OUT_URI']}?id_token_hint=#{current_user.id_token}&post_logout_redirect_uri=#{ENV['GOV_ONE_SIGN_OUT_REDIRECT_URI']}&state=#{SecureRandom.uuid}"
+    "#{ENV['GOV_ONE_SIGN_OUT_URI']}?id_token_hint=#{session[:id_token]}&post_logout_redirect_uri=#{ENV['GOV_ONE_SIGN_OUT_REDIRECT_URI']}&state=#{SecureRandom.uuid}"
   end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index 24d53ab25..72aef162b 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -21,9 +21,11 @@ def self.dashboard_headers
     DASHBOARD_ATTRS + Training::Module.live.map { |mod| "module_#{mod.position}_time" }
   end
 
+  # @param email [String]
+  # @param gov_one_id [String]
   # @return [User]
-  def self.create_from_gov_one(email:, id_token:)
-    user = User.new(email: email, id_token: id_token, confirmed_at: Time.zone.now)
+  def self.create_from_gov_one(email:, gov_one_id:)
+    user = User.new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now)
     user.save!(validate: false)
     user
   end
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 14ba31376..446449d2c 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -42,6 +42,16 @@ def user_info(access_token)
     {}
   end
 
+  # @param token [String]
+  # @return [Array<Hash>]
+  def decode_id_token(token)
+    kid = JWT.decode(token, nil, false).last['kid']
+    key_params = jwks['keys'].find { |key| key['kid'] == kid }
+    jwk = JWT::JWK.new(key_params)
+
+    JWT.decode(token, jwk.public_key, true, algorithm: 'ES256')
+  end
+
 private
 
   # @param uri [URI]
@@ -52,6 +62,14 @@ def build_http(uri)
     http
   end
 
+  def jwks
+    discovery_url = "#{ENV['GOV_ONE_BASE_URI']}/.well-known/jwks.json"
+    uri = URI.parse(discovery_url)
+    http = build_http(uri)
+    response = http.request(Net::HTTP::Get.new(uri.path))
+    JSON.parse(response.body)
+  end
+
   # @return [String]
   def jwt_assertion
     rsa_private = OpenSSL::PKey::RSA.new(Rails.application.config.gov_one_private_key)
diff --git a/db/migrate/20231031132907_rename_id_token_to_gov_one_id_in_users.rb b/db/migrate/20231031132907_rename_id_token_to_gov_one_id_in_users.rb
new file mode 100644
index 000000000..433bedd3c
--- /dev/null
+++ b/db/migrate/20231031132907_rename_id_token_to_gov_one_id_in_users.rb
@@ -0,0 +1,5 @@
+class RenameIdTokenToGovOneIdInUsers < ActiveRecord::Migration[7.0]
+  def change
+    rename_column :users, :id_token, :gov_one_id
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 5542f54b2..166d883ee 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema[7.0].define(version: 2023_10_31_094611) do
+ActiveRecord::Schema[7.0].define(version: 2023_10_31_132907) do
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
 
@@ -229,10 +229,10 @@
     t.string "closed_reason_custom"
     t.boolean "training_emails"
     t.boolean "early_years_emails"
-    t.string "id_token"
+    t.string "gov_one_id"
     t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
     t.index ["email"], name: "index_users_on_email", unique: true
-    t.index ["id_token"], name: "index_users_on_id_token", unique: true
+    t.index ["gov_one_id"], name: "index_users_on_gov_one_id", unique: true
     t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
     t.index ["unlock_token"], name: "index_users_on_unlock_token"
   end
diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index 87c7ebfd8..c28512af5 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -16,14 +16,17 @@
       allow(auth_service).to receive(:tokens).and_return({ 'access_token' => access_token, 'id_token' => id_token })
       allow(auth_service).to receive(:user_info).and_return({ 'email' => email })
       allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
+      allow(auth_service).to receive(:decode_id_token).and_return([{ 'sub' => 'mock_sub' }])
+      session[:gov_one_auth_state] = 'mock_state'
     end
 
     context 'when a User does not exist' do
       it 'creates the user with the email and id_token' do
-        get :openid_connect, params: { 'code' => 'mock_code' }
+        get :openid_connect, params: { 'code' => 'mock_code', 'state' => 'mock_state' }
         expect(User.find_by(email: email)).to be_truthy
-        expect(User.find_by(id_token: id_token)).to be_truthy
+        expect(User.find_by(gov_one_id: 'mock_sub')).to be_truthy
         expect(response).to redirect_to(edit_registration_name_path)
+        expect(session[:id_token]).to eq(id_token)
       end
     end
 
@@ -33,20 +36,22 @@
       end
 
       it 'updates the user id_token and signs them in' do
-        get :openid_connect, params: { 'code' => 'mock_code' }
-        expect(User.find_by(id_token: id_token)).to be_truthy
+        get :openid_connect, params: { 'code' => 'mock_code', 'state' => 'mock_state' }
+        expect(User.find_by(gov_one_id: 'mock_sub')).to be_truthy
         expect(response).to redirect_to(my_modules_path)
+        expect(session[:id_token]).to eq(id_token)
       end
     end
 
     context 'when a User id_token exists' do
       before do
-        create :user, :registered, id_token: id_token
+        create :user, :registered, gov_one_id: 'mock_sub'
       end
 
       it 'signs the user in' do
-        get :openid_connect, params: { 'code' => 'mock_code' }
+        get :openid_connect, params: { 'code' => 'mock_code', 'state' => 'mock_state' }
         expect(response).to redirect_to(my_modules_path)
+        expect(session[:id_token]).to eq(id_token)
       end
     end
   end

From 93f7080eb9080c95cca78742fe0aad08bf5603f9 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 1 Nov 2023 09:15:36 +0000
Subject: [PATCH 014/122] update gemfile.lock

---
 Gemfile.lock | 686 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 686 insertions(+)
 create mode 100644 Gemfile.lock

diff --git a/Gemfile.lock b/Gemfile.lock
new file mode 100644
index 000000000..e2fab4ca5
--- /dev/null
+++ b/Gemfile.lock
@@ -0,0 +1,686 @@
+GEM
+  remote: https://rubygems.org/
+  specs:
+    actioncable (7.0.8)
+      actionpack (= 7.0.8)
+      activesupport (= 7.0.8)
+      nio4r (~> 2.0)
+      websocket-driver (>= 0.6.1)
+    actionmailbox (7.0.8)
+      actionpack (= 7.0.8)
+      activejob (= 7.0.8)
+      activerecord (= 7.0.8)
+      activestorage (= 7.0.8)
+      activesupport (= 7.0.8)
+      mail (>= 2.7.1)
+      net-imap
+      net-pop
+      net-smtp
+    actionmailer (7.0.8)
+      actionpack (= 7.0.8)
+      actionview (= 7.0.8)
+      activejob (= 7.0.8)
+      activesupport (= 7.0.8)
+      mail (~> 2.5, >= 2.5.4)
+      net-imap
+      net-pop
+      net-smtp
+      rails-dom-testing (~> 2.0)
+    actionpack (7.0.8)
+      actionview (= 7.0.8)
+      activesupport (= 7.0.8)
+      rack (~> 2.0, >= 2.2.4)
+      rack-test (>= 0.6.3)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.0, >= 1.2.0)
+    actiontext (7.0.8)
+      actionpack (= 7.0.8)
+      activerecord (= 7.0.8)
+      activestorage (= 7.0.8)
+      activesupport (= 7.0.8)
+      globalid (>= 0.6.0)
+      nokogiri (>= 1.8.5)
+    actionview (7.0.8)
+      activesupport (= 7.0.8)
+      builder (~> 3.1)
+      erubi (~> 1.4)
+      rails-dom-testing (~> 2.0)
+      rails-html-sanitizer (~> 1.1, >= 1.2.0)
+    activejob (7.0.8)
+      activesupport (= 7.0.8)
+      globalid (>= 0.3.6)
+    activemodel (7.0.8)
+      activesupport (= 7.0.8)
+    activerecord (7.0.8)
+      activemodel (= 7.0.8)
+      activesupport (= 7.0.8)
+    activestorage (7.0.8)
+      actionpack (= 7.0.8)
+      activejob (= 7.0.8)
+      activerecord (= 7.0.8)
+      activesupport (= 7.0.8)
+      marcel (~> 1.0)
+      mini_mime (>= 1.1.0)
+    activesupport (7.0.8)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+      i18n (>= 1.6, < 2)
+      minitest (>= 5.1)
+      tzinfo (~> 2.0)
+    addressable (2.8.5)
+      public_suffix (>= 2.0.2, < 6.0)
+    aes_key_wrap (1.1.0)
+    ahoy_matey (5.0.1)
+      activesupport (>= 6.1)
+      device_detector (>= 1)
+      safely_block (>= 0.4)
+    ast (2.4.2)
+    attr_required (1.0.1)
+    backports (3.24.1)
+    base64 (0.1.1)
+    bcrypt (3.1.19)
+    better_errors (2.10.1)
+      erubi (>= 1.0.0)
+      rack (>= 0.9.0)
+      rouge (>= 1.0.0)
+    bindata (2.4.15)
+    bindex (0.8.1)
+    binding_of_caller (1.0.0)
+      debug_inspector (>= 0.0.1)
+    bootsnap (1.16.0)
+      msgpack (~> 1.2)
+    brakeman (6.0.1)
+    builder (3.2.4)
+    byebug (11.1.3)
+    capybara (3.39.2)
+      addressable
+      matrix
+      mini_mime (>= 0.1.3)
+      nokogiri (~> 1.8)
+      rack (>= 1.6.0)
+      rack-test (>= 0.6.3)
+      regexp_parser (>= 1.5, < 3.0)
+      xpath (~> 3.2)
+    choice (0.2.0)
+    coderay (1.1.3)
+    combine_pdf (1.0.23)
+      matrix
+      ruby-rc4 (>= 0.1.5)
+    concurrent-ruby (1.2.2)
+    contentful (2.17.0)
+      http (> 0.8, < 6.0)
+      multi_json (~> 1)
+    contentful-management (2.13.1)
+      http (> 1.0, < 5.0)
+      json (>= 1.8, < 3.0)
+      multi_json (~> 1)
+    contentful_model (1.3.0)
+      activesupport
+      contentful (~> 2.7)
+      contentful-management (~> 2.0)
+      redcarpet
+    contentful_rails (0.5.0)
+      contentful_model (~> 1.0)
+      rails (>= 4.2)
+      redcarpet (~> 3.2)
+    crass (1.0.6)
+    cssbundling-rails (1.3.3)
+      railties (>= 6.0.0)
+    date (3.3.3)
+    debug (1.8.0)
+      irb (>= 1.5.0)
+      reline (>= 0.3.1)
+    debug_inspector (1.1.0)
+    declarative (0.0.20)
+    device_detector (1.1.1)
+    devise (4.9.2)
+      bcrypt (~> 3.0)
+      orm_adapter (~> 0.1)
+      railties (>= 4.1.0)
+      responders
+      warden (~> 1.2.3)
+    dibber (0.7.0)
+    diff-lcs (1.5.0)
+    digest-crc (0.6.5)
+      rake (>= 12.0.0, < 14.0.0)
+    docile (1.4.0)
+    domain_name (0.5.20190701)
+      unf (>= 0.0.5, < 1.0.0)
+    dotenv (2.8.1)
+    dotenv-rails (2.8.1)
+      dotenv (= 2.8.1)
+      railties (>= 3.2)
+    dry-core (1.0.1)
+      concurrent-ruby (~> 1.0)
+      zeitwerk (~> 2.6)
+    dry-inflector (1.0.0)
+    dry-initializer (3.1.1)
+    dry-logic (1.5.0)
+      concurrent-ruby (~> 1.0)
+      dry-core (~> 1.0, < 2)
+      zeitwerk (~> 2.6)
+    dry-struct (1.6.0)
+      dry-core (~> 1.0, < 2)
+      dry-types (>= 1.7, < 2)
+      ice_nine (~> 0.11)
+      zeitwerk (~> 2.6)
+    dry-types (1.7.1)
+      concurrent-ruby (~> 1.0)
+      dry-core (~> 1.0)
+      dry-inflector (~> 1.0)
+      dry-logic (~> 1.4)
+      zeitwerk (~> 2.6)
+    erubi (1.12.0)
+    et-orbi (1.2.7)
+      tzinfo
+    factory_bot (6.2.1)
+      activesupport (>= 5.0.0)
+    factory_bot_rails (6.2.0)
+      factory_bot (~> 6.2.0)
+      railties (>= 5.0.0)
+    faker (3.2.1)
+      i18n (>= 1.8.11, < 2)
+    faraday (2.7.11)
+      base64
+      faraday-net_http (>= 2.0, < 3.1)
+      ruby2_keywords (>= 0.0.4)
+    faraday-follow_redirects (0.3.0)
+      faraday (>= 1, < 3)
+    faraday-net_http (3.0.2)
+    ffi (1.16.3)
+    ffi-compiler (1.0.1)
+      ffi (>= 1.0.0)
+      rake
+    foreman (0.87.2)
+    fugit (1.8.1)
+      et-orbi (~> 1, >= 1.2.7)
+      raabro (~> 1.4)
+    globalid (1.2.1)
+      activesupport (>= 6.1)
+    google-apis-core (0.11.1)
+      addressable (~> 2.5, >= 2.5.1)
+      googleauth (>= 0.16.2, < 2.a)
+      httpclient (>= 2.8.1, < 3.a)
+      mini_mime (~> 1.0)
+      representable (~> 3.0)
+      retriable (>= 2.0, < 4.a)
+      rexml
+      webrick
+    google-apis-iamcredentials_v1 (0.17.0)
+      google-apis-core (>= 0.11.0, < 2.a)
+    google-apis-storage_v1 (0.19.0)
+      google-apis-core (>= 0.9.0, < 2.a)
+    google-cloud-core (1.6.0)
+      google-cloud-env (~> 1.0)
+      google-cloud-errors (~> 1.0)
+    google-cloud-env (1.6.0)
+      faraday (>= 0.17.3, < 3.0)
+    google-cloud-errors (1.3.1)
+    google-cloud-storage (1.44.0)
+      addressable (~> 2.8)
+      digest-crc (~> 0.4)
+      google-apis-iamcredentials_v1 (~> 0.1)
+      google-apis-storage_v1 (~> 0.19.0)
+      google-cloud-core (~> 1.6)
+      googleauth (>= 0.16.2, < 2.a)
+      mini_mime (~> 1.0)
+    googleauth (1.8.1)
+      faraday (>= 0.17.3, < 3.a)
+      jwt (>= 1.4, < 3.0)
+      multi_json (~> 1.11)
+      os (>= 0.9, < 2.0)
+      signet (>= 0.16, < 2.a)
+    govuk-components (4.1.1)
+      html-attributes-utils (~> 1.0.0, >= 1.0.0)
+      pagy (~> 6.0)
+      view_component (>= 3.3, < 3.6)
+    govuk_design_system_formbuilder (4.1.1)
+      actionview (>= 6.1)
+      activemodel (>= 6.1)
+      activesupport (>= 6.1)
+      html-attributes-utils (~> 1)
+    govuk_markdown (2.0.1)
+      activesupport
+      redcarpet
+    govuk_notify_rails (2.2.0)
+      notifications-ruby-client (~> 5.1)
+      rails (>= 4.1.0)
+    grover (1.1.5)
+      combine_pdf (~> 1.0)
+      nokogiri (~> 1.0)
+    hashie (5.0.0)
+    html-attributes-utils (1.0.2)
+      activesupport (>= 6.1.4.4)
+    http (4.4.1)
+      addressable (~> 2.3)
+      http-cookie (~> 1.0)
+      http-form_data (~> 2.2)
+      http-parser (~> 1.2.0)
+    http-cookie (1.0.5)
+      domain_name (~> 0.5)
+    http-form_data (2.3.0)
+    http-parser (1.2.3)
+      ffi-compiler (>= 1.0, < 2.0)
+    httpclient (2.8.3)
+    i18n (1.14.1)
+      concurrent-ruby (~> 1.0)
+    ice_nine (0.11.2)
+    importmap-rails (1.2.1)
+      actionpack (>= 6.0.0)
+      railties (>= 6.0.0)
+    io-console (0.6.0)
+    irb (1.8.1)
+      rdoc
+      reline (>= 0.3.8)
+    jbuilder (2.11.5)
+      actionview (>= 5.0.0)
+      activesupport (>= 5.0.0)
+    jsbundling-rails (1.2.1)
+      railties (>= 6.0.0)
+    json (2.6.3)
+    json-jwt (1.16.3)
+      activesupport (>= 4.2)
+      aes_key_wrap
+      bindata
+      faraday (~> 2.0)
+      faraday-follow_redirects
+    jwt (2.7.1)
+    language_server-protocol (3.17.0.3)
+    launchy (2.5.2)
+      addressable (~> 2.8)
+    loofah (2.21.3)
+      crass (~> 1.0.2)
+      nokogiri (>= 1.12.0)
+    mail (2.8.1)
+      mini_mime (>= 0.1.1)
+      net-imap
+      net-pop
+      net-smtp
+    marcel (1.0.2)
+    matrix (0.4.2)
+    method_source (1.0.0)
+    mini_mime (1.1.5)
+    mini_portile2 (2.8.4)
+    minitest (5.20.0)
+    msgpack (1.7.2)
+    multi_json (1.15.0)
+    net-imap (0.4.0)
+      date
+      net-protocol
+    net-pop (0.1.2)
+      net-protocol
+    net-protocol (0.2.1)
+      timeout
+    net-smtp (0.4.0)
+      net-protocol
+    nio4r (2.5.9)
+    nokogiri (1.15.4)
+      mini_portile2 (~> 2.8.2)
+      racc (~> 1.4)
+    nokogiri (1.15.4-aarch64-linux)
+      racc (~> 1.4)
+    nokogiri (1.15.4-arm64-darwin)
+      racc (~> 1.4)
+    nokogiri (1.15.4-x86_64-darwin)
+      racc (~> 1.4)
+    nokogiri (1.15.4-x86_64-linux)
+      racc (~> 1.4)
+    notifications-ruby-client (5.4.0)
+      jwt (>= 1.5, < 3)
+    omniauth (2.1.1)
+      hashie (>= 3.4.6)
+      rack (>= 2.2.3)
+      rack-protection
+    omniauth-rails_csrf_protection (1.0.1)
+      actionpack (>= 4.2)
+      omniauth (~> 2.0)
+    omniauth_openid_connect (0.7.1)
+      omniauth (>= 1.9, < 3)
+      openid_connect (~> 2.2)
+    openid_connect (2.2.0)
+      activemodel
+      attr_required (>= 1.0.0)
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      json-jwt (>= 1.16)
+      net-smtp
+      rack-oauth2 (~> 2.2)
+      swd (~> 2.0)
+      tzinfo
+      validate_email
+      validate_url
+      webfinger (~> 2.0)
+    orm_adapter (0.5.0)
+    os (1.1.4)
+    pagy (6.1.0)
+    parallel (1.23.0)
+    parser (3.2.2.4)
+      ast (~> 2.4.1)
+      racc
+    pg (1.5.4)
+    pry (0.14.2)
+      coderay (~> 1.1)
+      method_source (~> 1.0)
+    pry-byebug (3.10.1)
+      byebug (~> 11.0)
+      pry (>= 0.13, < 0.15)
+    pry-doc (1.4.0)
+      pry (~> 0.11)
+      yard (~> 0.9.11)
+    pry-rails (0.3.9)
+      pry (>= 0.10.4)
+    psych (5.1.0)
+      stringio
+    public_suffix (5.0.3)
+    puma (6.4.0)
+      nio4r (~> 2.0)
+    que (2.2.1)
+    que-scheduler (4.4.0)
+      activesupport (>= 5.0)
+      fugit (~> 1.1, >= 1.1.8)
+      hashie (>= 3, < 6)
+      que (>= 0.14, < 3.0.0)
+    raabro (1.4.0)
+    racc (1.7.1)
+    rack (2.2.8)
+    rack-oauth2 (2.2.0)
+      activesupport
+      attr_required
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      json-jwt (>= 1.11.0)
+      rack (>= 2.1.0)
+    rack-protection (3.1.0)
+      rack (~> 2.2, >= 2.2.4)
+    rack-test (2.1.0)
+      rack (>= 1.3)
+    rails (7.0.8)
+      actioncable (= 7.0.8)
+      actionmailbox (= 7.0.8)
+      actionmailer (= 7.0.8)
+      actionpack (= 7.0.8)
+      actiontext (= 7.0.8)
+      actionview (= 7.0.8)
+      activejob (= 7.0.8)
+      activemodel (= 7.0.8)
+      activerecord (= 7.0.8)
+      activestorage (= 7.0.8)
+      activesupport (= 7.0.8)
+      bundler (>= 1.15.0)
+      railties (= 7.0.8)
+    rails-dom-testing (2.2.0)
+      activesupport (>= 5.0.0)
+      minitest
+      nokogiri (>= 1.6)
+    rails-erd (1.7.2)
+      activerecord (>= 4.2)
+      activesupport (>= 4.2)
+      choice (~> 0.2.0)
+      ruby-graphviz (~> 1.2)
+    rails-html-sanitizer (1.6.0)
+      loofah (~> 2.21)
+      nokogiri (~> 1.14)
+    railties (7.0.8)
+      actionpack (= 7.0.8)
+      activesupport (= 7.0.8)
+      method_source
+      rake (>= 12.2)
+      thor (~> 1.0)
+      zeitwerk (~> 2.5)
+    rainbow (3.1.1)
+    rake (13.0.6)
+    rdoc (6.5.0)
+      psych (>= 4.0.0)
+    redcarpet (3.6.0)
+    regexp_parser (2.8.1)
+    reline (0.3.9)
+      io-console (~> 0.5)
+    representable (3.2.0)
+      declarative (< 0.1.0)
+      trailblazer-option (>= 0.1.1, < 0.2.0)
+      uber (< 0.2.0)
+    responders (3.1.0)
+      actionpack (>= 5.2)
+      railties (>= 5.2)
+    retriable (3.1.2)
+    rexml (3.2.6)
+    rouge (4.1.3)
+    rspec-core (3.12.2)
+      rspec-support (~> 3.12.0)
+    rspec-expectations (3.12.3)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-mocks (3.12.6)
+      diff-lcs (>= 1.2.0, < 2.0)
+      rspec-support (~> 3.12.0)
+    rspec-rails (6.0.3)
+      actionpack (>= 6.1)
+      activesupport (>= 6.1)
+      railties (>= 6.1)
+      rspec-core (~> 3.12)
+      rspec-expectations (~> 3.12)
+      rspec-mocks (~> 3.12)
+      rspec-support (~> 3.12)
+    rspec-support (3.12.1)
+    rubocop (1.55.0)
+      json (~> 2.3)
+      language_server-protocol (>= 3.17.0)
+      parallel (~> 1.10)
+      parser (>= 3.2.2.3)
+      rainbow (>= 2.2.2, < 4.0)
+      regexp_parser (>= 1.8, < 3.0)
+      rexml (>= 3.2.5, < 4.0)
+      rubocop-ast (>= 1.28.1, < 2.0)
+      ruby-progressbar (~> 1.7)
+      unicode-display_width (>= 2.4.0, < 3.0)
+    rubocop-ast (1.29.0)
+      parser (>= 3.2.1.0)
+    rubocop-capybara (2.19.0)
+      rubocop (~> 1.41)
+    rubocop-factory_bot (2.24.0)
+      rubocop (~> 1.33)
+    rubocop-govuk (4.12.0)
+      rubocop (= 1.55.0)
+      rubocop-ast (= 1.29.0)
+      rubocop-rails (= 2.20.2)
+      rubocop-rake (= 0.6.0)
+      rubocop-rspec (= 2.22.0)
+    rubocop-performance (1.19.1)
+      rubocop (>= 1.7.0, < 2.0)
+      rubocop-ast (>= 0.4.0)
+    rubocop-rails (2.20.2)
+      activesupport (>= 4.2.0)
+      rack (>= 1.1)
+      rubocop (>= 1.33.0, < 2.0)
+    rubocop-rake (0.6.0)
+      rubocop (~> 1.0)
+    rubocop-rspec (2.22.0)
+      rubocop (~> 1.33)
+      rubocop-capybara (~> 2.17)
+      rubocop-factory_bot (~> 2.22)
+    ruby-graphviz (1.2.5)
+      rexml
+    ruby-progressbar (1.13.0)
+    ruby-rc4 (0.1.5)
+    ruby2_keywords (0.0.5)
+    rubyzip (2.3.2)
+    safely_block (0.4.0)
+    selenium-webdriver (4.13.1)
+      rexml (~> 3.2, >= 3.2.5)
+      rubyzip (>= 1.2.2, < 3.0)
+      websocket (~> 1.0)
+    sentry-rails (5.11.0)
+      railties (>= 5.0)
+      sentry-ruby (~> 5.11.0)
+    sentry-ruby (5.11.0)
+      concurrent-ruby (~> 1.0, >= 1.0.2)
+    show_me_the_cookies (6.0.0)
+      capybara (>= 2, < 4)
+    signet (0.18.0)
+      addressable (~> 2.8)
+      faraday (>= 0.17.5, < 3.a)
+      jwt (>= 1.5, < 3.0)
+      multi_json (~> 1.10)
+    simplecov (0.22.0)
+      docile (~> 1.1)
+      simplecov-html (~> 0.11)
+      simplecov_json_formatter (~> 0.1)
+    simplecov-html (0.12.3)
+    simplecov_json_formatter (0.1.4)
+    site_prism (4.0.3)
+      addressable (~> 2.8)
+      capybara (~> 3.27)
+      site_prism-all_there (~> 2.0)
+    site_prism-all_there (2.0.2)
+    sitemap_generator (6.3.0)
+      builder (~> 3.0)
+    slim (5.1.1)
+      temple (~> 0.10.0)
+      tilt (>= 2.1.0)
+    slim-rails (3.6.2)
+      actionpack (>= 3.1)
+      railties (>= 3.1)
+      slim (>= 3.0, < 6.0, != 5.0.0)
+    sprockets (4.2.1)
+      concurrent-ruby (~> 1.0)
+      rack (>= 2.2.4, < 4)
+    sprockets-rails (3.4.2)
+      actionpack (>= 5.2)
+      activesupport (>= 5.2)
+      sprockets (>= 3.0.0)
+    stimulus-rails (1.2.2)
+      railties (>= 6.0.0)
+    stringio (3.0.8)
+    swd (2.0.2)
+      activesupport (>= 3)
+      attr_required (>= 0.0.5)
+      faraday (~> 2.0)
+      faraday-follow_redirects
+    temple (0.10.3)
+    thor (1.2.2)
+    tilt (2.3.0)
+    timeout (0.4.0)
+    trailblazer-option (0.1.2)
+    turbo-rails (1.4.0)
+      actionpack (>= 6.0.0)
+      activejob (>= 6.0.0)
+      railties (>= 6.0.0)
+    tzinfo (2.0.6)
+      concurrent-ruby (~> 1.0)
+    uber (0.1.0)
+    unf (0.1.4)
+      unf_ext
+    unf_ext (0.0.8.2)
+    unicode-display_width (2.5.0)
+    validate_email (0.1.6)
+      activemodel (>= 3.0)
+      mail (>= 2.2.5)
+    validate_url (1.0.15)
+      activemodel (>= 3.0.0)
+      public_suffix
+    view_component (3.5.0)
+      activesupport (>= 5.2.0, < 8.0)
+      concurrent-ruby (~> 1.0)
+      method_source (~> 1.0)
+    warden (1.2.9)
+      rack (>= 2.0.9)
+    web-console (4.2.1)
+      actionview (>= 6.0.0)
+      activemodel (>= 6.0.0)
+      bindex (>= 0.4.0)
+      railties (>= 6.0.0)
+    webfinger (2.1.2)
+      activesupport
+      faraday (~> 2.0)
+      faraday-follow_redirects
+    webrick (1.8.1)
+    websocket (1.2.10)
+    websocket-driver (0.7.6)
+      websocket-extensions (>= 0.1.0)
+    websocket-extensions (0.1.5)
+    xpath (3.2.0)
+      nokogiri (~> 1.8)
+    yard (0.9.34)
+    yard-junk (0.0.9)
+      backports (>= 3.18)
+      rainbow
+      yard
+    zeitwerk (2.6.12)
+
+PLATFORMS
+  aarch64-linux-musl
+  arm64-darwin-20
+  arm64-darwin-21
+  arm64-darwin-22
+  ruby
+  x86_64-darwin-19
+  x86_64-darwin-20
+  x86_64-darwin-21
+  x86_64-darwin-22
+  x86_64-linux
+
+DEPENDENCIES
+  ahoy_matey
+  better_errors
+  binding_of_caller
+  bootsnap
+  brakeman
+  capybara
+  contentful_rails
+  cssbundling-rails
+  debug
+  devise
+  dibber
+  dotenv-rails
+  dry-core
+  dry-inflector
+  dry-initializer
+  dry-struct
+  dry-types
+  factory_bot_rails
+  faker
+  foreman
+  google-cloud-storage
+  govuk-components
+  govuk_design_system_formbuilder
+  govuk_markdown
+  govuk_notify_rails
+  grover
+  importmap-rails
+  jbuilder
+  jsbundling-rails
+  jwt
+  launchy
+  omniauth-rails_csrf_protection
+  omniauth_openid_connect
+  pg
+  pry-byebug
+  pry-doc
+  pry-rails
+  puma
+  que-scheduler
+  rails
+  rails-erd
+  redcarpet
+  rspec-rails
+  rubocop-govuk
+  rubocop-performance
+  selenium-webdriver
+  sentry-rails
+  sentry-ruby
+  show_me_the_cookies
+  simplecov
+  site_prism
+  sitemap_generator
+  slim-rails
+  sprockets-rails
+  stimulus-rails
+  turbo-rails
+  tzinfo-data
+  web-console
+  yard-junk
+
+RUBY VERSION
+   ruby 3.2.2p53
+
+BUNDLED WITH
+   2.3.26

From 48f4ae55aa6a2b473b80efdb367a34189d887d8c Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 1 Nov 2023 09:26:46 +0000
Subject: [PATCH 015/122] update migration number

---
 db/migrate/20231031094611_add_id_token_to_users.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/db/migrate/20231031094611_add_id_token_to_users.rb b/db/migrate/20231031094611_add_id_token_to_users.rb
index 47b69f094..1423064b7 100644
--- a/db/migrate/20231031094611_add_id_token_to_users.rb
+++ b/db/migrate/20231031094611_add_id_token_to_users.rb
@@ -1,4 +1,4 @@
-class AddIdTokenToUsers < ActiveRecord::Migration[7.1]
+class AddIdTokenToUsers < ActiveRecord::Migration[7.0]
   def change
     add_column :users, :id_token, :string
     add_index :users, :id_token, unique: true

From 7313839ff0455bbdda6aa8c474202632476b98ec Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 1 Nov 2023 10:55:14 +0000
Subject: [PATCH 016/122] update localisations and add all text to cms

---
 .gitignore                           | 4 ----
 app/models/user.rb                   | 2 +-
 app/services/gov_one_auth_service.rb | 8 ++++++--
 app/views/gov_one/info.html.slim     | 7 ++-----
 config/locales/en.yml                | 2 ++
 5 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/.gitignore b/.gitignore
index ba33dc4ad..8ca8af16b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,10 +57,6 @@ localhost.crt
 localhost.key
 nscacert.pem
 
-# gov one login keys
-private_key.pem
-public_key.pem
-
 # Yarn files
 .yarn/*
 
diff --git a/app/models/user.rb b/app/models/user.rb
index 72aef162b..d374a1f63 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -25,7 +25,7 @@ def self.dashboard_headers
   # @param gov_one_id [String]
   # @return [User]
   def self.create_from_gov_one(email:, gov_one_id:)
-    user = User.new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now)
+    user = new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now)
     user.save!(validate: false)
     user
   end
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 446449d2c..e5e9dfaae 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -13,8 +13,7 @@ def tokens
       client_assertion_type: ENV['GOV_ONE_CLIENT_ASSERTION_TYPE'],
       client_assertion: jwt_assertion,
     }
-
-    token_uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/token")
+    
     http = build_http(token_uri)
 
     token_request = Net::HTTP::Post.new(token_uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })
@@ -54,6 +53,11 @@ def decode_id_token(token)
 
 private
 
+  # @return [URI]
+  def token_uri
+    URI.parse("#{ENV['GOV_ONE_BASE_URI']}/token")
+  end
+
   # @param uri [URI]
   # @return [Net::HTTP]
   def build_http(uri)
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index 611b35e4f..da7a2602f 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -3,15 +3,12 @@
 - content_for :page_title do
   = html_title t('gov-one.info.title')
 
-.h1.govuk-heading-xl
-  = t('gov-one.info.title')
-
 .govuk-grid-row
   .govuk-grid-column-one-half
     = m('gov-one-info.body')
 
     - if current_user
-      = govuk_button_link_to 'Sign out of Gov One Login', logout_uri
+      = govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri
     - else
-      = govuk_button_link_to 'Sign in with Gov One Login', login_uri
+      = govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri
   
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index d8c8028b0..69b7c3d60 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -566,6 +566,8 @@ en:
   gov-one:
     info:
       title: Gov One Login
+      sign-in-button: Sign in with Gov One Login
+      sign-out-button: Sign out of Gov One Login
 
   # /settings/cookie-policy
   cookie_policy:

From 5cf8cc62ed906b2556a57c2bfdb76f4aa1abbf11 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 1 Nov 2023 11:07:48 +0000
Subject: [PATCH 017/122] update localisations

---
 config/locales/en.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/config/locales/en.yml b/config/locales/en.yml
index 69b7c3d60..463dc2a39 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -566,6 +566,10 @@ en:
   gov-one:
     info:
       title: Gov One Login
+      body: |
+        # Gov One Login
+
+        This is some information about Gov One Login... 
       sign-in-button: Sign in with Gov One Login
       sign-out-button: Sign out of Gov One Login
 

From 66170decebdc3c1824e326e0d42d1283970580d6 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 1 Nov 2023 11:09:06 +0000
Subject: [PATCH 018/122] rubocop

---
 app/services/gov_one_auth_service.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index e5e9dfaae..80dc7757c 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -13,7 +13,7 @@ def tokens
       client_assertion_type: ENV['GOV_ONE_CLIENT_ASSERTION_TYPE'],
       client_assertion: jwt_assertion,
     }
-    
+
     http = build_http(token_uri)
 
     token_request = Net::HTTP::Post.new(token_uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })

From 51b8acd33018b7d25cf0daeb1640f6b9691ce22a Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 1 Nov 2023 12:56:25 +0000
Subject: [PATCH 019/122] update omniauth callbacks controller

---
 app/controllers/users/omniauth_callbacks_controller.rb | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 12eb552dc..75df54064 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -48,15 +48,18 @@ def error_redirect
   # @return [User]
   def find_or_create_user(email, id)
     existing_user = User.find_by(email: email)
-
-    if existing_user
+    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.update!(email: email)
     else
-      existing_user = User.find_by(gov_one_id: id) || User.create_from_gov_one(email: email, gov_one_id: id)
+      return User.create_from_gov_one(email: email, gov_one_id: id)
     end
     existing_user
   end
 
+  # @return [String]
   def after_sign_in_path_for(resource)
     if resource.registration_complete?
       if resource.display_whats_new?

From 70816b5307a89d8b3ae40d6ccb0a626ea8cccd94 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 2 Nov 2023 09:30:51 +0000
Subject: [PATCH 020/122] update credentials

---
 app/helpers/gov_one_helper.rb              | 23 +++++++++++++++++++---
 app/services/gov_one_auth_service.rb       | 11 +++++++----
 config/application.rb                      |  2 ++
 config/credentials/development.yml.enc     |  2 +-
 config/credentials/production.yml.enc      |  2 +-
 config/credentials/test.yml.enc            |  2 +-
 config/initializers/devise.rb              |  2 +-
 spec/services/gov_one_auth_service_spec.rb |  2 --
 8 files changed, 33 insertions(+), 13 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index afdc3da79..58530c977 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -2,13 +2,30 @@ module GovOneHelper
   # @return [String]
   def login_uri
     state = SecureRandom.uuid
-    nonce = SecureRandom.uuid
     session[:gov_one_auth_state] = state
-    "#{ENV['GOV_ONE_AUTH_URI']}&nonce=#{nonce}&state=#{state}"
+
+    params = {
+      response_type: 'code',
+      scope: 'email openid',
+      client_id: Rails.application.config.gov_one_client_id,
+      nonce: SecureRandom.uuid,
+      state: state,
+    }
+
+    uri = URI.parse("#{Rails.application.config.gov_one_base_uri}/authorize")
+    uri.query = URI.encode_www_form(params)
+    "#{uri.to_s}&redirect_uri=#{ENV['GOV_ONE_REDIRECT_URI']}"
   end
 
   # @return [String]
   def logout_uri
-    "#{ENV['GOV_ONE_SIGN_OUT_URI']}?id_token_hint=#{session[:id_token]}&post_logout_redirect_uri=#{ENV['GOV_ONE_SIGN_OUT_REDIRECT_URI']}&state=#{SecureRandom.uuid}"
+    params = {
+      id_token_hint: session[:id_token],
+      state: SecureRandom.uuid,
+    }
+
+    uri = URI.parse("#{Rails.application.config.gov_one_base_uri}/logout")
+    uri.query = URI.encode_www_form(params)
+    "#{uri.to_s}&post_logout_redirect_uri=#{ENV['GOV_ONE_LOGOUT_REDIRECT_URI']}"
   end
 end
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 80dc7757c..46521d504 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -10,7 +10,7 @@ def tokens
       grant_type: 'authorization_code',
       code: @code,
       redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
-      client_assertion_type: ENV['GOV_ONE_CLIENT_ASSERTION_TYPE'],
+      client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
       client_assertion: jwt_assertion,
     }
 
@@ -29,7 +29,6 @@ def tokens
   # @param access_token [String]
   # @return [Hash]
   def user_info(access_token)
-    userinfo_uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/userinfo")
     http = build_http(userinfo_uri)
 
     userinfo_request = Net::HTTP::Get.new(userinfo_uri.path, { 'Authorization' => "Bearer #{access_token}" })
@@ -58,6 +57,10 @@ def token_uri
     URI.parse("#{ENV['GOV_ONE_BASE_URI']}/token")
   end
 
+  def userinfo_uri
+    URI.parse("#{ENV['GOV_ONE_BASE_URI']}/userinfo")
+  end
+
   # @param uri [URI]
   # @return [Net::HTTP]
   def build_http(uri)
@@ -80,8 +83,8 @@ def jwt_assertion
 
     payload = {
       aud: "#{ENV['GOV_ONE_BASE_URI']}/token",
-      iss: ENV['GOV_ONE_CLIENT_ID'],
-      sub: ENV['GOV_ONE_CLIENT_ID'],
+      iss: Rails.application.config.gov_one_client_id,
+      sub: Rails.application.config.gov_one_client_id,
       exp: Time.zone.now.to_i + 5 * 60,
       jti: SecureRandom.uuid,
       iat: Time.zone.now.to_i,
diff --git a/config/application.rb b/config/application.rb
index 10db7302e..90a2dc030 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -68,7 +68,9 @@ class Application < Rails::Application
     config.contentful_environment             = ENV.fetch('CONTENTFUL_ENVIRONMENT', credentials.dig(:contentful, :environment))
 
     # Gov one
+    config.gov_one_base_uri = ENV.fetch('GOV_ONE_BASE_URI')
     config.gov_one_private_key = ENV.fetch('GOV_ONE_PRIVATE_KEY', credentials.dig(:gov_one, :private_key))
+    config.gov_one_client_id = ENV.fetch('GOV_ONE_CLIENT_ID', credentials.dig(:gov_one, :client_id))
 
     # @return [Boolean]
     def live?
diff --git a/config/credentials/development.yml.enc b/config/credentials/development.yml.enc
index b4fe3c9ae..4606457df 100644
--- a/config/credentials/development.yml.enc
+++ b/config/credentials/development.yml.enc
@@ -1 +1 @@
-Y+EgIqYzaAxckrOE38ArevcqOV3rUrkTDRDDPzNBJnT87ckbDtlB2/Uhyvv/n29dvTRtAX6eQXAflgZL1EmGuZgyTZoE42/nAgYJ3RxER5ALL+Qa8t9oxiZTRT4ujbe7lmw6R9dmUOV/zMaYnxKT6OczkmWmipyJoUxTtmeoQKwnee7JX8rn5g5Umpu+dWFiA9JXxiAWOlyu+fRWyLObTceE07dHlwEXioTQvzq+4xnhNQ8Tx3SOZhSQXSKM1awr/j5snKdW1T56A9CybOhkzTV6FKa+H1i4YteHUfAXUd2JfDahynNOkQ/bfMP3y2hNhKgmaWum8PDNqlBo+WSNyYfmilpxeuSCIFNtFFvL3uus9/lFlQUqkiTw0wqHWK+FawGXcViakgcix9Vc+nLOlVfWvHgzGZ0KS0BUfKZ0k+hP/xIhr+FgkXR/PSSpC48f192DFqcZX72yQqTiRxtEVqEOEhsrY80DQPMZ0kcMho8s0uLQuz87qj5z2uS1VP+S/eVNw9D6F8W3FcCVw5UrNuJ+p3K/HA04cMLoQvIEtiMH3eUQJBlYdwxuRAdJmBuykr4vZo1eUu4t+vzRgSGHVLuWrlRSZS0FcJzJZkVyTmaf5iVm+jlMf1u9pQCFCQRycAePVW+MRJZ1lS3rbbcBvdtaSZT2ybRjvhHsfROp/H4/E0PBL2q13pBcLDaaHgXvJtjV/lKaFhm8XIDw5P8pmmzC5YXQ4k48n8zZwJcXBW8mAouvZBA9Lqcsjro0JnKFd+FJkvgNdq3pVcnYLXVRwsvy60HqKLbfCUW1XAYq4i/OQ+doUWShSuBeTdeMf34uuZ43fQaySXgloD1SUjbCx1tqEsZKxkQhRgxOkowS1fyYqV7OuvTKIZke23teWhOPR1I+nkLzUGO6l7HONrKZSC42/0jtakQKRknIROi+/vvkFP49Y5cQo3+d+HhPIdQrB+/HQluTgnTBjky9SVjGTFG9W700pXSxcdL0RzDjqd9yQSXURhFkm6A4HOrM20nitacVF+FaLF06MxCNwuwnUy5/NMDLawginnMQCScnNzfCxayXO7mwmYELowAdClonf8YFpejnKcSbMLcijTgW17ihh88ItjmLK1y4DBuUZ93AyD8rx8uWdTrEpQBCWehnCv6YzxoS2BLZqLPM1UUZjyAVQE/t4Mk7QOCuLyND7f8DVxPikxD0UH16wXOA2DSMOcxf/LfqKOlLsTedx2pEzzD+SJK9UeUED2OJh4aLzX8btuguiZQ+7DPvYwciG3lBktTUOiP5rCX4UxhDra9/Muw5DOKBqLIqoCn7AfXFuOPy+HpRGT+M8l/e3RpSMR6AO+5OERGpqNSRc+uuIzEMsw/JosyFXv+uyqQVksjwMYi25h9HGtAyEPNnDfluCifHAVY/zB0065kdN2xxorlrhXXico0/yVu6Z5M+KVpkKj2shqsCjCWY1iWy7EyGxi/TYw/JIspIlK+2xOLbbQfNytfNRU1I3TxXlEyc9IwOneqb3weZninvGMXym5t40NIudabTlTnKjlivissXRT3qdI80uY3L1I4aIIBqovkNXJAYrY7lBvczjqWenlu69xiAkQDjVslLBZPwlLZZB4lzIM1NgJWVPV+EqNM/rUD5uUhrDP8Cjo6JJ6khTgq3PhwbdmfBxwYFUJhUNbUQpa+F+YKj1xR+HaCIMETh48pOwpl4gMEqhaNb/XKsET0tRPYMaOCdw8GNgBTuyK4sYuFYwJHB0xXI8URnN3EkgwfSwbUW4zCxb/B1rJ3t1HK4+ou0eaEMtIPaqcqFEzdZtyF/cYlXyZZQFTXTqYaUoS6Ct3dSA2O5uRpjkDh4zInx2Bkxa+kODL0boaRjgWXb5i6DgvswszHig/ZYhTIZJ8zZLjZsOkcZzhU72O/prFeeqeocp4t9evYSIrsm5436pA1eRFhEcqkRA+zwhKjtQXSFRNBxh0ahBlWSHPY8PnYF+0nGnrNI1tDnKrVgJ2dHEBBv4dcB7FExlRf7VhkZOdYsnJcGuy3hMyHo7bVx8dtgK28tbgZAE6E8AfnrMCmgn8sgopW/Ds0Ne5zgFojoIfMtO4WTWCPUlELu3+qO/8j3Ig+V30IXayBY6KPS0i1OStnw9x8hrNfyXr425Z4hJMyGcd9ux89f7ulhiGh8BaXuEK1Iyktww8zW+2ipUZ28IblQ80x1ywXPSE2pvtiSDDV7EV8wjM+7qnsXgdoK8jTAw3H5aInXZeW+WHNH8H2Fu1RdeMUnXY3HNb5MkY+NTpEutu2ewiGzNkQIXoqGYO8TIdnjGphJE5Ui56K7v5kikuhk00KFN36Ts1oBBBUpCBUT3Om9qbnp4H4VxxnTERyJcE1iHeMjSxABnxhyPmJhOxIVXIr9MJRkWj3A/JgUEXFQASZIVXSJmxSljuhEu3Gck3ZCy9fD3pB3OzUt6AKSB1x6/HazjruYkoXpuLpybClf/uthj4LvPK5HlnkR2sUFjFwU5aSxTvuVVBQe8XWHa7Mp6zx5G2Ri2M0dQ+b3dBuMI1tvVNZw3t0z6FTtNzvyW2wM9ewg3LsDoqi7d8Gab2faOGTJTARabCiKJFBEnJL0X8mOWjnvP0+G1sVsfLvlTHkrbCl8d3udlzRuBSvxaH+NkkqUDFKmPqUpUff9jPrC2sgUDx8B8lhpG1AJ7MKEXncEdnM299d4+RC/sEGLeMlQCwJujQ9IptxDPX9JD04ZAuHRNGakpJqlD1nsQwmC2d6nJpP9DiXhp5T+kK7i7cmWhuIi5Bs9EeeotBN+V2F/atyjzFafQYmLgSv+EMGSdpiadvosugWFdyZrk+7wCIeU1md4LfEFK+FKhZdBaWVcMkvAncnYkPa6Xtalmt8RshRKndRYrgD6OZ3RE6p7HfPElhvqi1cN7vg+tGt2dzNSmCQWMe6MtogRZdGy4g/pl24f+Dc76e3FG2vF/x8yeFsxpK5gPw0nQ0mQvgamXjEAOiSjs8TP62NEzvP9idnHbwPNZ/yGMoToiZoKnWV2GPiL2KaLYjpE69vxotmUZgPuDUd2TtzvgXreeXR4JilgNXq+E9UyeQDZCgjEyXw054taxnxy32CKars/8Z0CFjxXbHztGOUDjeInwxZSzCIzEibMkffvMPGFeP9lts6FUsa3oOmJCG3NFAJ/ayyYSths+FzPAHAKkx6PN458wbJt0BylLR5qqvV8otofPSwVs6b1jf6cwxmp8O5iPWdacwqGJ4hkXkIa7s1V+2/68jkGN6XtZjJWjbvewrggpXChn5zPGo6kudtStY3RpvQ6hQNysbFWfwa1yHkeQXGmOxDNvTF5p7OPOKInc9Vmhe/490Er8iuv896KgUOtrzund1CGsjmFxrz5CN6DVplaqEzjUrPtqD0H1Yg/XSM7xMJGZ7vgeEAIBk9dEM21bAoxD5DOfUuPBOmSKTJdZ824uDHhJ5ScIBiTIjsUipetP5KjCUjrLzeS3t8J0anW9V7UP5wAZ2rEGQX/w8YlewErz0Sw6WnrQpryGbPFgLFPWBUwM5nZY+Gu0evrNWLunKtD6N7IMatb1YZuOyjDrEE3PqAHw6zgQANFqsd7F6voxiK4ku8YjIOFmMMfQyyNxO20DwtyQgLLbnOpqOAIpynEXNPQPn4eR0eotyGeiPrnYnzXc9LlSlMsJWKFCOR5DDrsxbcrAE+S4kyxf6CrbSSGNux6XzoSOFnH3qwXYbgBF4jwCbFvagndCutfTei9Q0oScdLZIkLYyee22so7TqaWFZ++uNz6NDltAJP7JxRtJly1pL8G+jP9g8SYPYP2gVwC/Ff3m0TqUK3ZdGDb7b6lTbJ+YR43KuwCTuFglomqxW9Vad5QFFmI7zQLB1Pvi18HbWoO8oxcXNC+69Hl4IPX7raEax2d9zviA2eeCjjRtjGvhmXOJgdY4WCrs/wYKuJAzBWxVXv8333dSEkbWzI1KSkdJN0zq3NHv4AQ/rPcj3ubjjr1bm8I0wr41aZIVxP9LLmbXCDi/+UJkRhtXRBZbZJgqGb0p9hMh4jzhl5zTy8how4D5lCDZM2bzgYcsKCrCCu+TufG1xZ3SisWiJeW9TsDXG4X8QGU4gZ0iI1TZGxHsMUpVHQgeZsovPMFPUm6ZV+kkXe/Br1UNcWtFNZPq3U/r2zZn9nGhqZ+aqN8nhIMrDsC2tInQOg/0mgPUSzzyQS/bUle4eJXXDsoFdSP15FdMwtD2F5nU3o/5Ye5i7CuNbZSXtQh7HzxYBqcgzEOFDgxu5GHiQdnvKClGzlR4n1ch/qHcKEkVxk8xFVvHv0P7jTZtM83MHBUNAVjofZLJJetVd4SQsb+1y0BcQwlFI4rn/AELQhkBZTaVc5evPM8cW2ia60YpfoAXh7uRSZqKQ1frtGljWQTqGZ8AJsNaIfoJPq+02BYXWC2ilbf7Y24G1fOXHY8lJgDNYk1QdTZYM1wtgq9NoCSMxxoajQ7SSniAcOdkH7uAAE2ZACCjELwZvJiQGoKL86RwfTV9U+/YDPbw+9qLKZK33znqx4NpYUDuS4fC3u1NE8VdT1OCJMjqG8ovNKOnvqlBpHxM7g4NFo0x55RWLCOpNQrZshgtwkiqGUAs2YO3fp6MxpcIgcn9E1wYtnx7+09hfxSG6lRpHtXoD8CfxtwQw2Xqq5iBGP8UTyH/6m9apfpHjuM+VaEOGe+GWjuBYXsCOfklXg2THp7vTbehKNfpscd0QjJLZbzMBpZnUVS9Qg4+Dw4/INggg1jOci/PpruqshJSURlVcXN2l5YxJ3LXmPC8c468tWbaMIW1dFBLWXU1cU/ZlXL0Ke4MK3WjwoM0Eya1zfA2cUNSHFAkMzmh5oY3l0Iwn49mvOPPdcsOmPqCm5PgofACIYMf0La7kcBKCoxk7V0mBSYKwa/TLSHJsX8YRTp0XVCOoGbeis77aXRlbNr9OYxloDhDD7eQOq8R9OWU5n/25oYwqGY7PXYfsxpb3427IBq65PU3CFWcnepJaHlkt+7uOyT/Nx4JapsUYVv30e+SGs6HQlUww/4u0lNgOz3OX2qFpSD0swFjcDcyT7IvlSk/vlpkMIqUo6CQ9eMgWK82hqQtxW4A6moNb+Ioy6GEUF4CRQmOqq2DQOYWiZ/jvz/Xt6y10fXEaZUf1r7v4ar/Kv58GohMcbGMqlQixRqsg/17ouCCkF/QFWN4LXeOO/IvRUprL21EyLLg44npGTek3LSUZ599IhlqtOgTy9AFBesU7ABzEvqcHkP2z8YNJphF+WXxVhY9FbJx5LmQKnWhPNRJ2mxXmGLL3f6NXn3micUDBlRtPKqBQ7Lcass+Oo8dAw0RYoAnIGg4dzYt8pLROSTXrdy7JNMW018oPKkbiYMy7LD+mnyR0o+3G4hfkSaaifXkh7tgnN7NZ8k0S8RIhHhC+bNrWJzXyo4cDhn/iu602Ud2go9Q4rrgT04C8Jhe7+Gcn/haeFVGLbZQq8U9LvbxrhlgVNnrMDi5p6LPZMm7QBDR7IW8xisuMu7DEksRRyZCF5phkb4I0sPfPpOYlkojgYu4CjHSw0BvJVkAmKOXI/UETfFtNNJV9pRzDf/DQLdXf2WI3MITsgnFqnzrZMwVYUbKl9PIOUbnpMBzH7qxZKaEsYS0wEB+c6lCrWmkEXXk7W7cj9CCxVHaGColbx5eNkpiEPdQj5ngmRRwDmMgSpJkGWYff1dT7bIG/b7Ibul4dbXCo/TRnWx9aZhEhqyV4omARp1JThMjuyuFwUMG7yZ8ULcuam5Pe/wOwoUxTQ6If7xtiAcjduOEXtuo6q2CBs6/9hk7wUB998OVReJHpdfOxg7k7lRt/QHPFjhchRFjOpzocFYjGcC1ZIymaXAsusumyHYbO6JLcUqyK99gpNjG5KTJsiNL05H/9d9LOz6f9+Kps4wGCFqS7RC4LJvCP1wOXGLvpr0sdl66Fc8MxSqYvagcdFFvLVUoE/MTcFgicAtIRzydjUfE9MHma5KJagAHAXl1rlBM9s51shI2kQaThACI52DB403RmTv9KQwql01eV9CJKXKGkqWPIKvFPJidGXhD8Nfv20Husmc0B6cshgiJ0OcDuTNvGCctBmkRZ/d+D5Bc9tDG4VAK9wBA2mpda8grurKr+kdn48mV2vX9LrTo8gpNoYGY/cn8z8vNNs8ymxj4q/MhV3F8Uh8Y5/sL60UOAY4eGw6FaysvA8c4A59mik6NymJ1AXMu8h377CY9wUPbXAWwperOo2asuRdBfVNCFKQro6axzZA8VjijsHZFmfa201vLSXbjLIyvPQgDUL4C5buWIbvkUSBNFVUKlscBA2qIy5dgCX/NzoR/ncLJ4du5dHFKCLn0H8RUsPvOD1E1BgCtm7pDHaduDtySLSxltgfN29UrE5M1X0qW8xRvSojGlwKR/ZU7RX0uCAlQHQASAOZz8SGzvmjYleTiimgX30Vyyyl1amjHXVgjTrqvsDuChw4he8J/yDNozqK1SJElBf4xnfxl+zoitxrzLbW17CxOnon+M4WVvemrbw0Hygv9ft4M0C36h62iLIlXzu+h/uaqdtbz0biqhMNlc+yOldN1HRRG54lt33ZGlMwRXptT9ed7vv3NrxZGbU2OVXLDf9CwzUxVaQwoYaMHM7WrOMD1I2atNRtEw+XHLXvsgDZ7xY807pEbKHHqP9xjFy32TrEV2cMP7wVQrKK7bW7loStYghYvSvKGhx+76Q3Mv1QEs77q+rv/BVBrLP7YRH4y35/ecakyiIydwO7YIdBB3Jyx7zEqmyP+AecZjHSmlDz76z7/sG9AqCf3oA8nS7H/O8ey3Pgnq9zT5qpvwAzQbCRgswhTDnTfykss9mVzm1eOEK7ZpIUGqnzf3HZS6D4Sj4TvGBolmhIYoMrKGh2nimiIf/1HwolZV+2Wwh1e90fCnuJKnNkurPw+29UXNtmicqfCir+/2RXW++7Giwaa3yFHwoA/ZvHA/087ORREm8WDosBoR+Q+0f0EvsblNfGKJ4d45+YWjlRt3RHCVHg1x+4CSwrEiSKf6n0wkVb9OQMnzymIKzaIyCB1a0RnBF4tCB13SY1zcIGt6jxQU65ZPhZYHxXPCO6oKGDAtgiu/sTGiYLprElfCUVIeVS8wfu0CPjB0B95GcNEMsfh/j+Iu6e1lY8qeT4NJQ5tAxX6FBBG+fXUhB1megQE+luQGHIgH6lTz6Xn+NSC7yaN8ffFPiK4Tl8UOE+pKOnx7hU3/cT6B2N5iAUGzhf8N3n0zKPlX1WOgEN8ZW4PBgSAuV3hKxjQcunoj5FZLHK+6GNIL2vSz+Jz6w09Vg54gz/tPnowWHt7EB6NIJDOYCqlWDz7b4Y/+eCRL8zvvwZccgt07bmFsISL6nAS/u97rQMYieE7N4E139GUsy+oYaTzQSWYFV6byao9nu1thDo91kF2/w+6F9wZ2r61+K+1gww83+luuT7vbWb9MgFTydxbvZg792tQFif0NPPTEYbv4aSUeYNFvJqheX++8JBQ1UqmxTw5LWpgm3icCDl8lIyGqjKeCgfl0BJ3SYrDKudvDfCK+CKLuMYPixkRz907huxcDeuDi7RP/HcY0lxA7HafE4LVDSOiIHgUcVwYAhMl8dFvKdz0InlXOtgaRGdX1+aoiRrHmTIGlS860iTPuob0VVTL8SIWvrQW963X7bU+ajwQkPnO7v/XHcsJg0L8hPt1ZGVpdcs8V85p6RuipLqsKeA7nu3gCau7S3SW8pZL8FnzUJLNbfij9ud1hMPQmqatYgO4nk27605ddn1zxB8XzvQTbWLY4vA49P3BoQQAxCx80QK2gzhWDpWAp9IjMlkYUeUjIA/xR0RKzFi6suJ7D6v0dTkltO7Um4BedfkdeXLO/BhPyrw0pBcLBbJVtXvEq0SfxL6XrSTqGQrz5NhnS3TSdBdZs15mQ+B15e3tXPuTNAzzHrA+GmDlf0vrKrPCxwu--bjV860iSph86biSM--eO2U1UjfoPVUyTzhWcIu9A==
\ No newline at end of file
+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--mE6HeB6Fsvd9DHxG--T8mDZYxmmz5GxmQ8ikSFTg==
\ No newline at end of file
diff --git a/config/credentials/production.yml.enc b/config/credentials/production.yml.enc
index a95dc679b..9dfe3025f 100644
--- a/config/credentials/production.yml.enc
+++ b/config/credentials/production.yml.enc
@@ -1 +1 @@
-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--o03/wyKMqS37wmEr--PGWkhEX04SURN12/rZeEDQ==
\ No newline at end of file
+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--D/uaMzAQH8aFuO+u--HkOCuZK3KJsvkGlaRQ45Gg==
\ No newline at end of file
diff --git a/config/credentials/test.yml.enc b/config/credentials/test.yml.enc
index 9f2b7b14a..e7e3894a0 100644
--- a/config/credentials/test.yml.enc
+++ b/config/credentials/test.yml.enc
@@ -1 +1 @@
-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--F7LPrF7SEIYJasNO--J/H0MGGLiOWbI22U2DGy8g==
\ No newline at end of file
+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--Jz3stjju1xhM5+A7--5uwznqznRm9MCJq4I4/q+w==
\ No newline at end of file
diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index c50ca7eb3..050e97991 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -314,7 +314,7 @@ def i18n_message(default = nil)
       port: 443,
       scheme: 'https',
       host: 'oidc.integration.account.gov.uk',
-      identifier: ENV['GOV_ONE_CLIENT_ID'],
+      identifier: Rails.application.config.gov_one_client_id,
       redirect_uri: 'users/auth/openid_connect/callback',
     },
     authorize_params: {
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index db2abbff8..3d9ce3c56 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -9,9 +9,7 @@
   before do
     allow(ENV).to receive(:[]).and_call_original
     allow(ENV).to receive(:[]).with('GOV_ONE_REDIRECT_URI').and_return('mock_redirect_uri')
-    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ASSERTION_TYPE').and_return('mock_assertion_type')
     allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('https://example.com')
-    allow(ENV).to receive(:[]).with('GOV_ONE_CLIENT_ID').and_return('mock_client_id')
     allow(mock_http).to receive(:request).and_return(mock_response)
     allow(Net::HTTP).to receive(:new).and_return(mock_http)
   end

From d4c6be6e188542cca9c80094ffb3ffdb7548d748 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 2 Nov 2023 09:55:36 +0000
Subject: [PATCH 021/122] update application config

---
 app/helpers/gov_one_helper.rb | 4 ++--
 config/application.rb         | 1 -
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 58530c977..a0ec1e447 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -12,7 +12,7 @@ def login_uri
       state: state,
     }
 
-    uri = URI.parse("#{Rails.application.config.gov_one_base_uri}/authorize")
+    uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/authorize")
     uri.query = URI.encode_www_form(params)
     "#{uri.to_s}&redirect_uri=#{ENV['GOV_ONE_REDIRECT_URI']}"
   end
@@ -24,7 +24,7 @@ def logout_uri
       state: SecureRandom.uuid,
     }
 
-    uri = URI.parse("#{Rails.application.config.gov_one_base_uri}/logout")
+    uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/logout")
     uri.query = URI.encode_www_form(params)
     "#{uri.to_s}&post_logout_redirect_uri=#{ENV['GOV_ONE_LOGOUT_REDIRECT_URI']}"
   end
diff --git a/config/application.rb b/config/application.rb
index 90a2dc030..39171dea3 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -68,7 +68,6 @@ class Application < Rails::Application
     config.contentful_environment             = ENV.fetch('CONTENTFUL_ENVIRONMENT', credentials.dig(:contentful, :environment))
 
     # Gov one
-    config.gov_one_base_uri = ENV.fetch('GOV_ONE_BASE_URI')
     config.gov_one_private_key = ENV.fetch('GOV_ONE_PRIVATE_KEY', credentials.dig(:gov_one, :private_key))
     config.gov_one_client_id = ENV.fetch('GOV_ONE_CLIENT_ID', credentials.dig(:gov_one, :client_id))
 

From c6c8ecacc4e504d49e010ef8185e07749108805b Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 2 Nov 2023 09:57:41 +0000
Subject: [PATCH 022/122] rubocop

---
 app/helpers/gov_one_helper.rb        | 4 ++--
 app/services/gov_one_auth_service.rb | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index a0ec1e447..4af428848 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -14,7 +14,7 @@ def login_uri
 
     uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/authorize")
     uri.query = URI.encode_www_form(params)
-    "#{uri.to_s}&redirect_uri=#{ENV['GOV_ONE_REDIRECT_URI']}"
+    "#{uri}&redirect_uri=#{ENV['GOV_ONE_REDIRECT_URI']}"
   end
 
   # @return [String]
@@ -26,6 +26,6 @@ def logout_uri
 
     uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/logout")
     uri.query = URI.encode_www_form(params)
-    "#{uri.to_s}&post_logout_redirect_uri=#{ENV['GOV_ONE_LOGOUT_REDIRECT_URI']}"
+    "#{uri}&post_logout_redirect_uri=#{ENV['GOV_ONE_LOGOUT_REDIRECT_URI']}"
   end
 end
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 46521d504..7dbcb215a 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -10,7 +10,7 @@ def tokens
       grant_type: 'authorization_code',
       code: @code,
       redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
-      client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
+      client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
       client_assertion: jwt_assertion,
     }
 

From a83da4e7325fda7d29d4f24c8abac57759bdedd0 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 6 Nov 2023 11:42:30 +0000
Subject: [PATCH 023/122] update env.example

---
 .env.example                         |  8 ++++++++
 app/services/gov_one_auth_service.rb | 21 ++++++++++++---------
 2 files changed, 20 insertions(+), 9 deletions(-)

diff --git a/.env.example b/.env.example
index 8f65f9bd3..629b0d7bc 100644
--- a/.env.example
+++ b/.env.example
@@ -81,3 +81,11 @@ E2E=true
 
 # Deployment environment
 ENVIRONMENT=
+
+# Base URI for Gov One Login requests
+GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk
+
+# Redirect URIs for Gov One Login redirects
+# Replace localhost:3000 with the correct application root path
+GOV_ONE_REDIRECT_URI=http://localhost:3000/users/auth/openid_connect/callback
+GOV_ONE_LOGOUT_REDIRECT_URI=http://localhost:3000/users/sign_out
\ No newline at end of file
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 7dbcb215a..7f8e39118 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -6,18 +6,10 @@ def initialize(code)
 
   # @return [Hash]
   def tokens
-    body = {
-      grant_type: 'authorization_code',
-      code: @code,
-      redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
-      client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
-      client_assertion: jwt_assertion,
-    }
-
     http = build_http(token_uri)
 
     token_request = Net::HTTP::Post.new(token_uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })
-    token_request.set_form_data(body)
+    token_request.set_form_data(token_body)
     token_response = http.request(token_request)
 
     JSON.parse(token_response.body)
@@ -92,4 +84,15 @@ def jwt_assertion
 
     JWT.encode payload, rsa_private, 'RS256'
   end
+
+  # @return [Hash]
+  def token_body
+    {
+      grant_type: 'authorization_code',
+      code: @code,
+      redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
+      client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
+      client_assertion: jwt_assertion,
+    }
+  end
 end

From 04a8a15ad84b73a305f701b16b330e5548ca0aad Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 6 Nov 2023 15:16:29 +0000
Subject: [PATCH 024/122] add gov one env variables to docker dev compose

---
 docker-compose.dev.yml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 7287b9819..6f104f3b5 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -21,6 +21,10 @@ services:
       - RAILS_ENV=development
       - DOMAIN=app:3000
       - RAILS_SERVE_STATIC_FILES=true
+      # gov one login
+      - GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk 
+      - GOV_ONE_REDIRECT_URI=http://localhost:3000/users/auth/openid_connect/callback
+      - GOV_ONE_LOGOUT_REDIRECT_URI=http://localhost:3000/users/sign_out
     volumes:
       - .:/srv
     tty: true

From 773178de60d063b1e23b023cab9470aec05273c7 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 7 Nov 2023 15:33:52 +0000
Subject: [PATCH 025/122] add feature flag and revert changes to
 application_helper

---
 .env.example                                  |  2 +
 .../users/omniauth_callbacks_controller.rb    | 18 +++++----
 app/helpers/application_helper.rb             | 39 +++++++++++--------
 app/services/gov_one_auth_service.rb          | 10 +++--
 config/application.rb                         |  5 +++
 config/routes.rb                              |  2 +-
 spec/services/gov_one_auth_service_spec.rb    |  2 +-
 7 files changed, 48 insertions(+), 30 deletions(-)

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

From c12af1bf8dc3e137ca0511ecfeb0cedfcb1dace8 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 7 Nov 2023 16:49:14 +0000
Subject: [PATCH 026/122] fix error from merge

---
 config/application.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/application.rb b/config/application.rb
index 76434eef5..f15e9ba5e 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -113,11 +113,11 @@ def debug?
     # @return [Boolean]
     def gov_one_login_enabled?
       Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN_ENABLED', false)]
+    end
 
      # @return [Boolean]
     def maintenance?
       Types::Params::Bool[ENV.fetch('MAINTENANCE', false)]
-
     end
 
     # @return [ActiveSupport::TimeWithZone]

From dcf4cf36d108a265d56a62946b5ac9e4e8745846 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 7 Nov 2023 17:13:18 +0000
Subject: [PATCH 027/122] rubocop

---
 app/controllers/users/omniauth_callbacks_controller.rb | 1 -
 config/application.rb                                  | 2 +-
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 8cf771764..f311ff375 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -1,6 +1,5 @@
 # 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
diff --git a/config/application.rb b/config/application.rb
index f15e9ba5e..08b3eb4b1 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -115,7 +115,7 @@ def gov_one_login_enabled?
       Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN_ENABLED', false)]
     end
 
-     # @return [Boolean]
+    # @return [Boolean]
     def maintenance?
       Types::Params::Bool[ENV.fetch('MAINTENANCE', false)]
     end

From e4f9f4fa9ed230f76a3df761335d72a53dc897bd Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 8 Nov 2023 15:58:51 +0000
Subject: [PATCH 028/122] refactor gov one auth service

---
 .../users/omniauth_callbacks_controller.rb    | 17 ++------
 app/helpers/application_helper.rb             | 39 ++++++++-----------
 app/helpers/gov_one_helper.rb                 | 14 +++++--
 app/models/user.rb                            | 15 +++++--
 app/services/gov_one_auth_service.rb          | 12 +++---
 config/application.rb                         |  2 +-
 config/routes.rb                              |  2 +-
 7 files changed, 49 insertions(+), 52 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index f311ff375..6a1b2b04f 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -3,7 +3,7 @@ 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? || Rails.application.gov_one_login_enabled?
+    return error_redirect unless valid_params? || Rails.application.gov_one_login?
 
     session.delete(:gov_one_auth_state)
 
@@ -17,9 +17,10 @@ def openid_connect
     gov_one_id = auth_service.decode_id_token(id_token)[0]['sub']
 
     user_info_response = auth_service.user_info(tokens_response['access_token'])
+    email = user_info_response['email']
     return error_redirect unless valid_user_info?(user_info_response)
 
-    gov_user = find_or_create_user(user_info_response['email'], gov_one_id)
+    gov_user = User.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
     sign_in_and_redirect gov_user if gov_user
   end
 
@@ -48,18 +49,6 @@ def error_redirect
     redirect_to root_path
   end
 
-  # @return [User]
-  def find_or_create_user(email, 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
-    existing_user
-  end
-
   # @return [String]
   def after_sign_in_path_for(resource)
     if resource.registration_complete?
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index b8f45ea20..d7b80bbf6 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,35 +1,28 @@
 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
-    govuk_header(classes: 'noprint') do |header|
-      header.with_navigation_item(text: 'Home', href: root_path)
-      header.with_custom_logo { custom_logo }
+    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])
       if user_signed_in?
-        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 } })
+        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: %w[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: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' })
       else
-        header.with_navigation_item(text: 'Sign in', href: new_user_session_path)
+        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: %w[dfe-header__navigation-item dfe-header-f-mob])
       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/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 4af428848..0dc2c1a00 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -12,8 +12,7 @@ def login_uri
       state: state,
     }
 
-    uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/authorize")
-    uri.query = URI.encode_www_form(params)
+    uri = uri_constructor('authorize', params)
     "#{uri}&redirect_uri=#{ENV['GOV_ONE_REDIRECT_URI']}"
   end
 
@@ -24,8 +23,15 @@ def logout_uri
       state: SecureRandom.uuid,
     }
 
-    uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/logout")
-    uri.query = URI.encode_www_form(params)
+    uri = uri_constructor('logout', params)
     "#{uri}&post_logout_redirect_uri=#{ENV['GOV_ONE_LOGOUT_REDIRECT_URI']}"
   end
+
+private
+
+  def uri_constructor(endpoint, params)
+    uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/#{endpoint}")
+    uri.query = URI.encode_www_form(params)
+    uri
+  end
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index eeffbf589..d05fad376 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -24,10 +24,17 @@ def self.dashboard_headers
   # @param email [String]
   # @param gov_one_id [String]
   # @return [User]
-  def self.create_from_gov_one(email:, gov_one_id:)
-    user = new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now)
-    user.save!(validate: false)
-    user
+  def self.find_or_create_from_gov_one(email:, gov_one_id:)
+    existing_user = find_by(email: email) || find_by(gov_one_id: gov_one_id)
+
+    if existing_user
+      existing_user.update!(email: email)
+      existing_user.update!(gov_one_id: gov_one_id) if existing_user.gov_one_id.nil?
+    else
+      existing_user = new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now)
+      existing_user.save!(validate: false)
+    end
+    existing_user
   end
 
   # Include default devise modules. Others available are:
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index bfda10fd8..eda7cb1d0 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -5,6 +5,8 @@ class GovOneAuthService
 
   option :code, Types::String
 
+  BASE_URL = ENV['GOV_ONE_BASE_URI'].freeze
+
   # @return [Hash]
   def tokens
     http = build_http(token_uri)
@@ -47,11 +49,11 @@ def decode_id_token(token)
 
   # @return [URI]
   def token_uri
-    URI.parse("#{ENV['GOV_ONE_BASE_URI']}/token")
+    URI.parse("#{BASE_URL}/token")
   end
 
   def userinfo_uri
-    URI.parse("#{ENV['GOV_ONE_BASE_URI']}/userinfo")
+    URI.parse("#{BASE_URL}/userinfo")
   end
 
   # @param uri [URI]
@@ -64,7 +66,7 @@ def build_http(uri)
 
   # @return [Hash]
   def jwks
-    discovery_url = "#{ENV['GOV_ONE_BASE_URI']}/.well-known/jwks.json"
+    discovery_url = "#{BASE_URL}/.well-known/jwks.json"
     uri = URI.parse(discovery_url)
     http = build_http(uri)
     response = http.request(Net::HTTP::Get.new(uri.path))
@@ -76,7 +78,7 @@ def jwt_assertion
     rsa_private = OpenSSL::PKey::RSA.new(Rails.application.config.gov_one_private_key)
 
     payload = {
-      aud: "#{ENV['GOV_ONE_BASE_URI']}/token",
+      aud: "#{BASE_URL}/token",
       iss: Rails.application.config.gov_one_client_id,
       sub: Rails.application.config.gov_one_client_id,
       exp: Time.zone.now.to_i + 5 * 60,
@@ -91,7 +93,7 @@ def jwt_assertion
   def token_body
     {
       grant_type: 'authorization_code',
-      code: @code,
+      code: code,
       redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
       client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
       client_assertion: jwt_assertion,
diff --git a/config/application.rb b/config/application.rb
index 08b3eb4b1..ab2578476 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -111,7 +111,7 @@ def debug?
     end
 
     # @return [Boolean]
-    def gov_one_login_enabled?
+    def gov_one_login?
       Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN_ENABLED', false)]
     end
 
diff --git a/config/routes.rb b/config/routes.rb
index 289c4f67a..0dc304b67 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' if Rails.application.gov_one_login_enabled?
+  get 'gov-one/info', to: 'gov_one#info' if Rails.application.gov_one_login?
 
   get '/404', to: 'errors#not_found', via: :all
   get '/422', to: 'errors#unprocessable_entity', via: :all

From ceaa9ddff65713e337822b842d91ec7e04a75e75 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 9 Nov 2023 15:12:16 +0000
Subject: [PATCH 029/122] add gov one documentation and refactor service

---
 .env.example                                  |  2 +-
 .../users/omniauth_callbacks_controller.rb    |  2 ++
 app/services/gov_one_auth_service.rb          | 24 +++++++++++++------
 config/application.rb                         |  5 +++-
 spec/services/gov_one_auth_service_spec.rb    |  5 ++--
 5 files changed, 26 insertions(+), 12 deletions(-)

diff --git a/.env.example b/.env.example
index e7f331853..380f3789d 100644
--- a/.env.example
+++ b/.env.example
@@ -82,7 +82,7 @@ E2E=true
 # Deployment environment
 ENVIRONMENT=
 
-GOV_ONE_LOGIN_ENABLED=true
+GOV_ONE_LOGIN=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 6a1b2b04f..bed63b459 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -1,4 +1,6 @@
 # Controller handling OmniAuth callbacks for user authentication.
+# This controller uses the GovOneAuthService to retrieve user informaton and create or sign in an user based on the email address or gov one id
+
 class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
   # This method is called by Devise after successful Gov One Login authentication
   # @return [nil]
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index eda7cb1d0..298983b7b 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -1,12 +1,17 @@
 # Service for interacting with Gov One Login
+# This service is initialised with an authorisation code and can be used to:
+# - exchange an authorisation code for tokens (access and id)
+# - exchange an access token for user info
+# - decode an id token to get the user's gov one id
+#
+# More information on the Gov One Login integration environment can be found here:
+# https://docs.sign-in.service.gov.uk/integrate-with-integration-environment/
 
 class GovOneAuthService
   extend Dry::Initializer
 
   option :code, Types::String
 
-  BASE_URL = ENV['GOV_ONE_BASE_URI'].freeze
-
   # @return [Hash]
   def tokens
     http = build_http(token_uri)
@@ -49,11 +54,17 @@ def decode_id_token(token)
 
   # @return [URI]
   def token_uri
-    URI.parse("#{BASE_URL}/token")
+    gov_one_uri('token')
   end
 
+  # @return [URI]
   def userinfo_uri
-    URI.parse("#{BASE_URL}/userinfo")
+    gov_one_uri('userinfo')
+  end
+
+  # @return [URI]
+  def gov_one_uri(endpoint)
+    URI.parse("#{Rails.application.config.gov_one_base_uri}/#{endpoint}")
   end
 
   # @param uri [URI]
@@ -66,8 +77,7 @@ def build_http(uri)
 
   # @return [Hash]
   def jwks
-    discovery_url = "#{BASE_URL}/.well-known/jwks.json"
-    uri = URI.parse(discovery_url)
+    uri = gov_one_uri('.well-known/jwks.json')
     http = build_http(uri)
     response = http.request(Net::HTTP::Get.new(uri.path))
     JSON.parse(response.body)
@@ -78,7 +88,7 @@ def jwt_assertion
     rsa_private = OpenSSL::PKey::RSA.new(Rails.application.config.gov_one_private_key)
 
     payload = {
-      aud: "#{BASE_URL}/token",
+      aud: token_uri.to_s,
       iss: Rails.application.config.gov_one_client_id,
       sub: Rails.application.config.gov_one_client_id,
       exp: Time.zone.now.to_i + 5 * 60,
diff --git a/config/application.rb b/config/application.rb
index ab2578476..02cdc1841 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -69,6 +69,9 @@ class Application < Rails::Application
     config.contentful_environment             = ENV.fetch('CONTENTFUL_ENVIRONMENT', credentials.dig(:contentful, :environment))
 
     # Gov one
+    config.gov_one_base_uri = ENV.fetch('GOV_ONE_BASE_URI', '#GOV_ONE_BASE_URI_env_var_missing')
+    config.gov_one_redirect_uri = ENV.fetch('GOV_ONE_REDIRECT_URI', '#GOV_ONE_REDIRECT_URI_env_var_missing')
+    config.gov_one_logout_redirect_uri = ENV.fetch('GOV_ONE_LOGOUT_REDIRECT_URI', '#GOV_ONE_LOGOUT_REDIRECT_URI_env_var_missing')
     config.gov_one_private_key = ENV.fetch('GOV_ONE_PRIVATE_KEY', credentials.dig(:gov_one, :private_key))
     config.gov_one_client_id = ENV.fetch('GOV_ONE_CLIENT_ID', credentials.dig(:gov_one, :client_id))
 
@@ -112,7 +115,7 @@ def debug?
 
     # @return [Boolean]
     def gov_one_login?
-      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN_ENABLED', false)]
+      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', false)]
     end
 
     # @return [Boolean]
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index 7cd6cb351..b686eea34 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -7,9 +7,8 @@
   let(:mock_http) { instance_double('Sentry::Net::HTTP') }
 
   before do
-    allow(ENV).to receive(:[]).and_call_original
-    allow(ENV).to receive(:[]).with('GOV_ONE_REDIRECT_URI').and_return('mock_redirect_uri')
-    allow(ENV).to receive(:[]).with('GOV_ONE_BASE_URI').and_return('https://example.com')
+    allow(Rails.application.config).to receive(:gov_one_base_uri).and_return('https://example.com')
+    allow(Rails.application.config).to receive(:gov_one_redirect_uri).and_return('mock_redirect_uri')
     allow(mock_http).to receive(:request).and_return(mock_response)
     allow(Net::HTTP).to receive(:new).and_return(mock_http)
   end

From 60597a709f1b3640f3c2d7ea02e8a8f996e04316 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 9 Nov 2023 15:33:31 +0000
Subject: [PATCH 030/122] refactor gov one helper and update yard

---
 app/helpers/gov_one_helper.rb        | 13 ++++++++-----
 app/services/gov_one_auth_service.rb |  1 +
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 0dc2c1a00..b4945c0d6 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -12,8 +12,8 @@ def login_uri
       state: state,
     }
 
-    uri = uri_constructor('authorize', params)
-    "#{uri}&redirect_uri=#{ENV['GOV_ONE_REDIRECT_URI']}"
+    uri = gov_one_uri('authorize', params)
+    "#{uri}&redirect_uri=#{Rails.application.config.gov_one_redirect_uri}"
   end
 
   # @return [String]
@@ -21,15 +21,18 @@ def logout_uri
     params = {
       id_token_hint: session[:id_token],
       state: SecureRandom.uuid,
+      post_logout_redirect_uri: Rails.application.config.gov_one_logout_redirect_uri,
     }
 
-    uri = uri_constructor('logout', params)
-    "#{uri}&post_logout_redirect_uri=#{ENV['GOV_ONE_LOGOUT_REDIRECT_URI']}"
+    uri = gov_one_uri('logout', params)
+    "#{uri}&post_logout_redirect_uri=#{Rails.application.config.gov_one_logout_redirect_uri}"
   end
 
 private
 
-  def uri_constructor(endpoint, params)
+# @param endpoint [String] the gov one endpoint
+# @param params [Hash] query params
+  def gov_one_uri(endpoint, params)
     uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/#{endpoint}")
     uri.query = URI.encode_www_form(params)
     uri
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 298983b7b..93029da85 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -62,6 +62,7 @@ def userinfo_uri
     gov_one_uri('userinfo')
   end
 
+  # @param endpoint [String]
   # @return [URI]
   def gov_one_uri(endpoint)
     URI.parse("#{Rails.application.config.gov_one_base_uri}/#{endpoint}")

From 368e8ab96ec36221d6772212ba5381ad377170f4 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 9 Nov 2023 15:35:11 +0000
Subject: [PATCH 031/122] rubocop

---
 app/helpers/gov_one_helper.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index b4945c0d6..06a614065 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -30,8 +30,8 @@ def logout_uri
 
 private
 
-# @param endpoint [String] the gov one endpoint
-# @param params [Hash] query params
+  # @param endpoint [String] the gov one endpoint
+  # @param params [Hash] query params
   def gov_one_uri(endpoint, params)
     uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/#{endpoint}")
     uri.query = URI.encode_www_form(params)

From d297b42eaadec941d65376a5584f9f28b9e2ea87 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 10 Nov 2023 10:22:27 +0000
Subject: [PATCH 032/122] update gov one auth service spec

---
 spec/services/gov_one_auth_service_spec.rb | 48 ++++++++++------------
 1 file changed, 22 insertions(+), 26 deletions(-)

diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index b686eea34..e4e0ea4f8 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -1,5 +1,19 @@
 require 'rails_helper'
 
+RSpec.shared_examples 'an unsuccessful request' do
+  it 'returns an empty hash' do
+    allow(mock_response).to receive(:body).and_return({}.to_json)
+    expect(result).to eq({})
+  end
+end
+
+RSpec.shared_examples 'a successful request' do |payload|
+  it 'returns a hash of user data' do
+    allow(mock_response).to receive(:body).and_return(payload.to_json)
+    expect(result).to eq(payload)
+  end
+end
+
 RSpec.describe GovOneAuthService do
   let(:code) { 'mock_code' }
   let(:mock_response) { instance_double('response') }
@@ -14,47 +28,29 @@
   end
 
   describe '#tokens' do
-    let(:token_payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
+    let(:payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
+    let(:result) { auth_service.tokens }
 
     context 'when the request is successful' do
-      it 'returns a hash of tokens' do
-        allow(mock_response).to receive(:body).and_return(token_payload.to_json)
-
-        result = auth_service.tokens
-        expect(result).to eq(token_payload)
-      end
+      it_behaves_like 'a successful request'
     end
 
     context 'when the request is unsuccessful' do
-      it 'returns an empty hash' do
-        allow(mock_response).to receive(:body).and_return({}.to_json)
-
-        result = auth_service.tokens
-        expect(result).to eq({})
-      end
+      it_behaves_like 'an unsuccessful request'
     end
   end
 
   describe '#user_info' do
     let(:access_token) { 'mock_access_token' }
-    let(:user_info_payload) { { 'email' => 'test@test.com' } }
+    let(:payload) { { 'email' => 'test@test.com' } }
+    let(:result) { auth_service.user_info(access_token) }
 
     context 'when the request is successful' do
-      it 'returns a hash of user info' do
-        allow(mock_response).to receive(:body).and_return(user_info_payload.to_json)
-
-        result = auth_service.user_info(access_token)
-        expect(result).to eq(user_info_payload)
-      end
+      it_behaves_like 'a successful request'
     end
 
     context 'when the request is unsuccessful' do
-      it 'returns an empty hash' do
-        allow(mock_response).to receive(:body).and_return({}.to_json)
-
-        result = auth_service.user_info(access_token)
-        expect(result).to eq({})
-      end
+      it_behaves_like 'an unsuccessful request'
     end
   end
 end

From 7294ab73601bba2594c50ed4a4e2e664298b9593 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 10 Nov 2023 10:26:38 +0000
Subject: [PATCH 033/122] update devise config

---
 config/initializers/devise.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb
index 050e97991..6ff37f9ac 100644
--- a/config/initializers/devise.rb
+++ b/config/initializers/devise.rb
@@ -313,7 +313,7 @@ def i18n_message(default = nil)
     client_options: {
       port: 443,
       scheme: 'https',
-      host: 'oidc.integration.account.gov.uk',
+      host: Rails.application.config.gov_one_base_uri,
       identifier: Rails.application.config.gov_one_client_id,
       redirect_uri: 'users/auth/openid_connect/callback',
     },

From 1eaf6ed159dca7ec6d3f1ede8293ba892b5d8647 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 10 Nov 2023 13:20:16 +0000
Subject: [PATCH 034/122] update gov one helper and spec

---
 app/helpers/gov_one_helper.rb       | 11 +++++--
 spec/helpers/gov_one_helper_spec.rb | 47 +++++++++++++++++++++++++++++
 2 files changed, 55 insertions(+), 3 deletions(-)
 create mode 100644 spec/helpers/gov_one_helper_spec.rb

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 06a614065..837d4fb96 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -2,7 +2,7 @@ module GovOneHelper
   # @return [String]
   def login_uri
     state = SecureRandom.uuid
-    session[:gov_one_auth_state] = state
+    session[:gov_one_auth_state] = state unless Rails.env.test?
 
     params = {
       response_type: 'code',
@@ -19,7 +19,7 @@ def login_uri
   # @return [String]
   def logout_uri
     params = {
-      id_token_hint: session[:id_token],
+      id_token_hint: current_id_token,
       state: SecureRandom.uuid,
       post_logout_redirect_uri: Rails.application.config.gov_one_logout_redirect_uri,
     }
@@ -30,10 +30,15 @@ def logout_uri
 
 private
 
+  # @return [String]
+  def current_id_token
+    session[:id_token]
+  end
+
   # @param endpoint [String] the gov one endpoint
   # @param params [Hash] query params
   def gov_one_uri(endpoint, params)
-    uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/#{endpoint}")
+    uri = URI.parse("#{Rails.application.config.gov_one_base_uri}/#{endpoint}")
     uri.query = URI.encode_www_form(params)
     uri
   end
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
new file mode 100644
index 000000000..a3973a0a5
--- /dev/null
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -0,0 +1,47 @@
+require 'rails_helper'
+
+RSpec.describe GovOneHelper do
+  let(:class_instance) { Class.new { include GovOneHelper }.new }
+
+  before do
+    allow(Rails.application.config).to receive(:gov_one_base_uri).and_return('https://example.com')
+    allow(Rails.application.config).to receive(:gov_one_redirect_uri).and_return('https://example.com/redirect')
+    allow(Rails.application.config).to receive(:gov_one_logout_redirect_uri).and_return('https://example.com/logout-redirect')
+    allow(Rails.application.config).to receive(:gov_one_client_id).and_return('client_id')
+  end
+
+  describe '#gov_one_uri' do
+    it 'constructs the URI with correct parameters' do
+      endpoint = 'authorize'
+      params = { response_type: 'code', scope: 'email openid', client_id: 'client_id' }
+
+      uri = class_instance.send(:gov_one_uri, endpoint, params)
+
+      expect(uri.to_s).to eq('https://example.com/authorize?response_type=code&scope=email+openid&client_id=client_id')
+    end
+  end
+
+  describe '#login_uri' do
+    it 'constructs the URI with correct parameters' do
+      result = class_instance.login_uri
+      uri = URI.parse(result)
+
+      expect(uri.to_s).to include('https://example.com/authorize')
+      expect(uri.query).to include('response_type=code', 'scope=email+openid', 'client_id=client_id', 'redirect_uri=https://example.com/redirect')
+    end
+  end
+
+  describe '#logout_uri' do
+    before do
+      allow(class_instance).to receive(:current_id_token).and_return('mock_id_token')
+    end
+
+    it 'constructs the URI with correct parameters' do
+      result = class_instance.logout_uri
+      uri = URI.parse(result)
+
+      expect(uri.to_s).to include('https://example.com/logout')
+      expect(uri.query).to include('id_token_hint=mock_id_token', 'state=', 'post_logout_redirect_uri=https://example.com/logout-redirect')
+    end
+  end
+end

From 6ca601a61f834526fbf90020db7e6c79b96683d3 Mon Sep 17 00:00:00 2001
From: Peter David Hamilton <pete@peterdavidhamilton.com>
Date: Fri, 10 Nov 2023 14:28:25 +0000
Subject: [PATCH 035/122] Helper methods with encoded URIs and specs

---
 README.md                           |  5 ++++
 app/helpers/gov_one_helper.rb       | 28 ++++++++-----------
 docker-compose.test.yml             |  3 ++
 spec/helpers/gov_one_helper_spec.rb | 43 ++++++-----------------------
 spec/lib/seed_snippets_spec.rb      |  2 +-
 5 files changed, 29 insertions(+), 52 deletions(-)

diff --git a/README.md b/README.md
index bdbe02141..e0f454480 100644
--- a/README.md
+++ b/README.md
@@ -313,6 +313,11 @@ The status of GovUK notify can be checked here: <https://status.notifications.se
 For more information the Notify team can be contacted here: <https://www.notifications.service.gov.uk/support>,
 or in the UK Government digital slack workspace in the `#govuk-notify` channel.
 
+---
+
+# One Login
+
+<https://signin.integration.account.gov.uk/sign-in-or-create>
 
 ---
 
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 837d4fb96..488e8d8fc 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -1,44 +1,38 @@
 module GovOneHelper
   # @return [String]
   def login_uri
-    state = SecureRandom.uuid
-    session[:gov_one_auth_state] = state unless Rails.env.test?
-
     params = {
       response_type: 'code',
       scope: 'email openid',
       client_id: Rails.application.config.gov_one_client_id,
       nonce: SecureRandom.uuid,
-      state: state,
+      state: SecureRandom.uuid,
+      redirect_uri: Rails.application.config.gov_one_redirect_uri,
     }
 
-    uri = gov_one_uri('authorize', params)
-    "#{uri}&redirect_uri=#{Rails.application.config.gov_one_redirect_uri}"
+    session[:gov_one_auth_state] = params[:state]
+
+    gov_one_uri('authorize', params).to_s
   end
 
   # @return [String]
   def logout_uri
     params = {
-      id_token_hint: current_id_token,
+      id_token_hint: session[:id_token],
       state: SecureRandom.uuid,
       post_logout_redirect_uri: Rails.application.config.gov_one_logout_redirect_uri,
     }
 
-    uri = gov_one_uri('logout', params)
-    "#{uri}&post_logout_redirect_uri=#{Rails.application.config.gov_one_logout_redirect_uri}"
+    gov_one_uri('logout', params).to_s
   end
 
 private
 
-  # @return [String]
-  def current_id_token
-    session[:id_token]
-  end
-
-  # @param endpoint [String] the gov one endpoint
-  # @param params [Hash] query params
+  # @param endpoint [String]
+  # @param params [Hash]
+  # @return [URI::HTTP, URI::HTTPS]
   def gov_one_uri(endpoint, params)
-    uri = URI.parse("#{Rails.application.config.gov_one_base_uri}/#{endpoint}")
+    uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/#{endpoint}")
     uri.query = URI.encode_www_form(params)
     uri
   end
diff --git a/docker-compose.test.yml b/docker-compose.test.yml
index 09abac58e..7b61fe942 100644
--- a/docker-compose.test.yml
+++ b/docker-compose.test.yml
@@ -14,5 +14,8 @@ services:
       - DEBUG=false
       - BOT_TOKEN=bot_token
       - TIMEOUT_MINUTES=25
+      - GOV_ONE_BASE_URI=https://oidc.test.account.gov.uk
+      - GOV_ONE_REDIRECT_URI=http://recovery.app/users/auth/openid_connect/callback
+      - GOV_ONE_LOGOUT_REDIRECT_URI=http://recovery.app/users/sign_out
     tty: true
     stdin_open: true
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index a3973a0a5..196178d2f 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -1,47 +1,22 @@
 require 'rails_helper'
 
-RSpec.describe GovOneHelper do
-  let(:class_instance) { Class.new { include GovOneHelper }.new }
-
-  before do
-    allow(Rails.application.config).to receive(:gov_one_base_uri).and_return('https://example.com')
-    allow(Rails.application.config).to receive(:gov_one_redirect_uri).and_return('https://example.com/redirect')
-    allow(Rails.application.config).to receive(:gov_one_logout_redirect_uri).and_return('https://example.com/logout-redirect')
-    allow(Rails.application.config).to receive(:gov_one_client_id).and_return('client_id')
-  end
-
-  describe '#gov_one_uri' do
-    it 'constructs the URI with correct parameters' do
-      endpoint = 'authorize'
-      params = { response_type: 'code', scope: 'email openid', client_id: 'client_id' }
-
-      uri = class_instance.send(:gov_one_uri, endpoint, params)
-
-      expect(uri.to_s).to eq('https://example.com/authorize?response_type=code&scope=email+openid&client_id=client_id')
-    end
-  end
+describe 'GovOneHelper', type: :helper do
 
   describe '#login_uri' do
-    it 'constructs the URI with correct parameters' do
-      result = class_instance.login_uri
-      uri = URI.parse(result)
+    subject(:login_uri) { helper.login_uri }
 
-      expect(uri.to_s).to include('https://example.com/authorize')
-      expect(uri.query).to include('response_type=code', 'scope=email+openid', 'client_id=client_id', 'redirect_uri=https://example.com/redirect')
+    it 'encodes the authorize endpoint params' do
+      expect(login_uri).to start_with 'https://oidc.test.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id='
+      expect(login_uri).to end_with 'redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback'
     end
   end
 
   describe '#logout_uri' do
-    before do
-      allow(class_instance).to receive(:current_id_token).and_return('mock_id_token')
-    end
-
-    it 'constructs the URI with correct parameters' do
-      result = class_instance.logout_uri
-      uri = URI.parse(result)
+    subject(:logout_uri) { helper.logout_uri }
 
-      expect(uri.to_s).to include('https://example.com/logout')
-      expect(uri.query).to include('id_token_hint=mock_id_token', 'state=', 'post_logout_redirect_uri=https://example.com/logout-redirect')
+    it 'encodes the logout endpoint params' do
+      expect(logout_uri).to start_with 'https://oidc.test.account.gov.uk/logout?id_token_hint&state='
+      expect(logout_uri).to end_with '&post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out'
     end
   end
 end
diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb
index 8add68d26..89e91259b 100644
--- a/spec/lib/seed_snippets_spec.rb
+++ b/spec/lib/seed_snippets_spec.rb
@@ -5,7 +5,7 @@
   subject(:locales) { described_class.new.call }
 
   it 'converts all translations' do
-    expect(locales.count).to be 177
+    expect(locales.count).to be 181
   end
 
   it 'dot separated key -> Page::Resource#name' do

From d95d246653cbea0b5e23776d4114f961fef40abb Mon Sep 17 00:00:00 2001
From: Peter David Hamilton <pete@peterdavidhamilton.com>
Date: Fri, 10 Nov 2023 14:53:22 +0000
Subject: [PATCH 036/122] Update CI envs

---
 .github/workflows/ci.yml            |  3 +++
 app/models/user.rb                  | 14 ++++++--------
 spec/helpers/gov_one_helper_spec.rb |  1 -
 3 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b0b10efcf..1cea13948 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,6 +33,9 @@ jobs:
       DATABASE_URL: postgres://postgres:password@localhost:5432/test
       DOMAIN: recovery.app
       BOT_TOKEN: bot_token
+      GOV_ONE_BASE_URI: https://oidc.test.account.gov.uk
+      GOV_ONE_REDIRECT_URI: https://recovery.app/users/auth/openid_connect/callback
+      GOV_ONE_LOGOUT_REDIRECT_URI: https://recovery.app/users/sign_out
 
     services:
       postgres:
diff --git a/app/models/user.rb b/app/models/user.rb
index d05fad376..711c392a3 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -25,16 +25,14 @@ def self.dashboard_headers
   # @param gov_one_id [String]
   # @return [User]
   def self.find_or_create_from_gov_one(email:, gov_one_id:)
-    existing_user = find_by(email: email) || find_by(gov_one_id: gov_one_id)
-
-    if existing_user
-      existing_user.update!(email: email)
-      existing_user.update!(gov_one_id: gov_one_id) if existing_user.gov_one_id.nil?
+    if (user = find_by(email: email) || find_by(gov_one_id: gov_one_id))
+      user.update!(email: email)
+      user.update!(gov_one_id: gov_one_id) if user.gov_one_id.nil?
     else
-      existing_user = new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now)
-      existing_user.save!(validate: false)
+      user = new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now)
+      user.save!(validate: false)
     end
-    existing_user
+    user
   end
 
   # Include default devise modules. Others available are:
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index 196178d2f..27994974d 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -1,7 +1,6 @@
 require 'rails_helper'
 
 describe 'GovOneHelper', type: :helper do
-
   describe '#login_uri' do
     subject(:login_uri) { helper.login_uri }
 

From 974be47b7c52908de25116e3acb1e1b46e278fae Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 10 Nov 2023 16:49:15 +0000
Subject: [PATCH 037/122] refactor gov one service and helper

---
 .github/workflows/ci.yml                   |  3 +++
 app/helpers/gov_one_helper.rb              |  8 ++++----
 app/services/gov_one_auth_service.rb       | 21 +++++++++++----------
 app/views/gov_one/info.html.slim           |  4 ++--
 docker-compose.test.yml                    |  3 +++
 spec/helpers/gov_one_helper_spec.rb        | 21 ++++++++-------------
 spec/services/gov_one_auth_service_spec.rb |  4 +---
 7 files changed, 32 insertions(+), 32 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9cc1a306a..58606bf0a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,6 +31,9 @@ jobs:
       DATABASE_URL: postgres://postgres:password@localhost:5432/test
       DOMAIN: recovery.app
       BOT_TOKEN: bot_token
+      GOV_ONE_CLIENT_ID: client_id
+      GOV_ONE_BASE_URI: https://oidc.integration.account.gov.uk
+      GOV_ONE_REDIRECT_URI: mock_redirect_uri
 
     services:
       postgres:
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 837d4fb96..ce25e546b 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -10,10 +10,10 @@ def login_uri
       client_id: Rails.application.config.gov_one_client_id,
       nonce: SecureRandom.uuid,
       state: state,
+      redirect_uri: Rails.application.config.gov_one_redirect_uri,
     }
 
-    uri = gov_one_uri('authorize', params)
-    "#{uri}&redirect_uri=#{Rails.application.config.gov_one_redirect_uri}"
+    gov_one_uri('authorize', params)
   end
 
   # @return [String]
@@ -24,8 +24,7 @@ def logout_uri
       post_logout_redirect_uri: Rails.application.config.gov_one_logout_redirect_uri,
     }
 
-    uri = gov_one_uri('logout', params)
-    "#{uri}&post_logout_redirect_uri=#{Rails.application.config.gov_one_logout_redirect_uri}"
+    gov_one_uri('logout', params)
   end
 
 private
@@ -37,6 +36,7 @@ def current_id_token
 
   # @param endpoint [String] the gov one endpoint
   # @param params [Hash] query params
+  # @return [URI]
   def gov_one_uri(endpoint, params)
     uri = URI.parse("#{Rails.application.config.gov_one_base_uri}/#{endpoint}")
     uri.query = URI.encode_www_form(params)
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 93029da85..cd5aa4f6e 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -4,14 +4,19 @@
 # - exchange an access token for user info
 # - decode an id token to get the user's gov one id
 #
-# More information on the Gov One Login integration environment can be found here:
-# https://docs.sign-in.service.gov.uk/integrate-with-integration-environment/
+# @see https://docs.sign-in.service.gov.uk/integrate-with-integration-environment/
 
 class GovOneAuthService
   extend Dry::Initializer
 
   option :code, Types::String
 
+  GOV_ONE_URIS = {
+    token: '/token',
+    userinfo: '/userinfo',
+    jwk: '/.well-known/jwks.json',
+  }.freeze
+
   # @return [Hash]
   def tokens
     http = build_http(token_uri)
@@ -29,6 +34,7 @@ def tokens
   # @param access_token [String]
   # @return [Hash]
   def user_info(access_token)
+    userinfo_uri = gov_one_uri(GOV_ONE_URIS[:userinfo])
     http = build_http(userinfo_uri)
 
     userinfo_request = Net::HTTP::Get.new(userinfo_uri.path, { 'Authorization' => "Bearer #{access_token}" })
@@ -54,18 +60,13 @@ def decode_id_token(token)
 
   # @return [URI]
   def token_uri
-    gov_one_uri('token')
-  end
-
-  # @return [URI]
-  def userinfo_uri
-    gov_one_uri('userinfo')
+    gov_one_uri(GOV_ONE_URIS[:token])
   end
 
   # @param endpoint [String]
   # @return [URI]
   def gov_one_uri(endpoint)
-    URI.parse("#{Rails.application.config.gov_one_base_uri}/#{endpoint}")
+    URI.join(Rails.application.config.gov_one_base_uri, endpoint)
   end
 
   # @param uri [URI]
@@ -78,7 +79,7 @@ def build_http(uri)
 
   # @return [Hash]
   def jwks
-    uri = gov_one_uri('.well-known/jwks.json')
+    uri = gov_one_uri(GOV_ONE_URIS[:jwk])
     http = build_http(uri)
     response = http.request(Net::HTTP::Get.new(uri.path))
     JSON.parse(response.body)
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index da7a2602f..39951ca0e 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -8,7 +8,7 @@
     = m('gov-one-info.body')
 
     - if current_user
-      = govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri
+      = govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri.to_s
     - else
-      = govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri
+      = govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri.to_s
   
\ No newline at end of file
diff --git a/docker-compose.test.yml b/docker-compose.test.yml
index 09abac58e..b1ff4aaf8 100644
--- a/docker-compose.test.yml
+++ b/docker-compose.test.yml
@@ -14,5 +14,8 @@ services:
       - DEBUG=false
       - BOT_TOKEN=bot_token
       - TIMEOUT_MINUTES=25
+      - GOV_ONE_CLIENT_ID=client_id
+      - GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk
+      - GOV_ONE_REDIRECT_URI=mock_redirect_uri
     tty: true
     stdin_open: true
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index a3973a0a5..842851f95 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -4,10 +4,7 @@
   let(:class_instance) { Class.new { include GovOneHelper }.new }
 
   before do
-    allow(Rails.application.config).to receive(:gov_one_base_uri).and_return('https://example.com')
-    allow(Rails.application.config).to receive(:gov_one_redirect_uri).and_return('https://example.com/redirect')
-    allow(Rails.application.config).to receive(:gov_one_logout_redirect_uri).and_return('https://example.com/logout-redirect')
-    allow(Rails.application.config).to receive(:gov_one_client_id).and_return('client_id')
+    allow(Rails.application.config).to receive(:gov_one_logout_redirect_uri).and_return('mock_logout_redirect_uri')
   end
 
   describe '#gov_one_uri' do
@@ -17,17 +14,16 @@
 
       uri = class_instance.send(:gov_one_uri, endpoint, params)
 
-      expect(uri.to_s).to eq('https://example.com/authorize?response_type=code&scope=email+openid&client_id=client_id')
+      expect(uri.to_s).to eq('https://oidc.integration.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id=client_id')
     end
   end
 
   describe '#login_uri' do
     it 'constructs the URI with correct parameters' do
-      result = class_instance.login_uri
-      uri = URI.parse(result)
+      uri = class_instance.login_uri
 
-      expect(uri.to_s).to include('https://example.com/authorize')
-      expect(uri.query).to include('response_type=code', 'scope=email+openid', 'client_id=client_id', 'redirect_uri=https://example.com/redirect')
+      expect(uri.to_s).to include('https://oidc.integration.account.gov.uk/authorize')
+      expect(uri.query).to include('response_type=code', 'scope=email+openid', 'client_id=client_id', 'redirect_uri=mock_redirect_uri')
     end
   end
 
@@ -37,11 +33,10 @@
     end
 
     it 'constructs the URI with correct parameters' do
-      result = class_instance.logout_uri
-      uri = URI.parse(result)
+      uri = class_instance.logout_uri
 
-      expect(uri.to_s).to include('https://example.com/logout')
-      expect(uri.query).to include('id_token_hint=mock_id_token', 'state=', 'post_logout_redirect_uri=https://example.com/logout-redirect')
+      expect(uri.to_s).to include('https://oidc.integration.account.gov.uk/logout')
+      expect(uri.query).to include('id_token_hint=mock_id_token', 'state=', 'post_logout_redirect_uri=mock_logout_redirect_uri')
     end
   end
 end
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index e4e0ea4f8..b416d83a7 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -7,7 +7,7 @@
   end
 end
 
-RSpec.shared_examples 'a successful request' do |payload|
+RSpec.shared_examples 'a successful request' do
   it 'returns a hash of user data' do
     allow(mock_response).to receive(:body).and_return(payload.to_json)
     expect(result).to eq(payload)
@@ -21,8 +21,6 @@
   let(:mock_http) { instance_double('Sentry::Net::HTTP') }
 
   before do
-    allow(Rails.application.config).to receive(:gov_one_base_uri).and_return('https://example.com')
-    allow(Rails.application.config).to receive(:gov_one_redirect_uri).and_return('mock_redirect_uri')
     allow(mock_http).to receive(:request).and_return(mock_response)
     allow(Net::HTTP).to receive(:new).and_return(mock_http)
   end

From bc7ddbedeff6887530a00c09605d31009deaf4dc Mon Sep 17 00:00:00 2001
From: Peter David Hamilton <pete@peterdavidhamilton.com>
Date: Fri, 10 Nov 2023 17:26:57 +0000
Subject: [PATCH 038/122] Refactor to use a constant and reduce number of
 variables

---
 .env.example                         |  7 +--
 .github/workflows/ci.yml             |  2 -
 app/helpers/gov_one_helper.rb        | 12 ++---
 app/services/gov_one_auth_service.rb | 68 +++++++++++++---------------
 app/views/gov_one/info.html.slim     |  8 ++--
 config/application.rb                | 10 ++--
 config/locales/en.yml                | 18 ++++----
 config/sitemap.rb                    |  3 +-
 docker-compose.dev.yml               |  7 +--
 docker-compose.test.yml              |  2 -
 10 files changed, 60 insertions(+), 77 deletions(-)

diff --git a/.env.example b/.env.example
index 380f3789d..ae0b13f45 100644
--- a/.env.example
+++ b/.env.example
@@ -82,15 +82,10 @@ E2E=true
 # Deployment environment
 ENVIRONMENT=
 
+# Gov One Authentication
 GOV_ONE_LOGIN=true
-
-# Base URI for Gov One Login requests
 GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk
 
-# Redirect URIs for Gov One Login redirects
-# Replace localhost:3000 with the correct application root path
-GOV_ONE_REDIRECT_URI=http://localhost:3000/users/auth/openid_connect/callback
-GOV_ONE_LOGOUT_REDIRECT_URI=http://localhost:3000/users/sign_out
 
 # DISPLAY SERVICE_UNAVAILABLE PAGE
 MAINTENANCE = true
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1cea13948..919fd8ca5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -34,8 +34,6 @@ jobs:
       DOMAIN: recovery.app
       BOT_TOKEN: bot_token
       GOV_ONE_BASE_URI: https://oidc.test.account.gov.uk
-      GOV_ONE_REDIRECT_URI: https://recovery.app/users/auth/openid_connect/callback
-      GOV_ONE_LOGOUT_REDIRECT_URI: https://recovery.app/users/sign_out
 
     services:
       postgres:
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 488e8d8fc..575622145 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -7,12 +7,12 @@ def login_uri
       client_id: Rails.application.config.gov_one_client_id,
       nonce: SecureRandom.uuid,
       state: SecureRandom.uuid,
-      redirect_uri: Rails.application.config.gov_one_redirect_uri,
+      redirect_uri: GovOneAuthService::CALLBACKS[:login],
     }
 
     session[:gov_one_auth_state] = params[:state]
 
-    gov_one_uri('authorize', params).to_s
+    gov_one_uri(:login, params).to_s
   end
 
   # @return [String]
@@ -20,19 +20,19 @@ def logout_uri
     params = {
       id_token_hint: session[:id_token],
       state: SecureRandom.uuid,
-      post_logout_redirect_uri: Rails.application.config.gov_one_logout_redirect_uri,
+      post_logout_redirect_uri: GovOneAuthService::CALLBACKS[:logout],
     }
 
-    gov_one_uri('logout', params).to_s
+    gov_one_uri(:logout, params).to_s
   end
 
 private
 
-  # @param endpoint [String]
+  # @param endpoint [Symbol]
   # @param params [Hash]
   # @return [URI::HTTP, URI::HTTPS]
   def gov_one_uri(endpoint, params)
-    uri = URI.parse("#{ENV['GOV_ONE_BASE_URI']}/#{endpoint}")
+    uri = URI.parse(GovOneAuthService::ENDPOINTS[endpoint])
     uri.query = URI.encode_www_form(params)
     uri
   end
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 93029da85..e806fc691 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -1,22 +1,34 @@
-# Service for interacting with Gov One Login
-# This service is initialised with an authorisation code and can be used to:
+#
 # - exchange an authorisation code for tokens (access and id)
 # - exchange an access token for user info
 # - decode an id token to get the user's gov one id
 #
-# More information on the Gov One Login integration environment can be found here:
-# https://docs.sign-in.service.gov.uk/integrate-with-integration-environment/
-
+# @see https://docs.sign-in.service.gov.uk/
 class GovOneAuthService
+  # @return [Hash{Symbol => String}]
+  CALLBACKS = {
+    login: "#{Rails.application.config.service_url}/users/auth/openid_connect/callback",
+    logout: "#{Rails.application.config.service_url}/users/sign_out",
+  }.freeze
+
+  # @return [Hash{Symbol => String}]
+  ENDPOINTS = {
+    login: "#{Rails.application.config.gov_one_base_uri}/authorize",
+    logout: "#{Rails.application.config.gov_one_base_uri}/logout",
+    token: "#{Rails.application.config.gov_one_base_uri}/token",
+    userinfo: "#{Rails.application.config.gov_one_base_uri}/userinfo",
+    jwks: "#{Rails.application.config.gov_one_base_uri}/.well-known/jwks.json",
+  }.freeze
+
   extend Dry::Initializer
 
-  option :code, Types::String
+  option :code, Types::Strict::String
 
+  # POST /token
   # @return [Hash]
   def tokens
-    http = build_http(token_uri)
-
-    token_request = Net::HTTP::Post.new(token_uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })
+    uri, http = build_http(ENDPOINTS[:token])
+    token_request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })
     token_request.set_form_data(token_body)
     token_response = http.request(token_request)
 
@@ -26,12 +38,12 @@ def tokens
     {}
   end
 
+  # GET /userinfo
   # @param access_token [String]
   # @return [Hash]
   def user_info(access_token)
-    http = build_http(userinfo_uri)
-
-    userinfo_request = Net::HTTP::Get.new(userinfo_uri.path, { 'Authorization' => "Bearer #{access_token}" })
+    uri, http = build_http(ENDPOINTS[:userinfo])
+    userinfo_request = Net::HTTP::Get.new(uri.path, { 'Authorization' => "Bearer #{access_token}" })
     userinfo_response = http.request(userinfo_request)
 
     JSON.parse(userinfo_response.body)
@@ -52,34 +64,18 @@ def decode_id_token(token)
 
 private
 
-  # @return [URI]
-  def token_uri
-    gov_one_uri('token')
-  end
-
-  # @return [URI]
-  def userinfo_uri
-    gov_one_uri('userinfo')
-  end
-
-  # @param endpoint [String]
-  # @return [URI]
-  def gov_one_uri(endpoint)
-    URI.parse("#{Rails.application.config.gov_one_base_uri}/#{endpoint}")
-  end
-
-  # @param uri [URI]
-  # @return [Net::HTTP]
-  def build_http(uri)
+  # @param address [String]
+  # @return [Array<URI::HTTP, Net::HTTP>]
+  def build_http(address)
+    uri = URI.parse(address)
     http = Net::HTTP.new(uri.host, uri.port)
     http.use_ssl = true unless Rails.env.test?
-    http
+    [uri, http]
   end
 
   # @return [Hash]
   def jwks
-    uri = gov_one_uri('.well-known/jwks.json')
-    http = build_http(uri)
+    uri, http = build_http(ENDPOINTS[:jwks])
     response = http.request(Net::HTTP::Get.new(uri.path))
     JSON.parse(response.body)
   end
@@ -89,7 +85,7 @@ def jwt_assertion
     rsa_private = OpenSSL::PKey::RSA.new(Rails.application.config.gov_one_private_key)
 
     payload = {
-      aud: token_uri.to_s,
+      aud: ENDPOINTS[:token],
       iss: Rails.application.config.gov_one_client_id,
       sub: Rails.application.config.gov_one_client_id,
       exp: Time.zone.now.to_i + 5 * 60,
@@ -105,7 +101,7 @@ def token_body
     {
       grant_type: 'authorization_code',
       code: code,
-      redirect_uri: ENV['GOV_ONE_REDIRECT_URI'],
+      redirect_uri: CALLBACKS[:login],
       client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
       client_assertion: jwt_assertion,
     }
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index da7a2602f..309f98ce8 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -1,14 +1,14 @@
 = render 'learning/cms_debug'
 
 - content_for :page_title do
-  = html_title t('gov-one.info.title')
+  = html_title t('gov_one.title')
 
 .govuk-grid-row
   .govuk-grid-column-one-half
-    = m('gov-one-info.body')
+    = m('gov_one.body')
 
     - if current_user
-      = govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri
+      = govuk_button_link_to t('gov_one.links.sign_out'), logout_uri
     - else
-      = govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri
+      = govuk_button_link_to t('gov_one.links.sign_in'), login_uri
   
\ No newline at end of file
diff --git a/config/application.rb b/config/application.rb
index 02cdc1841..62ccd629c 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -32,6 +32,8 @@ class Application < Rails::Application
     end
 
     config.service_name = 'Early years child development training'
+    config.service_url = (Rails.env.production? ? 'https://' : 'http://') + ENV.fetch('DOMAIN', 'child-development-training')
+
     config.internal_mailbox = ENV.fetch('INTERNAL_MAILBOX', 'child-development.training@education.gov.uk')
     config.middleware.use Grover::Middleware
     config.active_record.yaml_column_permitted_classes = [Symbol]
@@ -69,11 +71,9 @@ class Application < Rails::Application
     config.contentful_environment             = ENV.fetch('CONTENTFUL_ENVIRONMENT', credentials.dig(:contentful, :environment))
 
     # Gov one
-    config.gov_one_base_uri = ENV.fetch('GOV_ONE_BASE_URI', '#GOV_ONE_BASE_URI_env_var_missing')
-    config.gov_one_redirect_uri = ENV.fetch('GOV_ONE_REDIRECT_URI', '#GOV_ONE_REDIRECT_URI_env_var_missing')
-    config.gov_one_logout_redirect_uri = ENV.fetch('GOV_ONE_LOGOUT_REDIRECT_URI', '#GOV_ONE_LOGOUT_REDIRECT_URI_env_var_missing')
-    config.gov_one_private_key = ENV.fetch('GOV_ONE_PRIVATE_KEY', credentials.dig(:gov_one, :private_key))
-    config.gov_one_client_id = ENV.fetch('GOV_ONE_CLIENT_ID', credentials.dig(:gov_one, :client_id))
+    config.gov_one_base_uri     = ENV.fetch('GOV_ONE_BASE_URI', '#GOV_ONE_BASE_URI_env_var_missing')
+    config.gov_one_private_key  = ENV.fetch('GOV_ONE_PRIVATE_KEY', credentials.dig(:gov_one, :private_key))
+    config.gov_one_client_id    = ENV.fetch('GOV_ONE_CLIENT_ID', credentials.dig(:gov_one, :client_id))
 
     # @return [Boolean]
     def live?
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e3f7c11b4..776d69931 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -571,15 +571,15 @@ en:
       %{criteria}
 
   # /gov-one/info
-  gov-one:
-    info:
-      title: Gov One Login
-      body: |
-        # Gov One Login
-
-        This is some information about Gov One Login... 
-      sign-in-button: Sign in with Gov One Login
-      sign-out-button: Sign out of Gov One Login
+  gov_one:
+    title: Gov One Login
+    body: |
+      # Gov One Login
+
+      This is some information about Gov One Login...
+    links:
+      sign_in: Sign in with Gov One Login
+      sign_out: Sign out of Gov One Login
 
   # /settings/cookie-policy
   cookie_policy:
diff --git a/config/sitemap.rb b/config/sitemap.rb
index bfdaecb7f..901358c90 100644
--- a/config/sitemap.rb
+++ b/config/sitemap.rb
@@ -14,8 +14,7 @@
 #     }
 #   ]
 #
-protocol = Rails.env.production? ? 'https://' : 'http://'
-SitemapGenerator::Sitemap.default_host = protocol + ENV['DOMAIN']
+SitemapGenerator::Sitemap.default_host = Rails.application.config.service_url
 SitemapGenerator::Sitemap.compress = false
 
 # Run this command to update /public/sitemap.xml
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 6f104f3b5..8e3a4eeed 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -19,12 +19,9 @@ services:
       # app config
       - DATABASE_URL=postgres://postgres:password@db:5432/early_years_foundation_recovery_development
       - RAILS_ENV=development
-      - DOMAIN=app:3000
+      - DOMAIN=localhost:3000 # put back to app
       - RAILS_SERVE_STATIC_FILES=true
-      # gov one login
-      - GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk 
-      - GOV_ONE_REDIRECT_URI=http://localhost:3000/users/auth/openid_connect/callback
-      - GOV_ONE_LOGOUT_REDIRECT_URI=http://localhost:3000/users/sign_out
+      - GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk
     volumes:
       - .:/srv
     tty: true
diff --git a/docker-compose.test.yml b/docker-compose.test.yml
index 7b61fe942..47551644c 100644
--- a/docker-compose.test.yml
+++ b/docker-compose.test.yml
@@ -15,7 +15,5 @@ services:
       - BOT_TOKEN=bot_token
       - TIMEOUT_MINUTES=25
       - GOV_ONE_BASE_URI=https://oidc.test.account.gov.uk
-      - GOV_ONE_REDIRECT_URI=http://recovery.app/users/auth/openid_connect/callback
-      - GOV_ONE_LOGOUT_REDIRECT_URI=http://recovery.app/users/sign_out
     tty: true
     stdin_open: true

From f5976812bb250a83ba83383000675303dd557e77 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 13 Nov 2023 16:22:04 +0000
Subject: [PATCH 039/122] refactor gov one auth service and specs for service
 and helper

---
 .github/workflows/ci.yml                   |   1 -
 app/helpers/gov_one_helper.rb              |   4 +-
 app/services/gov_one_auth_service.rb       |  32 +++++--
 app/views/gov_one/info.html.slim           |   4 +-
 config/application.rb                      |   2 +-
 config/credentials/development.yml.enc     |   2 +-
 config/credentials/production.yml.enc      |   2 +-
 config/credentials/test.yml.enc            |   2 +-
 docker-compose.test.yml                    |   1 -
 spec/helpers/gov_one_helper_spec.rb        |  18 +++-
 spec/services/gov_one_auth_service_spec.rb | 100 +++++++++++++++------
 11 files changed, 121 insertions(+), 47 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 919fd8ca5..b0b10efcf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,7 +33,6 @@ jobs:
       DATABASE_URL: postgres://postgres:password@localhost:5432/test
       DOMAIN: recovery.app
       BOT_TOKEN: bot_token
-      GOV_ONE_BASE_URI: https://oidc.test.account.gov.uk
 
     services:
       postgres:
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 575622145..b9fea0c05 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -12,7 +12,7 @@ def login_uri
 
     session[:gov_one_auth_state] = params[:state]
 
-    gov_one_uri(:login, params).to_s
+    gov_one_uri(:login, params)
   end
 
   # @return [String]
@@ -23,7 +23,7 @@ def logout_uri
       post_logout_redirect_uri: GovOneAuthService::CALLBACKS[:logout],
     }
 
-    gov_one_uri(:logout, params).to_s
+    gov_one_uri(:logout, params)
   end
 
 private
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index e806fc691..7f1d2809a 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -30,7 +30,7 @@ def tokens
     uri, http = build_http(ENDPOINTS[:token])
     token_request = Net::HTTP::Post.new(uri.path, { 'Content-Type' => 'application/x-www-form-urlencoded' })
     token_request.set_form_data(token_body)
-    token_response = http.request(token_request)
+    token_response = response(token_request, http)
 
     JSON.parse(token_response.body)
   rescue StandardError => e
@@ -44,7 +44,7 @@ def tokens
   def user_info(access_token)
     uri, http = build_http(ENDPOINTS[:userinfo])
     userinfo_request = Net::HTTP::Get.new(uri.path, { 'Authorization' => "Bearer #{access_token}" })
-    userinfo_response = http.request(userinfo_request)
+    userinfo_response = response(userinfo_request, http)
 
     JSON.parse(userinfo_response.body)
   rescue StandardError => e
@@ -52,6 +52,20 @@ def user_info(access_token)
     {}
   end
 
+  # @param request [Net::HTTP::Get, Net::HTTP::Post]
+  # @param http [Net::HTTP]
+  # @return [Net::HTTPResponse]
+  def response(request, http)
+    http.request(request)
+  end
+
+  # @param request [Net::HTTP::Get, Net::HTTP::Post]
+  # @return [Hash]
+  def handle_request(request)
+    response = http.request(request)
+    JSON.parse(response.body)
+  end
+
   # @param token [String]
   # @return [Array<Hash>]
   def decode_id_token(token)
@@ -62,17 +76,17 @@ def decode_id_token(token)
     JWT.decode(token, jwk.public_key, true, algorithm: 'ES256')
   end
 
-private
-
   # @param address [String]
   # @return [Array<URI::HTTP, Net::HTTP>]
   def build_http(address)
     uri = URI.parse(address)
     http = Net::HTTP.new(uri.host, uri.port)
-    http.use_ssl = true unless Rails.env.test?
+    http.use_ssl = true
     [uri, http]
   end
 
+private
+
   # @return [Hash]
   def jwks
     uri, http = build_http(ENDPOINTS[:jwks])
@@ -83,8 +97,12 @@ def jwks
   # @return [String]
   def jwt_assertion
     rsa_private = OpenSSL::PKey::RSA.new(Rails.application.config.gov_one_private_key)
+    JWT.encode jwt_payload, rsa_private, 'RS256'
+  end
 
-    payload = {
+  # @return [Hash]
+  def jwt_payload
+    {
       aud: ENDPOINTS[:token],
       iss: Rails.application.config.gov_one_client_id,
       sub: Rails.application.config.gov_one_client_id,
@@ -92,8 +110,6 @@ def jwt_assertion
       jti: SecureRandom.uuid,
       iat: Time.zone.now.to_i,
     }
-
-    JWT.encode payload, rsa_private, 'RS256'
   end
 
   # @return [Hash]
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index 309f98ce8..fce694838 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -8,7 +8,7 @@
     = m('gov_one.body')
 
     - if current_user
-      = govuk_button_link_to t('gov_one.links.sign_out'), logout_uri
+      = govuk_button_link_to t('gov_one.links.sign_out'), logout_uri.to_s
     - else
-      = govuk_button_link_to t('gov_one.links.sign_in'), login_uri
+      = govuk_button_link_to t('gov_one.links.sign_in'), login_uri.to_s
   
\ No newline at end of file
diff --git a/config/application.rb b/config/application.rb
index 62ccd629c..6eeab7a6b 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -71,7 +71,7 @@ class Application < Rails::Application
     config.contentful_environment             = ENV.fetch('CONTENTFUL_ENVIRONMENT', credentials.dig(:contentful, :environment))
 
     # Gov one
-    config.gov_one_base_uri     = ENV.fetch('GOV_ONE_BASE_URI', '#GOV_ONE_BASE_URI_env_var_missing')
+    config.gov_one_base_uri     = ENV.fetch('GOV_ONE_BASE_URI', credentials.dig(:gov_one, :base_uri))
     config.gov_one_private_key  = ENV.fetch('GOV_ONE_PRIVATE_KEY', credentials.dig(:gov_one, :private_key))
     config.gov_one_client_id    = ENV.fetch('GOV_ONE_CLIENT_ID', credentials.dig(:gov_one, :client_id))
 
diff --git a/config/credentials/development.yml.enc b/config/credentials/development.yml.enc
index 4606457df..292c4ec32 100644
--- a/config/credentials/development.yml.enc
+++ b/config/credentials/development.yml.enc
@@ -1 +1 @@
-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--mE6HeB6Fsvd9DHxG--T8mDZYxmmz5GxmQ8ikSFTg==
\ No newline at end of file
+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--QhoOqS2u5TN6yGex--D+M0HrxGhh3aOh6fIxi9SQ==
\ No newline at end of file
diff --git a/config/credentials/production.yml.enc b/config/credentials/production.yml.enc
index 9dfe3025f..0bf5120f8 100644
--- a/config/credentials/production.yml.enc
+++ b/config/credentials/production.yml.enc
@@ -1 +1 @@
-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--D/uaMzAQH8aFuO+u--HkOCuZK3KJsvkGlaRQ45Gg==
\ No newline at end of file
+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--zJGkDqYx7hBVWmDr--IjW3HIJrI8vrbQ7r4gR4Mg==
\ No newline at end of file
diff --git a/config/credentials/test.yml.enc b/config/credentials/test.yml.enc
index e7e3894a0..69becba05 100644
--- a/config/credentials/test.yml.enc
+++ b/config/credentials/test.yml.enc
@@ -1 +1 @@
-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--Jz3stjju1xhM5+A7--5uwznqznRm9MCJq4I4/q+w==
\ No newline at end of file
+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--ub0s9DBRWGcD5tT1--YSXZBp3jUhqXA6bl51La3w==
\ No newline at end of file
diff --git a/docker-compose.test.yml b/docker-compose.test.yml
index 47551644c..09abac58e 100644
--- a/docker-compose.test.yml
+++ b/docker-compose.test.yml
@@ -14,6 +14,5 @@ services:
       - DEBUG=false
       - BOT_TOKEN=bot_token
       - TIMEOUT_MINUTES=25
-      - GOV_ONE_BASE_URI=https://oidc.test.account.gov.uk
     tty: true
     stdin_open: true
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index 27994974d..33fe599d3 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -4,18 +4,28 @@
   describe '#login_uri' do
     subject(:login_uri) { helper.login_uri }
 
+    it 'returns a URI object with the correct host and path' do
+      expect(login_uri.host).to eq 'oidc.test.account.gov.uk'
+      expect(login_uri.path).to eq '/authorize'
+    end
+
     it 'encodes the authorize endpoint params' do
-      expect(login_uri).to start_with 'https://oidc.test.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id='
-      expect(login_uri).to end_with 'redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback'
+      expect(login_uri.query).to start_with 'response_type=code&scope=email+openid&client_id='
+      expect(login_uri.query).to end_with '&redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback'
     end
   end
 
   describe '#logout_uri' do
     subject(:logout_uri) { helper.logout_uri }
 
+    it 'returns a URI object with the correct host and path' do
+      expect(logout_uri.host).to eq 'oidc.test.account.gov.uk'
+      expect(logout_uri.path).to eq '/logout'
+    end
+
     it 'encodes the logout endpoint params' do
-      expect(logout_uri).to start_with 'https://oidc.test.account.gov.uk/logout?id_token_hint&state='
-      expect(logout_uri).to end_with '&post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out'
+      expect(logout_uri.query).to start_with 'id_token_hint&state='
+      expect(logout_uri.query).to end_with '&post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out'
     end
   end
 end
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index e4e0ea4f8..6f795b0c4 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -1,56 +1,106 @@
 require 'rails_helper'
 
-RSpec.shared_examples 'an unsuccessful request' do
-  it 'returns an empty hash' do
-    allow(mock_response).to receive(:body).and_return({}.to_json)
-    expect(result).to eq({})
-  end
-end
-
-RSpec.shared_examples 'a successful request' do |payload|
-  it 'returns a hash of user data' do
-    allow(mock_response).to receive(:body).and_return(payload.to_json)
-    expect(result).to eq(payload)
-  end
-end
-
 RSpec.describe GovOneAuthService do
   let(:code) { 'mock_code' }
   let(:mock_response) { instance_double('response') }
   let(:auth_service) { described_class.new(code: code) }
-  let(:mock_http) { instance_double('Sentry::Net::HTTP') }
+  let(:payload) { { 'info' => 'auth_info' } }
 
   before do
-    allow(Rails.application.config).to receive(:gov_one_base_uri).and_return('https://example.com')
-    allow(Rails.application.config).to receive(:gov_one_redirect_uri).and_return('mock_redirect_uri')
-    allow(mock_http).to receive(:request).and_return(mock_response)
-    allow(Net::HTTP).to receive(:new).and_return(mock_http)
+    allow(auth_service).to receive(:response).and_return(mock_response)
   end
 
   describe '#tokens' do
-    let(:payload) { { 'access_token' => 'mock_access_token', 'id_token' => 'mock_id_token' } }
     let(:result) { auth_service.tokens }
 
     context 'when the request is successful' do
-      it_behaves_like 'a successful request'
+      it 'returns a hash of tokens' do
+        allow(mock_response).to receive(:body).and_return(payload.to_json)
+
+        expect(result).to eq(payload)
+        expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Post), an_instance_of(Net::HTTP))
+      end
     end
 
     context 'when the request is unsuccessful' do
-      it_behaves_like 'an unsuccessful request'
+      it 'returns an empty hash' do
+        allow(mock_response).to receive(:body).and_return({}.to_json)
+
+        expect(result).to eq({})
+        expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Post), an_instance_of(Net::HTTP))
+      end
     end
   end
 
   describe '#user_info' do
     let(:access_token) { 'mock_access_token' }
-    let(:payload) { { 'email' => 'test@test.com' } }
     let(:result) { auth_service.user_info(access_token) }
 
     context 'when the request is successful' do
-      it_behaves_like 'a successful request'
+      it 'returns a hash of user info' do
+        allow(mock_response).to receive(:body).and_return(payload.to_json)
+
+        expect(result).to eq(payload)
+        expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Get), an_instance_of(Net::HTTP))
+      end
     end
 
     context 'when the request is unsuccessful' do
-      it_behaves_like 'an unsuccessful request'
+      it 'returns an empty hash' do
+        allow(mock_response).to receive(:body).and_return({}.to_json)
+
+        expect(result).to eq({})
+        expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Get), an_instance_of(Net::HTTP))
+      end
+    end
+  end
+
+  describe 'CALLBACKS' do
+    it 'returns a frozen hash of callbacks urls' do
+      expect(described_class::CALLBACKS).to eq({
+        login: 'http://recovery.app/users/auth/openid_connect/callback',
+        logout: 'http://recovery.app/users/sign_out',
+      })
+
+      expect(described_class::CALLBACKS).to be_frozen
+    end
+  end
+
+  describe 'ENDPOINTS' do
+    it 'returns a frozen hash of endpoints urls' do
+      expect(described_class::ENDPOINTS).to eq({
+        login: 'https://oidc.test.account.gov.uk/authorize',
+        logout: 'https://oidc.test.account.gov.uk/logout',
+        token: 'https://oidc.test.account.gov.uk/token',
+        userinfo: 'https://oidc.test.account.gov.uk/userinfo',
+        jwks: 'https://oidc.test.account.gov.uk/.well-known/jwks.json',
+      })
+
+      expect(described_class::ENDPOINTS).to be_frozen
+    end
+  end
+
+  describe '#token_body' do
+    let(:token_body) { auth_service.send(:token_body) }
+
+    it 'returns a hash of correct token body' do
+      expect(token_body[:grant_type]).to eq('authorization_code')
+      expect(token_body[:code]).to eq(code)
+      expect(token_body[:redirect_uri]).to end_with('/users/auth/openid_connect/callback')
+      expect(token_body[:client_assertion_type]).to eq('urn:ietf:params:oauth:client-assertion-type:jwt-bearer')
+    end
+  end
+
+  describe '#jwt_payload' do
+    let(:jwt_payload) { auth_service.send(:jwt_payload) }
+
+    it 'returns a hash of correct jwt payload' do
+      expect(jwt_payload[:aud]).to eq('https://oidc.test.account.gov.uk/token')
+      expect(jwt_payload[:iss]).to be_a(String)
+      expect(jwt_payload[:sub]).to be_a(String)
+      expect(jwt_payload[:exp]).to be_between(Time.zone.now.to_i + 4 * 60, Time.zone.now.to_i + 6 * 60)
+      expect(jwt_payload[:jti]).to be_a(String)
+      expect(jwt_payload[:iat]).to be_a(Integer)
     end
   end
 end

From c035a0f51a9ecc57ecc33a6ac7a672ef211621b3 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 13 Nov 2023 16:34:11 +0000
Subject: [PATCH 040/122] remove typos

---
 app/helpers/gov_one_helper.rb       | 11 -----------
 app/views/gov_one/info.html.slim    |  6 ------
 spec/helpers/gov_one_helper_spec.rb |  8 --------
 3 files changed, 25 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 6883ba810..b9fea0c05 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -28,20 +28,9 @@ def logout_uri
 
 private
 
-<<<<<<< HEAD
-  # @return [String]
-  def current_id_token
-    session[:id_token]
-  end
-
-  # @param endpoint [String] the gov one endpoint
-  # @param params [Hash] query params
-  # @return [URI]
-=======
   # @param endpoint [Symbol]
   # @param params [Hash]
   # @return [URI::HTTP, URI::HTTPS]
->>>>>>> gov-one-refactor
   def gov_one_uri(endpoint, params)
     uri = URI.parse(GovOneAuthService::ENDPOINTS[endpoint])
     uri.query = URI.encode_www_form(params)
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index ffb837b51..fce694838 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -8,13 +8,7 @@
     = m('gov_one.body')
 
     - if current_user
-<<<<<<< HEAD
-      = govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri.to_s
-    - else
-      = govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri.to_s
-=======
       = govuk_button_link_to t('gov_one.links.sign_out'), logout_uri.to_s
     - else
       = govuk_button_link_to t('gov_one.links.sign_in'), login_uri.to_s
->>>>>>> gov-one-refactor
   
\ No newline at end of file
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index 986814152..33fe599d3 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -23,17 +23,9 @@
       expect(logout_uri.path).to eq '/logout'
     end
 
-<<<<<<< HEAD
-    it 'constructs the URI with correct parameters' do
-      uri = class_instance.logout_uri
-
-      expect(uri.to_s).to include('https://oidc.integration.account.gov.uk/logout')
-      expect(uri.query).to include('id_token_hint=mock_id_token', 'state=', 'post_logout_redirect_uri=mock_logout_redirect_uri')
-=======
     it 'encodes the logout endpoint params' do
       expect(logout_uri.query).to start_with 'id_token_hint&state='
       expect(logout_uri.query).to end_with '&post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out'
->>>>>>> gov-one-refactor
     end
   end
 end

From 818839b04b01c51e13cdf975a70afb8b2c0f629d Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 13 Nov 2023 16:46:42 +0000
Subject: [PATCH 041/122] remove unneeded env variables from docker compose and
 ci workflow

---
 .github/workflows/ci.yml | 4 ----
 docker-compose.test.yml  | 3 ---
 2 files changed, 7 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 71fa005ee..afcda98a2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,10 +33,6 @@ jobs:
       DATABASE_URL: postgres://postgres:password@localhost:5432/test
       DOMAIN: recovery.app
       BOT_TOKEN: bot_token
-      GOV_ONE_CLIENT_ID: client_id
-      GOV_ONE_BASE_URI: https://oidc.integration.account.gov.uk
-      GOV_ONE_REDIRECT_URI: mock_redirect_uri
-
     services:
       postgres:
         image: postgres:15.4-alpine
diff --git a/docker-compose.test.yml b/docker-compose.test.yml
index b1ff4aaf8..09abac58e 100644
--- a/docker-compose.test.yml
+++ b/docker-compose.test.yml
@@ -14,8 +14,5 @@ services:
       - DEBUG=false
       - BOT_TOKEN=bot_token
       - TIMEOUT_MINUTES=25
-      - GOV_ONE_CLIENT_ID=client_id
-      - GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk
-      - GOV_ONE_REDIRECT_URI=mock_redirect_uri
     tty: true
     stdin_open: true

From 48297ce97bf727f11802c65568db7d661c2c218a Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 14 Nov 2023 10:08:14 +0000
Subject: [PATCH 042/122] enable one login by default except for production

---
 config/application.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/application.rb b/config/application.rb
index 6eeab7a6b..17558d36b 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -120,7 +120,7 @@ def gov_one_login?
 
     # @return [Boolean]
     def maintenance?
-      Types::Params::Bool[ENV.fetch('MAINTENANCE', false)]
+      Types::Params::Bool[ENV.fetch('MAINTENANCE', true)] && !live?
     end
 
     # @return [ActiveSupport::TimeWithZone]

From dffa92100f997363a3da07baea27ccf0102c3c85 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 14 Nov 2023 10:15:02 +0000
Subject: [PATCH 043/122] update application gov_one_login? check

---
 config/application.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/config/application.rb b/config/application.rb
index 17558d36b..8ea687452 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -115,12 +115,12 @@ def debug?
 
     # @return [Boolean]
     def gov_one_login?
-      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', false)]
+      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', true)] && !live?
     end
 
     # @return [Boolean]
     def maintenance?
-      Types::Params::Bool[ENV.fetch('MAINTENANCE', true)] && !live?
+      Types::Params::Bool[ENV.fetch('MAINTENANCE', false)]
     end
 
     # @return [ActiveSupport::TimeWithZone]

From 5af4ec3e5d1df316799da3286a4c66598c7a0c60 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 14 Nov 2023 11:12:27 +0000
Subject: [PATCH 044/122] update account page for one login

---
 app/views/user/show.html.slim | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/app/views/user/show.html.slim b/app/views/user/show.html.slim
index 1cf9f3e03..ab99e56f1 100644
--- a/app/views/user/show.html.slim
+++ b/app/views/user/show.html.slim
@@ -14,14 +14,8 @@
         - row.with_key { 'Name' }
         - row.with_value(text: current_user.name, classes: %w[data-hj-suppress])
         - row.with_action(text: 'Change name', href: edit_registration_name_path, html_attributes: { id: :edit_name_registration })
-      - your_details.with_row do |row|
-        - row.with_key { 'Email' }
-        - row.with_value(text:current_user.email, classes: %w[data-hj-suppress])
-        - row.with_action(text: 'Change email', href: edit_email_user_path, html_attributes: { id: :edit_email_user })
-      - your_details.with_row do |row|
-        - row.with_key { 'Password' }
-        - row.with_value { t('my_account.password_changed', date: current_user.password_last_changed) }
-        - row.with_action(text: 'Change password', href: edit_password_user_path, html_attributes: { id: :edit_password_user })
+    .govuk-body-small
+      = m('my_account.name_information')
 
     = govuk_summary_list do |other_details|
       - other_details.with_row do |row|

From adaa7027e2c0d0e5cce00d88d09f42d96e4ec1c0 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 14 Nov 2023 14:00:28 +0000
Subject: [PATCH 045/122] create gov one bridging page

---
 app/controllers/gov_one_controller.rb |  7 ++++++-
 app/views/gov_one/info.html.slim      | 30 +++++++++++++++++++--------
 2 files changed, 27 insertions(+), 10 deletions(-)

diff --git a/app/controllers/gov_one_controller.rb b/app/controllers/gov_one_controller.rb
index 8c4f49d46..33fc1aafd 100644
--- a/app/controllers/gov_one_controller.rb
+++ b/app/controllers/gov_one_controller.rb
@@ -1,3 +1,8 @@
 class GovOneController < ApplicationController
-  def info; end
+
+  layout "hero"
+
+  def info
+    redirect_to my_modules_path if current_user
+  end
 end
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index fce694838..a97326df7 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -1,14 +1,26 @@
-= render 'learning/cms_debug'
-
 - content_for :page_title do
-  = html_title t('gov_one.title')
+  = html_title t('home.title')
+
+- content_for :hero do
+  .govuk-grid-row class='govuk-!-padding-top-9 govuk-!-padding-bottom-0'
+    .govuk-grid-column-three-quarters
+      h1.dfe-heading-xl class='govuk-!-margin-bottom-4'
+        = t('gov-one-info.hero-header')
+      p.govuk-body-l = t('gov-one-info.hero-body')
+
 
 .govuk-grid-row
-  .govuk-grid-column-one-half
-    = m('gov_one.body')
+  .govuk-grid-column-three-quarters
+    . class='govuk-!-margin-bottom-5'
+        = m('gov-one-info.body')
+        hr
+
+    = govuk_button_link_to t('gov-one-info.sign-int-button'), login_uri
 
-    - if current_user
-      = govuk_button_link_to t('gov_one.links.sign_out'), logout_uri.to_s
-    - else
-      = govuk_button_link_to t('gov_one.links.sign_in'), login_uri.to_s
+    details.govuk-details class='govuk-!-margin-top-6' data-module='govuk-details'
+        summary.govuk-details__summary
+          span.govuk-details__summary-text
+            | How to access an existing training account
+        .govuk-details__text
+          | If you have an existing early years child development training account but you do not yet have a GOV.UK One Login, you must use the same email address for both accounts. This will ensure that any progress you have made through the training is retained.
   
\ No newline at end of file

From a15457b3977a5883cfce7d3c76756f1d85e23d15 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 14 Nov 2023 17:08:17 +0000
Subject: [PATCH 046/122] update gov one helper and specs

---
 .env.example                                  |  4 --
 .../users/omniauth_callbacks_controller.rb    |  2 -
 app/helpers/gov_one_helper.rb                 | 14 ++++-
 app/models/user.rb                            |  5 +-
 app/views/gov_one/info.html.slim              |  4 +-
 config/application.rb                         |  8 +--
 config/credentials/test.yml.enc               |  2 +-
 config/routes.rb                              |  2 +-
 docker-compose.dev.yml                        |  3 +-
 spec/helpers/gov_one_helper_spec.rb           | 34 +++++++-----
 spec/models/user_spec.rb                      | 35 ++++++++++++
 spec/services/gov_one_auth_service_spec.rb    | 55 +++++++++++++------
 12 files changed, 117 insertions(+), 51 deletions(-)

diff --git a/.env.example b/.env.example
index ae0b13f45..781f4309c 100644
--- a/.env.example
+++ b/.env.example
@@ -82,10 +82,6 @@ E2E=true
 # Deployment environment
 ENVIRONMENT=
 
-# Gov One Authentication
-GOV_ONE_LOGIN=true
-GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk
-
 
 # DISPLAY SERVICE_UNAVAILABLE PAGE
 MAINTENANCE = true
\ No newline at end of file
diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index bed63b459..817d3281d 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -5,8 +5,6 @@ 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? || Rails.application.gov_one_login?
-
     session.delete(:gov_one_auth_state)
 
     auth_service = GovOneAuthService.new(code: params['code'])
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index b9fea0c05..13027decf 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -12,7 +12,7 @@ def login_uri
 
     session[:gov_one_auth_state] = params[:state]
 
-    gov_one_uri(:login, params)
+    gov_one_uri(:login, params).to_s
   end
 
   # @return [String]
@@ -23,7 +23,17 @@ def logout_uri
       post_logout_redirect_uri: GovOneAuthService::CALLBACKS[:logout],
     }
 
-    gov_one_uri(:logout, params)
+    gov_one_uri(:logout, params).to_s
+  end
+
+  # @return [String]
+  def login_button
+    govuk_button_link_to t('gov_one.links.sign_in'), login_uri
+  end
+
+  # @return [String]
+  def logout_button
+    govuk_button_link_to t('gov_one.links.sign_out'), logout_uri
   end
 
 private
diff --git a/app/models/user.rb b/app/models/user.rb
index 711c392a3..f16fdab98 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -26,8 +26,9 @@ def self.dashboard_headers
   # @return [User]
   def self.find_or_create_from_gov_one(email:, gov_one_id:)
     if (user = find_by(email: email) || find_by(gov_one_id: gov_one_id))
-      user.update!(email: email)
-      user.update!(gov_one_id: gov_one_id) if user.gov_one_id.nil?
+      user.update_column(:email, email)
+      user.update_column(:gov_one_id, gov_one_id) if user.gov_one_id.nil?
+      user.save!
     else
       user = new(email: email, gov_one_id: gov_one_id, confirmed_at: Time.zone.now)
       user.save!(validate: false)
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index fce694838..6969b39f2 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -8,7 +8,7 @@
     = m('gov_one.body')
 
     - if current_user
-      = govuk_button_link_to t('gov_one.links.sign_out'), logout_uri.to_s
+      = logout_button
     - else
-      = govuk_button_link_to t('gov_one.links.sign_in'), login_uri.to_s
+      = login_button
   
\ No newline at end of file
diff --git a/config/application.rb b/config/application.rb
index 8ea687452..182ddd68f 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -71,9 +71,9 @@ class Application < Rails::Application
     config.contentful_environment             = ENV.fetch('CONTENTFUL_ENVIRONMENT', credentials.dig(:contentful, :environment))
 
     # Gov one
-    config.gov_one_base_uri     = ENV.fetch('GOV_ONE_BASE_URI', credentials.dig(:gov_one, :base_uri))
-    config.gov_one_private_key  = ENV.fetch('GOV_ONE_PRIVATE_KEY', credentials.dig(:gov_one, :private_key))
-    config.gov_one_client_id    = ENV.fetch('GOV_ONE_CLIENT_ID', credentials.dig(:gov_one, :client_id))
+    config.gov_one_base_uri     = credentials.dig(:gov_one, :base_uri)
+    config.gov_one_private_key  = credentials.dig(:gov_one, :private_key)
+    config.gov_one_client_id    = credentials.dig(:gov_one, :client_id)
 
     # @return [Boolean]
     def live?
@@ -115,7 +115,7 @@ def debug?
 
     # @return [Boolean]
     def gov_one_login?
-      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', true)] && !live?
+      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', false)] || !live?
     end
 
     # @return [Boolean]
diff --git a/config/credentials/test.yml.enc b/config/credentials/test.yml.enc
index 69becba05..d7eaeabb8 100644
--- a/config/credentials/test.yml.enc
+++ b/config/credentials/test.yml.enc
@@ -1 +1 @@
-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--ub0s9DBRWGcD5tT1--YSXZBp3jUhqXA6bl51La3w==
\ No newline at end of file
+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--Qdgcpq2DkjafVGqU--C3fQKZmL1SwRwBJpGQ3IZg==
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index 0dc304b67..9de3c4b5c 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' if Rails.application.gov_one_login?
+  get 'gov-one/info', to: 'gov_one#info'
 
   get '/404', to: 'errors#not_found', via: :all
   get '/422', to: 'errors#unprocessable_entity', via: :all
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 8e3a4eeed..7287b9819 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -19,9 +19,8 @@ services:
       # app config
       - DATABASE_URL=postgres://postgres:password@db:5432/early_years_foundation_recovery_development
       - RAILS_ENV=development
-      - DOMAIN=localhost:3000 # put back to app
+      - DOMAIN=app:3000
       - RAILS_SERVE_STATIC_FILES=true
-      - GOV_ONE_BASE_URI=https://oidc.integration.account.gov.uk
     volumes:
       - .:/srv
     tty: true
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index 33fe599d3..5ec6f27cd 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -4,28 +4,36 @@
   describe '#login_uri' do
     subject(:login_uri) { helper.login_uri }
 
-    it 'returns a URI object with the correct host and path' do
-      expect(login_uri.host).to eq 'oidc.test.account.gov.uk'
-      expect(login_uri.path).to eq '/authorize'
-    end
-
     it 'encodes the authorize endpoint params' do
-      expect(login_uri.query).to start_with 'response_type=code&scope=email+openid&client_id='
-      expect(login_uri.query).to end_with '&redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback'
+      expect(login_uri).to start_with 'https://oidc.test.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id='
+      expect(login_uri).to end_with 'redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback'
     end
   end
 
   describe '#logout_uri' do
     subject(:logout_uri) { helper.logout_uri }
 
-    it 'returns a URI object with the correct host and path' do
-      expect(logout_uri.host).to eq 'oidc.test.account.gov.uk'
-      expect(logout_uri.path).to eq '/logout'
+    it 'encodes the logout endpoint params' do
+      expect(logout_uri).to start_with 'https://oidc.test.account.gov.uk/logout?id_token_hint&state='
+      expect(logout_uri).to end_with '&post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out'
     end
+  end
 
-    it 'encodes the logout endpoint params' do
-      expect(logout_uri.query).to start_with 'id_token_hint&state='
-      expect(logout_uri.query).to end_with '&post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out'
+  describe '#login_button' do
+    subject(:login_button) { helper.login_button }
+
+    it 'returns a link to the login uri' do
+      expect(login_button).to include 'govuk-button'
+      expect(login_button).to include 'Sign in with Gov One Login'
+    end
+  end
+
+  describe '#logout_button' do
+    subject(:logout_button) { helper.logout_button }
+
+    it 'returns a link to the login uri' do
+      expect(logout_button).to include 'govuk-button'
+      expect(logout_button).to include 'Sign out of Gov One Login'
     end
   end
 end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index fd0217a17..220633237 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -206,4 +206,39 @@
       end
     end
   end
+
+  describe '.find_or_create_from_gov_one' do
+    let(:email) { 'test@test.com' }
+    let(:gov_one_id) { '123' }
+
+    context 'when user exists with email but no gov_one_id' do
+      let!(:user) { create(:user, email: email) }
+
+      it 'updates the user gov_one_id' do
+        described_class.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
+        expect(user.reload.gov_one_id).to eq(gov_one_id)
+      end
+    end
+
+    context 'when user exists with gov_one_id' do
+      let!(:user) { create(:user, gov_one_id: gov_one_id) }
+
+      it 'updates the user email' do
+        described_class.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
+        expect(user.reload.email).to eq(email)
+      end
+    end
+
+    context 'when user does not exist' do
+      let(:email) { 'some_new_email@test.com' }
+      let(:gov_one_id) { '321' }
+
+      it 'creates a new user' do
+        described_class.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
+        expect(described_class.count).to eq(1)
+        expect(described_class.first.email).to eq(email)
+        expect(described_class.first.gov_one_id).to eq(gov_one_id)
+      end
+    end
+  end
 end
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index 6f795b0c4..564ccfa14 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -56,27 +56,46 @@
   end
 
   describe 'CALLBACKS' do
-    it 'returns a frozen hash of callbacks urls' do
-      expect(described_class::CALLBACKS).to eq({
-        login: 'http://recovery.app/users/auth/openid_connect/callback',
-        logout: 'http://recovery.app/users/sign_out',
-      })
+    subject(:callbacks) { described_class::CALLBACKS }
 
-      expect(described_class::CALLBACKS).to be_frozen
+    specify 'callbacks' do
+      expect(callbacks).to be_frozen
+    end
+
+    specify 'login' do
+      expect(callbacks[:login]).to eq 'http://recovery.app/users/auth/openid_connect/callback'
+    end
+
+    specify 'logout' do
+      expect(callbacks[:logout]).to eq 'http://recovery.app/users/sign_out'
     end
   end
 
   describe 'ENDPOINTS' do
-    it 'returns a frozen hash of endpoints urls' do
-      expect(described_class::ENDPOINTS).to eq({
-        login: 'https://oidc.test.account.gov.uk/authorize',
-        logout: 'https://oidc.test.account.gov.uk/logout',
-        token: 'https://oidc.test.account.gov.uk/token',
-        userinfo: 'https://oidc.test.account.gov.uk/userinfo',
-        jwks: 'https://oidc.test.account.gov.uk/.well-known/jwks.json',
-      })
-
-      expect(described_class::ENDPOINTS).to be_frozen
+    subject(:endpoints) { described_class::ENDPOINTS }
+
+    specify 'endpoints' do
+      expect(endpoints).to be_frozen
+    end
+
+    specify 'login' do
+      expect(endpoints[:login]).to eq 'https://oidc.test.account.gov.uk/authorize'
+    end
+
+    specify 'logout' do
+      expect(endpoints[:logout]).to eq 'https://oidc.test.account.gov.uk/logout'
+    end
+
+    specify 'token' do
+      expect(endpoints[:token]).to eq 'https://oidc.test.account.gov.uk/token'
+    end
+
+    specify 'userinfo' do
+      expect(endpoints[:userinfo]).to eq 'https://oidc.test.account.gov.uk/userinfo'
+    end
+
+    specify 'jwks' do
+      expect(endpoints[:jwks]).to eq 'https://oidc.test.account.gov.uk/.well-known/jwks.json'
     end
   end
 
@@ -96,8 +115,8 @@
 
     it 'returns a hash of correct jwt payload' do
       expect(jwt_payload[:aud]).to eq('https://oidc.test.account.gov.uk/token')
-      expect(jwt_payload[:iss]).to be_a(String)
-      expect(jwt_payload[:sub]).to be_a(String)
+      expect(jwt_payload[:iss]).to eq('some_client_id')
+      expect(jwt_payload[:sub]).to eq('some_client_id')
       expect(jwt_payload[:exp]).to be_between(Time.zone.now.to_i + 4 * 60, Time.zone.now.to_i + 6 * 60)
       expect(jwt_payload[:jti]).to be_a(String)
       expect(jwt_payload[:iat]).to be_a(Integer)

From 8410f8fe8ba0056358c0eb34b1461dab93e41cbf Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 15 Nov 2023 14:21:12 +0000
Subject: [PATCH 047/122] update bridging page

---
 app/views/gov_one/info.html.slim | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index 265026392..3522640ec 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -17,10 +17,11 @@
 
     = govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri
 
-    details.govuk-details class='govuk-!-margin-top-6 govuk-!-margin-bottom-9' data-module='govuk-details'
-        summary.govuk-details__summary
-          span.govuk-details__summary-text
-            | How to access an existing training account
-        .govuk-details__text
-          | If you have an existing early years child development training account but you do not yet have a GOV.UK One Login, you must use the same email address for both accounts. This will ensure that any progress you have made through the training is retained.
+. class='govuk-!-margin-top-6 govuk-!-margin-bottom-9'
+  details.govuk-details data-module='govuk-details'
+      summary.govuk-details__summary
+        span.govuk-details__summary-text
+          = t('gov-one-info.details-summary')
+      .govuk-details__text
+        = t('gov-one-info.details-text')
   
\ No newline at end of file

From b7a8ff54cdbca449431cababc258c929c052327c Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 09:59:12 +0000
Subject: [PATCH 048/122] update homepage

---
 app/controllers/gov_one_controller.rb |  3 +--
 app/views/home/_hero.html.slim        |  7 ++----
 app/views/home/index.html.slim        | 17 ++++++---------
 spec/system/gov_one_info_spec.rb      | 31 +++++++++++++++++++++++++++
 4 files changed, 41 insertions(+), 17 deletions(-)
 create mode 100644 spec/system/gov_one_info_spec.rb

diff --git a/app/controllers/gov_one_controller.rb b/app/controllers/gov_one_controller.rb
index 33fc1aafd..de6d437dd 100644
--- a/app/controllers/gov_one_controller.rb
+++ b/app/controllers/gov_one_controller.rb
@@ -1,6 +1,5 @@
 class GovOneController < ApplicationController
-
-  layout "hero"
+  layout 'hero'
 
   def info
     redirect_to my_modules_path if current_user
diff --git a/app/views/home/_hero.html.slim b/app/views/home/_hero.html.slim
index 3406bc55d..a148caac1 100644
--- a/app/views/home/_hero.html.slim
+++ b/app/views/home/_hero.html.slim
@@ -7,15 +7,12 @@
     p.govuk-body-l
       = t('home.hero')
 
-    = govuk_button_link_to course_overview_path, class: 'govuk-button--start govuk-!-margin-bottom-4' do
+    = link_to course_overview_path, class: 'govuk-!-margin-bottom-4 govuk-link--no-visited-state govuk-!-font-weight-bold govuk-body' do
       - if current_user
         | Learn more
       - else
-        | Learn more and enrol
+        | Learn more about this training
         p.govuk-visually-hidden on the course
 
-      svg.govuk-button__start-icon xmlns='http://www.w3.org/2000/svg' width='17.5' height='19' viewBox='0 0 33 40' aria-hidden='true' focusable='false'
-        path fill='currentColor' d='M0 0h13l20 20-20 20H0l20-20z'
-
   .govuk-grid-column-one-third class='govuk-!-text-align-right'
     = m('home.thumb')
\ No newline at end of file
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index ac3501a91..c29377f96 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -11,16 +11,6 @@
   .govuk-grid-column-one-half
     = m('home.about', headings_start_with: 'xl')
 
-  - unless current_user
-    .govuk-grid-column-one-half
-      .light-grey-box.enrol-box
-        = m('home.login', headings_start_with: 'xl')
-
-        .govuk-button-group
-          = govuk_button_link_to 'Sign in', new_user_session_path
-          .white-space-pre-wrap= ' or '
-          = govuk_link_to 'create an account', new_user_registration_path
-
 .prompt.prompt-home
   .govuk-grid-row
     .govuk-grid-column-one-quarter
@@ -28,3 +18,10 @@
 
     .govuk-grid-column-three-quarters
       = m('home.prompt', headings_start_with: 'xl')
+
+- unless current_user
+  .govuk-button-group
+    = govuk_button_link_to 'gov-one/info', class: "govuk-button--start" do
+      | #{t('home.gov-one-button')}
+      svg.govuk-button__start-icon xmlns='http://www.w3.org/2000/svg' width='17.5' height='19' viewBox='0 0 33 40' aria-hidden='true' focusable='false'
+          path fill='currentColor' d='M0 0h13l20 20-20 20H0l20-20z'
diff --git a/spec/system/gov_one_info_spec.rb b/spec/system/gov_one_info_spec.rb
new file mode 100644
index 000000000..9a7073f93
--- /dev/null
+++ b/spec/system/gov_one_info_spec.rb
@@ -0,0 +1,31 @@
+require 'rails_helper'
+
+RSpec.describe 'Gov One Info' do
+  context 'when the user is not logged in' do
+    before do
+      visit '/gov-one/info'
+    end
+
+    it 'displays the correct content' do
+      expect(page).to have_css('h1', text: 'How to access this training course')
+      expect(page).to have_css('p', text: 'This service uses GOV.UK One Login which is managed by the Government Digital Service.')
+      expect(page).to have_css('p', text: 'You will be asked to sign in to your account, or create a One Login account, in this service')
+      expect(page).to have_css('a', text: 'Continue to GOV.UK One Login')
+    end
+
+    it 'has the correct login link' do
+      link = find_link('Continue to GOV.UK One Login')
+      expect(link[:href]).to start_with('https://oidc.test.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id=some_client_id')
+      expect(link[:href]).to end_with('&redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback')
+    end
+  end
+
+  context 'when the user is logged in' do
+    include_context 'with user'
+
+    it 'they are redirected to the my modules page' do
+      visit '/gov-one/info'
+      expect(page).to have_current_path('/my-modules')
+    end
+  end
+end

From 36ee42d12e27394f9c0a92bce46dc792fed49912 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 10:53:20 +0000
Subject: [PATCH 049/122] update gov one helper and specs

---
 app/helpers/gov_one_helper.rb       |  7 ++-----
 spec/helpers/gov_one_helper_spec.rb | 12 +++++++-----
 spec/models/user_spec.rb            |  6 +++---
 3 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 13027decf..c8c30e321 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -7,12 +7,11 @@ def login_uri
       client_id: Rails.application.config.gov_one_client_id,
       nonce: SecureRandom.uuid,
       state: SecureRandom.uuid,
-      redirect_uri: GovOneAuthService::CALLBACKS[:login],
     }
 
     session[:gov_one_auth_state] = params[:state]
 
-    gov_one_uri(:login, params).to_s
+    "#{gov_one_uri(:login, params)}&redirect_uri=#{GovOneAuthService::CALLBACKS[:login]}"
   end
 
   # @return [String]
@@ -20,10 +19,8 @@ def logout_uri
     params = {
       id_token_hint: session[:id_token],
       state: SecureRandom.uuid,
-      post_logout_redirect_uri: GovOneAuthService::CALLBACKS[:logout],
     }
-
-    gov_one_uri(:logout, params).to_s
+    "#{gov_one_uri(:logout, params)}&post_logout_redirect_uri=#{GovOneAuthService::CALLBACKS[:logout]}"
   end
 
   # @return [String]
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index 5ec6f27cd..f9693a7e3 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -5,8 +5,8 @@
     subject(:login_uri) { helper.login_uri }
 
     it 'encodes the authorize endpoint params' do
-      expect(login_uri).to start_with 'https://oidc.test.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id='
-      expect(login_uri).to end_with 'redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback'
+      expect(login_uri).to start_with 'https://oidc.test.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id=some_client_id&'
+      expect(login_uri).to end_with 'redirect_uri=http://recovery.app/users/auth/openid_connect/callback'
     end
   end
 
@@ -15,25 +15,27 @@
 
     it 'encodes the logout endpoint params' do
       expect(logout_uri).to start_with 'https://oidc.test.account.gov.uk/logout?id_token_hint&state='
-      expect(logout_uri).to end_with '&post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out'
+      expect(logout_uri).to end_with '&post_logout_redirect_uri=http://recovery.app/users/sign_out'
     end
   end
 
   describe '#login_button' do
     subject(:login_button) { helper.login_button }
 
-    it 'returns a link to the login uri' do
+    it 'returns a button link to the gov one login uri' do
       expect(login_button).to include 'govuk-button'
       expect(login_button).to include 'Sign in with Gov One Login'
+      expect(login_button).to include 'href="https://oidc.test.account.gov.uk/authorize?response_type=code&amp;scope=email+openid&amp;client_id=some_client_id&'
     end
   end
 
   describe '#logout_button' do
     subject(:logout_button) { helper.logout_button }
 
-    it 'returns a link to the login uri' do
+    it 'returns a button link to the gov one logout uri' do
       expect(logout_button).to include 'govuk-button'
       expect(logout_button).to include 'Sign out of Gov One Login'
+      expect(logout_button).to include 'href="https://oidc.test.account.gov.uk/logout?id_token_hint&amp;state='
     end
   end
 end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 220633237..1c1d0afa7 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -211,7 +211,7 @@
     let(:email) { 'test@test.com' }
     let(:gov_one_id) { '123' }
 
-    context 'when user exists with email but no gov_one_id' do
+    context 'with an existing user having an email but no gov_one_id' do
       let!(:user) { create(:user, email: email) }
 
       it 'updates the user gov_one_id' do
@@ -220,7 +220,7 @@
       end
     end
 
-    context 'when user exists with gov_one_id' do
+    context 'with an existing user having a gov_one_id' do
       let!(:user) { create(:user, gov_one_id: gov_one_id) }
 
       it 'updates the user email' do
@@ -229,7 +229,7 @@
       end
     end
 
-    context 'when user does not exist' do
+    context 'without an existing user' do
       let(:email) { 'some_new_email@test.com' }
       let(:gov_one_id) { '321' }
 

From e35f97e6ccca36cac2ce5033a44b9c68ec09acb0 Mon Sep 17 00:00:00 2001
From: jack-coggin <119428483+jack-coggin@users.noreply.github.com>
Date: Thu, 16 Nov 2023 10:58:18 +0000
Subject: [PATCH 050/122] Remove .env.example whitespace

Remove whitespace from .env.example
---
 .env.example | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.env.example b/.env.example
index 311b11fcd..fac18a28a 100644
--- a/.env.example
+++ b/.env.example
@@ -85,4 +85,3 @@ E2E=true
 
 # Deployment environment
 ENVIRONMENT=
-

From 2cf70646c5c7876e61ad8e60ec728b1eb33a86b9 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 11:23:53 +0000
Subject: [PATCH 051/122] update application_helper links, gov_one_helper spec
 and revert changes to account page

---
 app/helpers/application_helper.rb   |  8 ++++----
 app/views/user/show.html.slim       | 10 ++++++++--
 spec/helpers/gov_one_helper_spec.rb | 12 +-----------
 3 files changed, 13 insertions(+), 17 deletions(-)

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index d7b80bbf6..cdb815c45 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -11,14 +11,14 @@ def navigation
       header.with_navigation_item(text: 'Home', href: root_path, classes: %w[dfe-header__navigation-item])
       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_action_link(text: 'Sign out', href: logout_uri, 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: %w[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: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' })
+        header.with_navigation_item(text: 'Sign out', href: logout_uri, options: { data: { turbo_method: :get } }, classes: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' })
       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: %w[dfe-header__navigation-item dfe-header-f-mob])
+        header.with_action_link(text: 'Sign in', href: 'gov-one/info', options: { inverse: true })
+        header.with_navigation_item(text: 'Sign in', href: 'gov-one/info', classes: %w[dfe-header__navigation-item dfe-header-f-mob])
       end
     end
   end
diff --git a/app/views/user/show.html.slim b/app/views/user/show.html.slim
index ab99e56f1..1cf9f3e03 100644
--- a/app/views/user/show.html.slim
+++ b/app/views/user/show.html.slim
@@ -14,8 +14,14 @@
         - row.with_key { 'Name' }
         - row.with_value(text: current_user.name, classes: %w[data-hj-suppress])
         - row.with_action(text: 'Change name', href: edit_registration_name_path, html_attributes: { id: :edit_name_registration })
-    .govuk-body-small
-      = m('my_account.name_information')
+      - your_details.with_row do |row|
+        - row.with_key { 'Email' }
+        - row.with_value(text:current_user.email, classes: %w[data-hj-suppress])
+        - row.with_action(text: 'Change email', href: edit_email_user_path, html_attributes: { id: :edit_email_user })
+      - your_details.with_row do |row|
+        - row.with_key { 'Password' }
+        - row.with_value { t('my_account.password_changed', date: current_user.password_last_changed) }
+        - row.with_action(text: 'Change password', href: edit_password_user_path, html_attributes: { id: :edit_password_user })
 
     = govuk_summary_list do |other_details|
       - other_details.with_row do |row|
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index f9693a7e3..829e6f149 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -24,18 +24,8 @@
 
     it 'returns a button link to the gov one login uri' do
       expect(login_button).to include 'govuk-button'
-      expect(login_button).to include 'Sign in with Gov One Login'
+      expect(login_button).to include 'Continue to GOV.UK One Login'
       expect(login_button).to include 'href="https://oidc.test.account.gov.uk/authorize?response_type=code&amp;scope=email+openid&amp;client_id=some_client_id&'
     end
   end
-
-  describe '#logout_button' do
-    subject(:logout_button) { helper.logout_button }
-
-    it 'returns a button link to the gov one logout uri' do
-      expect(logout_button).to include 'govuk-button'
-      expect(logout_button).to include 'Sign out of Gov One Login'
-      expect(logout_button).to include 'href="https://oidc.test.account.gov.uk/logout?id_token_hint&amp;state='
-    end
-  end
 end

From 784f11626c3392428136e15c0b7bd2f89b095f60 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 11:58:25 +0000
Subject: [PATCH 052/122] skip whats_new_page_spec and update locales

---
 config/locales/en.yml              |  2 --
 spec/system/whats_new_page_spec.rb | 22 ++++++++++++----------
 2 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/config/locales/en.yml b/config/locales/en.yml
index fdff220a9..9d2d87198 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -508,8 +508,6 @@ en:
   home:
     title: Home page
     hero: |
-      # %{service_name}
-
       This free, online training provides an overview of child development and gives practical advice for supporting the development of children in your early years setting.
     thumb: |
       ![A practitioner smiling as they do an activity with children in an early years setting](//images.ctfassets.net/dvmeh832nmjc/2VRBZCHJYLeKx0a2CIcJzE/687fb0d28c71104820cd42f131d7dfe2/_assets_thumb-1161068272-320x240.jpg?fit=scale&w=380)
diff --git a/spec/system/whats_new_page_spec.rb b/spec/system/whats_new_page_spec.rb
index 7fc194638..1a86631ff 100644
--- a/spec/system/whats_new_page_spec.rb
+++ b/spec/system/whats_new_page_spec.rb
@@ -20,18 +20,20 @@
     end
 
     describe 'with subsequent logins' do
-      before do
-        click_on 'sign-out-desktop'
+      skip 'wip' do
+        before do
+          click_on 'sign-out-desktop'
 
-        visit '/users/sign-in'
-        fill_in 'Email address', with: user.email
-        fill_in 'Password', with: user.password
-        click_button 'Sign in'
-      end
+          visit '/users/sign-in'
+          fill_in 'Email address', with: user.email
+          fill_in 'Password', with: user.password
+          click_button 'Sign in'
+        end
 
-      it 'does not appear' do
-        expect(page).not_to have_current_path '/whats-new'
-        expect(page).to have_current_path '/my-modules'
+        it 'does not appear' do
+          expect(page).not_to have_current_path '/whats-new'
+          expect(page).to have_current_path '/my-modules'
+        end
       end
     end
   end

From 4451a126953fab4d0c7705a8d39dfb2817b7a627 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 14:37:04 +0000
Subject: [PATCH 053/122] use content from cms

---
 app/helpers/gov_one_helper.rb    |  4 ++--
 app/views/gov_one/info.html.slim |  2 +-
 config/locales/en.yml            | 11 -----------
 3 files changed, 3 insertions(+), 14 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index c8c30e321..789ed2aea 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -25,12 +25,12 @@ def logout_uri
 
   # @return [String]
   def login_button
-    govuk_button_link_to t('gov_one.links.sign_in'), login_uri
+    govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri
   end
 
   # @return [String]
   def logout_button
-    govuk_button_link_to t('gov_one.links.sign_out'), logout_uri
+    govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri
   end
 
 private
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index 6969b39f2..74040bf19 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -5,7 +5,7 @@
 
 .govuk-grid-row
   .govuk-grid-column-one-half
-    = m('gov_one.body')
+    = m('gov-one-info.hero-body')
 
     - if current_user
       = logout_button
diff --git a/config/locales/en.yml b/config/locales/en.yml
index fdff220a9..6a261b5d7 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -574,17 +574,6 @@ en:
 
       %{criteria}
 
-  # /gov-one/info
-  gov_one:
-    title: Gov One Login
-    body: |
-      # Gov One Login
-
-      This is some information about Gov One Login...
-    links:
-      sign_in: Sign in with Gov One Login
-      sign_out: Sign out of Gov One Login
-
   # /settings/cookie-policy
   cookie_policy:
     title: Cookie policy

From dadd4b8cc6214b37317c5e857423627a5c42651a Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 14:50:26 +0000
Subject: [PATCH 054/122] update helper spec

---
 spec/helpers/gov_one_helper_spec.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index f9693a7e3..10b923853 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -24,7 +24,7 @@
 
     it 'returns a button link to the gov one login uri' do
       expect(login_button).to include 'govuk-button'
-      expect(login_button).to include 'Sign in with Gov One Login'
+      expect(login_button).to include 'Continue to GOV.UK One Login'
       expect(login_button).to include 'href="https://oidc.test.account.gov.uk/authorize?response_type=code&amp;scope=email+openid&amp;client_id=some_client_id&'
     end
   end

From 7b776bec7a8b892d5ef52fb27d56217c07a179bc Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 15:22:36 +0000
Subject: [PATCH 055/122] add route alias for bridging page

---
 app/helpers/application_helper.rb | 4 ++--
 app/views/home/index.html.slim    | 2 +-
 config/routes.rb                  | 2 +-
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index cdb815c45..623e5c338 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -17,8 +17,8 @@ def navigation
         header.with_navigation_item(text: 'My account', href: user_path, classes: %w[dfe-header__navigation-item dfe-header-f-mob])
         header.with_navigation_item(text: 'Sign out', href: logout_uri, options: { data: { turbo_method: :get } }, classes: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' })
       else
-        header.with_action_link(text: 'Sign in', href: 'gov-one/info', options: { inverse: true })
-        header.with_navigation_item(text: 'Sign in', href: 'gov-one/info', classes: %w[dfe-header__navigation-item dfe-header-f-mob])
+        header.with_action_link(text: 'Sign in', href: gov_one_info_path, options: { inverse: true })
+        header.with_navigation_item(text: 'Sign in', href: gov_one_info_path, classes: %w[dfe-header__navigation-item dfe-header-f-mob])
       end
     end
   end
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index c29377f96..771ecd25b 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -21,7 +21,7 @@
 
 - unless current_user
   .govuk-button-group
-    = govuk_button_link_to 'gov-one/info', class: "govuk-button--start" do
+    = govuk_button_link_to gov_one_info_path, class: "govuk-button--start" do
       | #{t('home.gov-one-button')}
       svg.govuk-button__start-icon xmlns='http://www.w3.org/2000/svg' width='17.5' height='19' viewBox='0 0 33 40' aria-hidden='true' focusable='false'
           path fill='currentColor' d='M0 0h13l20 20-20 20H0l20-20z'
diff --git a/config/routes.rb b/config/routes.rb
index 9de3c4b5c..a93482981 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', as: :gov_one_info
 
   get '/404', to: 'errors#not_found', via: :all
   get '/422', to: 'errors#unprocessable_entity', via: :all

From 9765caf5b0847ebede660f73ab702040cd260c75 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 15:26:18 +0000
Subject: [PATCH 056/122] extract homepage cta to partial

---
 app/views/home/_cta.html.slim  | 5 +++++
 app/views/home/index.html.slim | 6 +-----
 2 files changed, 6 insertions(+), 5 deletions(-)
 create mode 100644 app/views/home/_cta.html.slim

diff --git a/app/views/home/_cta.html.slim b/app/views/home/_cta.html.slim
new file mode 100644
index 000000000..1c114a821
--- /dev/null
+++ b/app/views/home/_cta.html.slim
@@ -0,0 +1,5 @@
+  .govuk-button-group
+    = govuk_button_link_to gov_one_info_path, class: "govuk-button--start" do
+      | #{t('home.gov-one-button')}
+      svg.govuk-button__start-icon xmlns='http://www.w3.org/2000/svg' width='17.5' height='19' viewBox='0 0 33 40' aria-hidden='true' focusable='false'
+          path fill='currentColor' d='M0 0h13l20 20-20 20H0l20-20z'
\ No newline at end of file
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index 771ecd25b..5c4543f0a 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -20,8 +20,4 @@
       = m('home.prompt', headings_start_with: 'xl')
 
 - unless current_user
-  .govuk-button-group
-    = govuk_button_link_to gov_one_info_path, class: "govuk-button--start" do
-      | #{t('home.gov-one-button')}
-      svg.govuk-button__start-icon xmlns='http://www.w3.org/2000/svg' width='17.5' height='19' viewBox='0 0 33 40' aria-hidden='true' focusable='false'
-          path fill='currentColor' d='M0 0h13l20 20-20 20H0l20-20z'
+  = render 'cta'

From 6d74e30cc2059d4f16fd6d78c39f8de752c215f2 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 16 Nov 2023 16:08:59 +0000
Subject: [PATCH 057/122] remove unnecessary route alias

---
 config/routes.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/routes.rb b/config/routes.rb
index a93482981..9de3c4b5c 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', as: :gov_one_info
+  get 'gov-one/info', to: 'gov_one#info'
 
   get '/404', to: 'errors#not_found', via: :all
   get '/422', to: 'errors#unprocessable_entity', via: :all

From dd05c11a84be85350dd0a99e072556785797a7f5 Mon Sep 17 00:00:00 2001
From: Peter David Hamilton <pete@peterdavidhamilton.com>
Date: Thu, 16 Nov 2023 16:46:29 +0000
Subject: [PATCH 058/122] Retest local dev with encoded redirect params

---
 app/helpers/gov_one_helper.rb | 17 ++++++++++-------
 docker-compose.dev.yml        |  3 ++-
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 789ed2aea..5791a5c14 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -1,36 +1,39 @@
 module GovOneHelper
-  # @return [String]
+  # @return [URI]
   def login_uri
     params = {
+      redirect_uri: GovOneAuthService::CALLBACKS[:login],
+      client_id: Rails.application.config.gov_one_client_id,
       response_type: 'code',
       scope: 'email openid',
-      client_id: Rails.application.config.gov_one_client_id,
       nonce: SecureRandom.uuid,
       state: SecureRandom.uuid,
     }
 
     session[:gov_one_auth_state] = params[:state]
 
-    "#{gov_one_uri(:login, params)}&redirect_uri=#{GovOneAuthService::CALLBACKS[:login]}"
+    gov_one_uri(:login, params)
   end
 
-  # @return [String]
+  # @return [URI]
   def logout_uri
     params = {
+      post_logout_redirect_uri: GovOneAuthService::CALLBACKS[:logout],
       id_token_hint: session[:id_token],
       state: SecureRandom.uuid,
     }
-    "#{gov_one_uri(:logout, params)}&post_logout_redirect_uri=#{GovOneAuthService::CALLBACKS[:logout]}"
+
+    gov_one_uri(:logout, params)
   end
 
   # @return [String]
   def login_button
-    govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri
+    govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri.to_s
   end
 
   # @return [String]
   def logout_button
-    govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri
+    govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri.to_s
   end
 
 private
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 7287b9819..11001e395 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -19,7 +19,8 @@ services:
       # app config
       - DATABASE_URL=postgres://postgres:password@db:5432/early_years_foundation_recovery_development
       - RAILS_ENV=development
-      - DOMAIN=app:3000
+      # - DOMAIN=app:3000
+      - DOMAIN=localhost:3000
       - RAILS_SERVE_STATIC_FILES=true
     volumes:
       - .:/srv

From 59bc7410ed9a4a8baa81897c31e211a9ed838c62 Mon Sep 17 00:00:00 2001
From: Peter David Hamilton <pete@peterdavidhamilton.com>
Date: Fri, 17 Nov 2023 08:55:47 +0000
Subject: [PATCH 059/122] Highlight duplication in the service spec with a
 little refactor more to do???

---
 app/services/gov_one_auth_service.rb       |  7 --
 spec/services/gov_one_auth_service_spec.rb | 85 ++++++++++------------
 2 files changed, 38 insertions(+), 54 deletions(-)

diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 7f1d2809a..786ec439c 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -59,13 +59,6 @@ def response(request, http)
     http.request(request)
   end
 
-  # @param request [Net::HTTP::Get, Net::HTTP::Post]
-  # @return [Hash]
-  def handle_request(request)
-    response = http.request(request)
-    JSON.parse(response.body)
-  end
-
   # @param token [String]
   # @return [Array<Hash>]
   def decode_id_token(token)
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index 564ccfa14..d6adfbf89 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -8,25 +8,24 @@
 
   before do
     allow(auth_service).to receive(:response).and_return(mock_response)
+    allow(mock_response).to receive(:body).and_return(payload.to_json)
   end
 
   describe '#tokens' do
     let(:result) { auth_service.tokens }
 
-    context 'when the request is successful' do
+    context 'when successful' do
       it 'returns a hash of tokens' do
-        allow(mock_response).to receive(:body).and_return(payload.to_json)
-
         expect(result).to eq(payload)
         expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Post), an_instance_of(Net::HTTP))
       end
     end
 
-    context 'when the request is unsuccessful' do
-      it 'returns an empty hash' do
-        allow(mock_response).to receive(:body).and_return({}.to_json)
+    context 'when unsuccessful' do
+      let(:payload) { {} }
 
-        expect(result).to eq({})
+      it 'returns an empty hash' do
+        expect(result).to eq(payload)
         expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Post), an_instance_of(Net::HTTP))
       end
     end
@@ -36,31 +35,49 @@
     let(:access_token) { 'mock_access_token' }
     let(:result) { auth_service.user_info(access_token) }
 
-    context 'when the request is successful' do
+    context 'when successful' do
       it 'returns a hash of user info' do
-        allow(mock_response).to receive(:body).and_return(payload.to_json)
-
         expect(result).to eq(payload)
         expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Get), an_instance_of(Net::HTTP))
       end
     end
 
-    context 'when the request is unsuccessful' do
-      it 'returns an empty hash' do
-        allow(mock_response).to receive(:body).and_return({}.to_json)
+    context 'when unsuccessful' do
+      let(:payload) { {} }
 
-        expect(result).to eq({})
+      it 'returns an empty hash' do
+        expect(result).to eq(payload)
         expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Get), an_instance_of(Net::HTTP))
       end
     end
   end
 
-  describe 'CALLBACKS' do
-    subject(:callbacks) { described_class::CALLBACKS }
+  describe '#token_body' do
+    let(:token_body) { auth_service.send(:token_body) }
+
+    it 'returns a hash of correct token body' do
+      expect(token_body[:grant_type]).to eq('authorization_code')
+      expect(token_body[:code]).to eq(code)
+      expect(token_body[:redirect_uri]).to end_with('/users/auth/openid_connect/callback')
+      expect(token_body[:client_assertion_type]).to eq('urn:ietf:params:oauth:client-assertion-type:jwt-bearer')
+    end
+  end
+
+  describe '#jwt_payload' do
+    let(:jwt_payload) { auth_service.send(:jwt_payload) }
 
-    specify 'callbacks' do
-      expect(callbacks).to be_frozen
+    it 'returns a hash of correct jwt payload' do
+      expect(jwt_payload[:aud]).to eq 'https://oidc.test.account.gov.uk/token'
+      expect(jwt_payload[:iss]).to eq 'some_client_id'
+      expect(jwt_payload[:sub]).to eq 'some_client_id'
+      expect(jwt_payload[:exp]).to be_between(Time.zone.now.to_i + 4 * 60, Time.zone.now.to_i + 6 * 60)
+      expect(jwt_payload[:jti]).to be_a String
+      expect(jwt_payload[:iat]).to be_a Integer
     end
+  end
+
+  describe 'Internal callbacks' do
+    subject(:callbacks) { described_class::CALLBACKS }
 
     specify 'login' do
       expect(callbacks[:login]).to eq 'http://recovery.app/users/auth/openid_connect/callback'
@@ -71,13 +88,11 @@
     end
   end
 
-  describe 'ENDPOINTS' do
+  describe 'OIDC endpoints' do
     subject(:endpoints) { described_class::ENDPOINTS }
 
-    specify 'endpoints' do
-      expect(endpoints).to be_frozen
-    end
-
+    # opportunity to add more detail and use specs for documentation on exactly what each endpoint
+    # is for in the description text
     specify 'login' do
       expect(endpoints[:login]).to eq 'https://oidc.test.account.gov.uk/authorize'
     end
@@ -98,28 +113,4 @@
       expect(endpoints[:jwks]).to eq 'https://oidc.test.account.gov.uk/.well-known/jwks.json'
     end
   end
-
-  describe '#token_body' do
-    let(:token_body) { auth_service.send(:token_body) }
-
-    it 'returns a hash of correct token body' do
-      expect(token_body[:grant_type]).to eq('authorization_code')
-      expect(token_body[:code]).to eq(code)
-      expect(token_body[:redirect_uri]).to end_with('/users/auth/openid_connect/callback')
-      expect(token_body[:client_assertion_type]).to eq('urn:ietf:params:oauth:client-assertion-type:jwt-bearer')
-    end
-  end
-
-  describe '#jwt_payload' do
-    let(:jwt_payload) { auth_service.send(:jwt_payload) }
-
-    it 'returns a hash of correct jwt payload' do
-      expect(jwt_payload[:aud]).to eq('https://oidc.test.account.gov.uk/token')
-      expect(jwt_payload[:iss]).to eq('some_client_id')
-      expect(jwt_payload[:sub]).to eq('some_client_id')
-      expect(jwt_payload[:exp]).to be_between(Time.zone.now.to_i + 4 * 60, Time.zone.now.to_i + 6 * 60)
-      expect(jwt_payload[:jti]).to be_a(String)
-      expect(jwt_payload[:iat]).to be_a(Integer)
-    end
-  end
 end

From bda11baf0025a9f573346faeee5b0d3ab3d25191 Mon Sep 17 00:00:00 2001
From: Peter David Hamilton <pete@peterdavidhamilton.com>
Date: Fri, 17 Nov 2023 10:24:58 +0000
Subject: [PATCH 060/122] Users::OmniauthCallbacksController   with an existing
 gov-one user     redirects to /my-modules   with an existing non-gov-one user
     redirects to /my-modules     updates the account   with a new user    
 creates an account     redirects to complete registration

---
 .../omniauth_callbacks_controller_spec.rb     | 100 ++++++++++--------
 1 file changed, 54 insertions(+), 46 deletions(-)

diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index c28512af5..0cf73f971 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -1,58 +1,66 @@
 require 'rails_helper'
 
 RSpec.describe Users::OmniauthCallbacksController, type: :controller do
+  let(:auth_service) { instance_double(GovOneAuthService) }
+  let(:access_token) { 'mock_access_token' }
+  let(:id_token) { 'mock_id_token' }
+  let(:email) { 'test@example.com' }
+  let(:params) do
+    { 'code' => 'mock_code', 'state' => 'mock_state' }
+  end
+
   before do
     request.env['devise.mapping'] = Devise.mappings[:user]
+    session[:gov_one_auth_state] = 'mock_state'
+
+    allow(GovOneAuthService).to receive(:new).and_return(auth_service)
+    allow(auth_service).to receive(:tokens).and_return({ 'access_token' => access_token, 'id_token' => id_token })
+    allow(auth_service).to receive(:user_info).and_return({ 'email' => email })
+    allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
+    allow(auth_service).to receive(:decode_id_token).and_return([{ 'sub' => 'mock_sub' }])
+  end
+
+  context 'with a new user' do
+    before do
+      get :openid_connect, params: params
+    end
+
+    it 'creates an account' do
+      expect(User.find_by(email: email)).to be_truthy
+      expect(User.find_by(gov_one_id: 'mock_sub')).to be_truthy
+    end
+
+    it 'redirects to complete registration' do
+      expect(session[:id_token]).to eq id_token
+      expect(response).to redirect_to edit_registration_name_path
+    end
   end
 
-  describe '#openid_connect' do
-    let(:auth_service) { instance_double(GovOneAuthService) }
-    let(:access_token) { 'mock_access_token' }
-    let(:id_token) { 'mock_id_token' }
-    let(:email) { 'test@example.com' }
+  context 'with an existing non-gov-one user' do
+    before do
+      create :user, :registered, email: email
+      get :openid_connect, params: params
+    end
+
+    it 'updates the account' do
+      expect(User.find_by(gov_one_id: 'mock_sub')).to be_truthy
+    end
+
+    it 'redirects to /my-modules' do
+      expect(session[:id_token]).to eq id_token
+      expect(response).to redirect_to my_modules_path
+    end
+  end
 
+  context 'with an existing gov-one user' do
     before do
-      allow(GovOneAuthService).to receive(:new).and_return(auth_service)
-      allow(auth_service).to receive(:tokens).and_return({ 'access_token' => access_token, 'id_token' => id_token })
-      allow(auth_service).to receive(:user_info).and_return({ 'email' => email })
-      allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
-      allow(auth_service).to receive(:decode_id_token).and_return([{ 'sub' => 'mock_sub' }])
-      session[:gov_one_auth_state] = 'mock_state'
-    end
-
-    context 'when a User does not exist' do
-      it 'creates the user with the email and id_token' do
-        get :openid_connect, params: { 'code' => 'mock_code', 'state' => 'mock_state' }
-        expect(User.find_by(email: email)).to be_truthy
-        expect(User.find_by(gov_one_id: 'mock_sub')).to be_truthy
-        expect(response).to redirect_to(edit_registration_name_path)
-        expect(session[:id_token]).to eq(id_token)
-      end
-    end
-
-    context 'when a User email exists' do
-      before do
-        create :user, :registered, email: email
-      end
-
-      it 'updates the user id_token and signs them in' do
-        get :openid_connect, params: { 'code' => 'mock_code', 'state' => 'mock_state' }
-        expect(User.find_by(gov_one_id: 'mock_sub')).to be_truthy
-        expect(response).to redirect_to(my_modules_path)
-        expect(session[:id_token]).to eq(id_token)
-      end
-    end
-
-    context 'when a User id_token exists' do
-      before do
-        create :user, :registered, gov_one_id: 'mock_sub'
-      end
-
-      it 'signs the user in' do
-        get :openid_connect, params: { 'code' => 'mock_code', 'state' => 'mock_state' }
-        expect(response).to redirect_to(my_modules_path)
-        expect(session[:id_token]).to eq(id_token)
-      end
+      create :user, :registered, gov_one_id: 'mock_sub'
+      get :openid_connect, params: params
+    end
+
+    it 'redirects to /my-modules' do
+      expect(session[:id_token]).to eq id_token
+      expect(response).to redirect_to my_modules_path
     end
   end
 end

From 5c6a727fa6636ac36e4727e95ef4f3470452f9c1 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 17 Nov 2023 10:39:48 +0000
Subject: [PATCH 061/122] update helper and service specs

---
 config/application.rb                      |  5 -----
 spec/helpers/gov_one_helper_spec.rb        | 20 ++++++++++++++------
 spec/models/user_spec.rb                   | 21 ++++++++++++++-------
 spec/services/gov_one_auth_service_spec.rb | 12 +++++-------
 4 files changed, 33 insertions(+), 25 deletions(-)

diff --git a/config/application.rb b/config/application.rb
index 9979b9a54..42c3e1478 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -96,11 +96,6 @@ def debug?
       Types::Params::Bool[ENV.fetch('DEBUG', false)]
     end
 
-    # @return [Boolean]
-    def gov_one_login?
-      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', false)] || !live?
-    end
-
     # @return [Boolean]
     def maintenance?
       Types::Params::Bool[ENV.fetch('MAINTENANCE', false)]
diff --git a/spec/helpers/gov_one_helper_spec.rb b/spec/helpers/gov_one_helper_spec.rb
index 10b923853..07ce7b8c5 100644
--- a/spec/helpers/gov_one_helper_spec.rb
+++ b/spec/helpers/gov_one_helper_spec.rb
@@ -5,8 +5,13 @@
     subject(:login_uri) { helper.login_uri }
 
     it 'encodes the authorize endpoint params' do
-      expect(login_uri).to start_with 'https://oidc.test.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id=some_client_id&'
-      expect(login_uri).to end_with 'redirect_uri=http://recovery.app/users/auth/openid_connect/callback'
+      expect(login_uri.host).to eq 'oidc.test.account.gov.uk'
+      expect(login_uri.path).to eq '/authorize'
+      expect(login_uri.query).to include 'redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback'
+      expect(login_uri.query).to include 'client_id=some_client_id'
+      expect(login_uri.query).to include 'response_type=code'
+      expect(login_uri.query).to include 'scope=email+openid'
+      expect(login_uri.query).to include 'nonce='
     end
   end
 
@@ -14,8 +19,11 @@
     subject(:logout_uri) { helper.logout_uri }
 
     it 'encodes the logout endpoint params' do
-      expect(logout_uri).to start_with 'https://oidc.test.account.gov.uk/logout?id_token_hint&state='
-      expect(logout_uri).to end_with '&post_logout_redirect_uri=http://recovery.app/users/sign_out'
+      expect(logout_uri.host).to eq 'oidc.test.account.gov.uk'
+      expect(logout_uri.path).to eq '/logout'
+      expect(logout_uri.query).to include 'post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out'
+      expect(logout_uri.query).to include 'id_token_hint'
+      expect(logout_uri.query).to include 'state='
     end
   end
 
@@ -25,7 +33,7 @@
     it 'returns a button link to the gov one login uri' do
       expect(login_button).to include 'govuk-button'
       expect(login_button).to include 'Continue to GOV.UK One Login'
-      expect(login_button).to include 'href="https://oidc.test.account.gov.uk/authorize?response_type=code&amp;scope=email+openid&amp;client_id=some_client_id&'
+      expect(login_button).to include 'href="https://oidc.test.account.gov.uk/authorize?redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback&amp;client_id=some_client_id&amp;response_type=code&amp;scope=email+openid&amp;nonce='
     end
   end
 
@@ -35,7 +43,7 @@
     it 'returns a button link to the gov one logout uri' do
       expect(logout_button).to include 'govuk-button'
       expect(logout_button).to include 'Sign out of Gov One Login'
-      expect(logout_button).to include 'href="https://oidc.test.account.gov.uk/logout?id_token_hint&amp;state='
+      expect(logout_button).to include 'href="https://oidc.test.account.gov.uk/logout?post_logout_redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fsign_out&amp;id_token_hint&amp;state='
     end
   end
 end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 1c1d0afa7..c1b68455c 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -208,33 +208,40 @@
   end
 
   describe '.find_or_create_from_gov_one' do
+    subject(:find_or_create) { described_class.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id) }
+
     let(:email) { 'test@test.com' }
     let(:gov_one_id) { '123' }
+    let(:user) { create(:user) }
 
     context 'with an existing user having an email but no gov_one_id' do
-      let!(:user) { create(:user, email: email) }
+      before do
+        user.update_column(:email, email)
+        find_or_create
+      end
 
       it 'updates the user gov_one_id' do
-        described_class.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
         expect(user.reload.gov_one_id).to eq(gov_one_id)
       end
     end
 
     context 'with an existing user having a gov_one_id' do
-      let!(:user) { create(:user, gov_one_id: gov_one_id) }
+      before do
+        user.update_column(:gov_one_id, gov_one_id)
+        find_or_create
+      end
 
       it 'updates the user email' do
-        described_class.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
         expect(user.reload.email).to eq(email)
       end
     end
 
     context 'without an existing user' do
-      let(:email) { 'some_new_email@test.com' }
-      let(:gov_one_id) { '321' }
+      before do
+        find_or_create
+      end
 
       it 'creates a new user' do
-        described_class.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
         expect(described_class.count).to eq(1)
         expect(described_class.first.email).to eq(email)
         expect(described_class.first.gov_one_id).to eq(gov_one_id)
diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index d6adfbf89..66b65187b 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -91,25 +91,23 @@
   describe 'OIDC endpoints' do
     subject(:endpoints) { described_class::ENDPOINTS }
 
-    # opportunity to add more detail and use specs for documentation on exactly what each endpoint
-    # is for in the description text
-    specify 'login' do
+    specify 'login endpoint for starting gov one user session and redirecting back to service' do
       expect(endpoints[:login]).to eq 'https://oidc.test.account.gov.uk/authorize'
     end
 
-    specify 'logout' do
+    specify 'logout endpoint for ending gov one user session and redirecting back to service' do
       expect(endpoints[:logout]).to eq 'https://oidc.test.account.gov.uk/logout'
     end
 
-    specify 'token' do
+    specify 'token endpoint for retrieving user access token and id token' do
       expect(endpoints[:token]).to eq 'https://oidc.test.account.gov.uk/token'
     end
 
-    specify 'userinfo' do
+    specify 'userinfo endpoint for retrieving user email' do
       expect(endpoints[:userinfo]).to eq 'https://oidc.test.account.gov.uk/userinfo'
     end
 
-    specify 'jwks' do
+    specify 'jwks endpoint for retrieving public key for verifying user id token' do
       expect(endpoints[:jwks]).to eq 'https://oidc.test.account.gov.uk/.well-known/jwks.json'
     end
   end

From 6729f63f4c8de89726ff60a181353f10b6f407ed Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 17 Nov 2023 10:57:27 +0000
Subject: [PATCH 062/122] update locales

---
 app/helpers/gov_one_helper.rb    | 4 ++--
 app/views/gov_one/info.html.slim | 4 ++--
 config/locales/en.yml            | 9 +++++++--
 spec/lib/seed_snippets_spec.rb   | 2 +-
 4 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 5791a5c14..9369fbbef 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -28,12 +28,12 @@ def logout_uri
 
   # @return [String]
   def login_button
-    govuk_button_link_to t('gov-one-info.sign-in-button'), login_uri.to_s
+    govuk_button_link_to t('gov_one_info.sign_in_button'), login_uri.to_s
   end
 
   # @return [String]
   def logout_button
-    govuk_button_link_to t('gov-one-info.sign-out-button'), logout_uri.to_s
+    govuk_button_link_to t('gov_one_info.sign_out_button'), logout_uri.to_s
   end
 
 private
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index 74040bf19..4fd836421 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -1,11 +1,11 @@
 = render 'learning/cms_debug'
 
 - content_for :page_title do
-  = html_title t('gov_one.title')
+  = html_title t('gov_one_info.title')
 
 .govuk-grid-row
   .govuk-grid-column-one-half
-    = m('gov-one-info.hero-body')
+    = m('gov_one_info.body')
 
     - if current_user
       = logout_button
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 6a261b5d7..78cbdf319 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -508,8 +508,6 @@ en:
   home:
     title: Home page
     hero: |
-      # %{service_name}
-
       This free, online training provides an overview of child development and gives practical advice for supporting the development of children in your early years setting.
     thumb: |
       ![A practitioner smiling as they do an activity with children in an early years setting](//images.ctfassets.net/dvmeh832nmjc/2VRBZCHJYLeKx0a2CIcJzE/687fb0d28c71104820cd42f131d7dfe2/_assets_thumb-1161068272-320x240.jpg?fit=scale&w=380)
@@ -574,6 +572,13 @@ en:
 
       %{criteria}
 
+  # /gov-one/info
+  gov_one_info:
+    title: Gov One Info
+    body: This is some information about Gov One Login.
+    sign_in_button: Continue to GOV.UK One Login
+    sign_out_button: Sign out of Gov One Login
+
   # /settings/cookie-policy
   cookie_policy:
     title: Cookie policy
diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb
index 75a4ac39a..5041dca24 100644
--- a/spec/lib/seed_snippets_spec.rb
+++ b/spec/lib/seed_snippets_spec.rb
@@ -5,7 +5,7 @@
   subject(:locales) { described_class.new.call }
 
   it 'converts all translations' do
-    expect(locales.count).to be 187
+    expect(locales.count).to be 191
   end
 
   it 'dot separated key -> Page::Resource#name' do

From 703d6424379d794c680bed553e60871d55126386 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 17 Nov 2023 11:28:47 +0000
Subject: [PATCH 063/122] update locales

---
 config/locales/en.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/config/locales/en.yml b/config/locales/en.yml
index 78cbdf319..ec06dafcc 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -508,6 +508,8 @@ en:
   home:
     title: Home page
     hero: |
+      # %{service_name}
+      
       This free, online training provides an overview of child development and gives practical advice for supporting the development of children in your early years setting.
     thumb: |
       ![A practitioner smiling as they do an activity with children in an early years setting](//images.ctfassets.net/dvmeh832nmjc/2VRBZCHJYLeKx0a2CIcJzE/687fb0d28c71104820cd42f131d7dfe2/_assets_thumb-1161068272-320x240.jpg?fit=scale&w=380)

From e17b30e8566cfc410aa62ca210d6ae6a1248af15 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 17 Nov 2023 11:29:11 +0000
Subject: [PATCH 064/122] update locales

---
 config/locales/en.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/config/locales/en.yml b/config/locales/en.yml
index 65fd41cef..cce7aa516 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -508,6 +508,8 @@ en:
   home:
     title: Home page
     hero: |
+      # %{service_name}
+      
       This free, online training provides an overview of child development and gives practical advice for supporting the development of children in your early years setting.
     thumb: |
       ![A practitioner smiling as they do an activity with children in an early years setting](//images.ctfassets.net/dvmeh832nmjc/2VRBZCHJYLeKx0a2CIcJzE/687fb0d28c71104820cd42f131d7dfe2/_assets_thumb-1161068272-320x240.jpg?fit=scale&w=380)

From 391c35b1b0249fba495470b65ff39742e10bde35 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 17 Nov 2023 11:59:38 +0000
Subject: [PATCH 065/122] convert sso auth uri to string in navbar

---
 app/helpers/application_helper.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 623e5c338..1d4c66edb 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -11,11 +11,11 @@ def navigation
       header.with_navigation_item(text: 'Home', href: root_path, classes: %w[dfe-header__navigation-item])
       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: logout_uri, options: { id: 'sign-out-desktop', data: { turbo_method: :get }, inverse: true })
+        header.with_action_link(text: 'Sign out', href: logout_uri.to_s, 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: %w[dfe-header__navigation-item dfe-header-f-mob])
-        header.with_navigation_item(text: 'Sign out', href: logout_uri, options: { data: { turbo_method: :get } }, classes: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' })
+        header.with_navigation_item(text: 'Sign out', href: logout_uri.to_s, options: { data: { turbo_method: :get } }, classes: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' })
       else
         header.with_action_link(text: 'Sign in', href: gov_one_info_path, options: { inverse: true })
         header.with_navigation_item(text: 'Sign in', href: gov_one_info_path, classes: %w[dfe-header__navigation-item dfe-header-f-mob])

From 2b4f1a5e6abc241f520d18255d829352c3766074 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 17 Nov 2023 12:08:51 +0000
Subject: [PATCH 066/122] update system spec

---
 spec/system/front_page_spec.rb | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/spec/system/front_page_spec.rb b/spec/system/front_page_spec.rb
index b72275cc3..9216e8620 100644
--- a/spec/system/front_page_spec.rb
+++ b/spec/system/front_page_spec.rb
@@ -7,16 +7,16 @@
     it 'logged in content' do
       visit '/'
       expect(page).to have_text 'Learn more'
-      expect(page).not_to have_text 'Learn more and enrol'
-      expect(page).not_to have_text 'Sign in to continue learning'
+      expect(page).not_to have_text 'Learn more about this training'
+      expect(page).not_to have_text 'Start your training now'
     end
   end
 
   context 'with an unauthenticated visitor' do
     it 'log in content' do
       visit '/'
-      expect(page).to have_text('Learn more and enrol')
-        .and have_text('Sign in to continue learning')
+      expect(page).to have_text('Learn more about this training')
+        .and have_text('Start your training now')
     end
   end
 end

From 208c212a391d97ba2acb367211145242f2b87b24 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 17 Nov 2023 12:36:32 +0000
Subject: [PATCH 067/122] update gov one info system spec

---
 spec/system/gov_one_info_spec.rb | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/spec/system/gov_one_info_spec.rb b/spec/system/gov_one_info_spec.rb
index 9a7073f93..bb252a4b7 100644
--- a/spec/system/gov_one_info_spec.rb
+++ b/spec/system/gov_one_info_spec.rb
@@ -1,7 +1,7 @@
 require 'rails_helper'
 
 RSpec.describe 'Gov One Info' do
-  context 'when the user is not logged in' do
+  context 'with an unauthenticated visitor' do
     before do
       visit '/gov-one/info'
     end
@@ -15,15 +15,14 @@
 
     it 'has the correct login link' do
       link = find_link('Continue to GOV.UK One Login')
-      expect(link[:href]).to start_with('https://oidc.test.account.gov.uk/authorize?response_type=code&scope=email+openid&client_id=some_client_id')
-      expect(link[:href]).to end_with('&redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback')
+      expect(link[:href]).to start_with('https://oidc.test.account.gov.uk/authorize?redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback&client_id=some_client_id&response_type=code&scope=email+openid&nonce=')
     end
   end
 
-  context 'when the user is logged in' do
+  context 'with an authenticated user' do
     include_context 'with user'
 
-    it 'they are redirected to the my modules page' do
+    it 'redirects to the my modules page' do
       visit '/gov-one/info'
       expect(page).to have_current_path('/my-modules')
     end

From 3b7ba6a7d3cbae5a3158c4a5ed1553e4f7086c90 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 20 Nov 2023 10:07:11 +0000
Subject: [PATCH 068/122] styling tweaks

---
 app/views/gov_one/info.html.slim | 21 +++++++++++----------
 app/views/home/index.html.slim   |  9 +++++----
 2 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index a6e57f1d4..8e43b575e 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -2,7 +2,7 @@
   = html_title t('home.title')
 
 - content_for :hero do
-  .govuk-grid-row class='govuk-!-padding-top-9 govuk-!-padding-bottom-0'
+  .govuk-grid-row class='govuk-!-padding-top-9'
     .govuk-grid-column-three-quarters
       h1.dfe-heading-xl class='govuk-!-margin-bottom-4'
         = t('gov_one_info.hero_header')
@@ -10,18 +10,19 @@
 
 
 .govuk-grid-row
-  .govuk-grid-column-three-quarters
+  .govuk-grid-column-full
     . class='govuk-!-margin-bottom-5'
         = m('gov_one_info.body')
         hr
 
     = login_button
 
-. class='govuk-!-margin-top-6 govuk-!-margin-bottom-9'
-  details.govuk-details data-module='govuk-details'
-      summary.govuk-details__summary
-        span.govuk-details__summary-text
-          = t('gov_one_info.details_summary')
-      .govuk-details__text
-        = t('gov_one_info.details_text')
-  
\ No newline at end of file
+.govuk-grid-row
+  .govuk-grid-column-full class='govuk-!-margin-top-4 govuk-!-margin-bottom-9'
+    details.govuk-details data-module='govuk-details'
+        summary.govuk-details__summary
+          span.govuk-details__summary-text
+            = t('gov_one_info.details_summary')
+        .govuk-details__text
+          = t('gov_one_info.details_text')
+    
\ No newline at end of file
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index 8e805cf00..3cc7f73e2 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -20,7 +20,8 @@
       = m('home.prompt', headings_start_with: 'xl')
 
 - unless current_user
-    .govuk-button-group
-    = govuk_button_link_to gov_one_info_path, class: "govuk-button--start" do
-      | #{t('home.gov_one_button')}
-      = render 'chevron'
+  .govuk-grid-row class="govuk-!-margin-top-9"
+    .govuk-grid-column-full
+      = govuk_button_link_to gov_one_info_path, class: "govuk-button--start" do
+        | #{t('home.gov_one_button')}
+        = render 'chevron'

From b556ac3493e853fd89a7adb0e1ba83a10e5608ca Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 20 Nov 2023 10:39:53 +0000
Subject: [PATCH 069/122] change div width

---
 app/views/gov_one/info.html.slim | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
index 8e43b575e..f50a52ce6 100644
--- a/app/views/gov_one/info.html.slim
+++ b/app/views/gov_one/info.html.slim
@@ -18,7 +18,7 @@
     = login_button
 
 .govuk-grid-row
-  .govuk-grid-column-full class='govuk-!-margin-top-4 govuk-!-margin-bottom-9'
+  .govuk-grid-column-three-quarters class='govuk-!-margin-top-4 govuk-!-margin-bottom-9'
     details.govuk-details data-module='govuk-details'
         summary.govuk-details__summary
           span.govuk-details__summary-text

From 2d46c4f9466896c6d328267ddfe8a5bfd0949dff Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 20 Nov 2023 15:50:22 +0000
Subject: [PATCH 070/122] update for onboarding tech checklist

---
 .../users/omniauth_callbacks_controller.rb    | 11 +++++----
 app/helpers/gov_one_helper.rb                 |  3 ++-
 app/services/gov_one_auth_service.rb          | 24 +++++++++++++++----
 .../omniauth_callbacks_controller_spec.rb     |  1 +
 4 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 817d3281d..e4e3ef3b4 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -9,18 +9,19 @@ def openid_connect
 
     auth_service = GovOneAuthService.new(code: params['code'])
     tokens_response = auth_service.tokens
-
     return error_redirect unless valid_tokens?(tokens_response)
 
-    id_token = tokens_response['id_token']
-    session[:id_token] = id_token
-    gov_one_id = auth_service.decode_id_token(id_token)[0]['sub']
+    id_token = auth_service.decode_id_token(tokens_response['id_token'])[0]
+    session[:id_token] = tokens_response['id_token']
+    gov_one_id = id_token['sub']
+    return error_redirect unless auth_service.valid_id_token?(id_token, session[:gov_one_auth_nonce])
 
     user_info_response = auth_service.user_info(tokens_response['access_token'])
     email = user_info_response['email']
     return error_redirect unless valid_user_info?(user_info_response)
 
     gov_user = User.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
+    session.delete(:nonce)
     sign_in_and_redirect gov_user if gov_user
   end
 
@@ -40,7 +41,7 @@ def valid_tokens?(tokens_response)
   # @param user_info_response [Hash]
   # @return [Boolean]
   def valid_user_info?(user_info_response)
-    user_info_response.present? && user_info_response['email'].present?
+    user_info_response.present? && user_info_response['email'].present? && user_info_response['sub'] == session[:id_token]['sub']
   end
 
   # @return [nil]
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 9369fbbef..53c5b3048 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -6,11 +6,12 @@ def login_uri
       client_id: Rails.application.config.gov_one_client_id,
       response_type: 'code',
       scope: 'email openid',
-      nonce: SecureRandom.uuid,
+      nonce: SecureRandom.alphanumeric(25),
       state: SecureRandom.uuid,
     }
 
     session[:gov_one_auth_state] = params[:state]
+    session[:gov_one_auth_nonce] = params[:nonce]
 
     gov_one_uri(:login, params)
   end
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 786ec439c..6018bbb52 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -38,6 +38,15 @@ def tokens
     {}
   end
 
+  # @param token [String]
+  # @param nonce [String]
+  # @return [Boolean]
+  def valid_id_token?(token, nonce)
+    token['iss'] == "#{Rails.application.config.gov_one_base_uri}/" &&
+      token['aud'] == Rails.application.config.gov_one_client_id &&
+      token['nonce'] == nonce
+  end
+
   # GET /userinfo
   # @param access_token [String]
   # @return [Hash]
@@ -66,7 +75,7 @@ def decode_id_token(token)
     key_params = jwks['keys'].find { |key| key['kid'] == kid }
     jwk = JWT::JWK.new(key_params)
 
-    JWT.decode(token, jwk.public_key, true, algorithm: 'ES256')
+    JWT.decode(token, jwk.public_key, true, { verify_iat: true, algorithm: 'ES256' })
   end
 
   # @param address [String]
@@ -80,11 +89,18 @@ def build_http(address)
 
 private
 
+  # @return [Hash]
+  def fetch_and_cache_jwks
+    Rails.cache.fetch('jwks', expires_in: 24.hours) do
+      uri, http = build_http(ENDPOINTS[:jwks])
+      response = http.request(Net::HTTP::Get.new(uri.path))
+      JSON.parse(response.body)
+    end
+  end
+
   # @return [Hash]
   def jwks
-    uri, http = build_http(ENDPOINTS[:jwks])
-    response = http.request(Net::HTTP::Get.new(uri.path))
-    JSON.parse(response.body)
+    @jwks ||= fetch_and_cache_jwks
   end
 
   # @return [String]
diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index 0cf73f971..173221a15 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -18,6 +18,7 @@
     allow(auth_service).to receive(:user_info).and_return({ 'email' => email })
     allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
     allow(auth_service).to receive(:decode_id_token).and_return([{ 'sub' => 'mock_sub' }])
+    allow(auth_service).to receive(:valid_id_token?).and_return(true)
   end
 
   context 'with a new user' do

From 6feb32675031de7bf7fdee654522cc61d19ec237 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 21 Nov 2023 09:41:28 +0000
Subject: [PATCH 071/122] check for session state and nonce on one-login
 redirect

---
 .../users/omniauth_callbacks_controller.rb      | 17 +++++++++++++++--
 app/services/gov_one_auth_service.rb            |  1 +
 2 files changed, 16 insertions(+), 2 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index e4e3ef3b4..4302dfdb7 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -5,7 +5,7 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
   # This method is called by Devise after successful Gov One Login authentication
   # @return [nil]
   def openid_connect
-    session.delete(:gov_one_auth_state)
+    return unless session_params? && valid_params?
 
     auth_service = GovOneAuthService.new(code: params['code'])
     tokens_response = auth_service.tokens
@@ -21,7 +21,8 @@ def openid_connect
     return error_redirect unless valid_user_info?(user_info_response)
 
     gov_user = User.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
-    session.delete(:nonce)
+    
+    delete_session_params
     sign_in_and_redirect gov_user if gov_user
   end
 
@@ -32,6 +33,12 @@ def valid_params?
     params['code'].present? && params['state'].present? && params['state'] == session[:gov_one_auth_state]
   end
 
+  # check state and nonce are saved in session
+  # @return [Boolean]
+  def session_params?
+    session[:gov_one_auth_state].present? && session[:gov_one_auth_nonce].present?
+  end
+
   # @param tokens_response [Hash]
   # @return [Boolean]
   def valid_tokens?(tokens_response)
@@ -50,6 +57,12 @@ def error_redirect
     redirect_to root_path
   end
 
+  # @return [nil]
+  def delete_session_params
+    session.delete(:gov_one_auth_state)
+    session.delete(:gov_one_auth_nonce)
+  end
+
   # @return [String]
   def after_sign_in_path_for(resource)
     if resource.registration_complete?
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 6018bbb52..1b06383a2 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -89,6 +89,7 @@ def build_http(address)
 
 private
 
+  # GET /.well-known/jwks.json
   # @return [Hash]
   def fetch_and_cache_jwks
     Rails.cache.fetch('jwks', expires_in: 24.hours) do

From 771550c1cfd0c40e3abaa7ca4149da1fadb58400 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 21 Nov 2023 10:03:51 +0000
Subject: [PATCH 072/122] mock session nonce in controller spec

---
 spec/controllers/users/omniauth_callbacks_controller_spec.rb | 1 +
 1 file changed, 1 insertion(+)

diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index 173221a15..339497fa4 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -12,6 +12,7 @@
   before do
     request.env['devise.mapping'] = Devise.mappings[:user]
     session[:gov_one_auth_state] = 'mock_state'
+    session[:gov_one_auth_nonce] = 'mock_nonce'
 
     allow(GovOneAuthService).to receive(:new).and_return(auth_service)
     allow(auth_service).to receive(:tokens).and_return({ 'access_token' => access_token, 'id_token' => id_token })

From a484a8364b98520aa2451a7f4cf356969163e9a3 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 21 Nov 2023 10:32:49 +0000
Subject: [PATCH 073/122] rubocop

---
 app/controllers/users/omniauth_callbacks_controller.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 4302dfdb7..2e6f0d920 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -21,7 +21,7 @@ def openid_connect
     return error_redirect unless valid_user_info?(user_info_response)
 
     gov_user = User.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id)
-    
+
     delete_session_params
     sign_in_and_redirect gov_user if gov_user
   end

From f781f1914858ac7d0c98d8b2cda430e4e54e2c31 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 21 Nov 2023 15:34:13 +0000
Subject: [PATCH 074/122] update account page for one login move

---
 app/assets/stylesheets/application.scss |  4 ++++
 app/views/user/show.html.slim           | 10 ++--------
 config/locales/en.yml                   |  1 +
 spec/lib/seed_snippets_spec.rb          |  2 +-
 4 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index a87d09295..d1704f7c4 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -194,3 +194,7 @@ ul>li>ul>li {
 .white-space-pre-wrap {
   white-space: pre-wrap;
 }
+
+.text-secondary {
+  color: $govuk-secondary-text-colour !important;
+}
diff --git a/app/views/user/show.html.slim b/app/views/user/show.html.slim
index 1cf9f3e03..b0ae1b03e 100644
--- a/app/views/user/show.html.slim
+++ b/app/views/user/show.html.slim
@@ -14,14 +14,8 @@
         - row.with_key { 'Name' }
         - row.with_value(text: current_user.name, classes: %w[data-hj-suppress])
         - row.with_action(text: 'Change name', href: edit_registration_name_path, html_attributes: { id: :edit_name_registration })
-      - your_details.with_row do |row|
-        - row.with_key { 'Email' }
-        - row.with_value(text:current_user.email, classes: %w[data-hj-suppress])
-        - row.with_action(text: 'Change email', href: edit_email_user_path, html_attributes: { id: :edit_email_user })
-      - your_details.with_row do |row|
-        - row.with_key { 'Password' }
-        - row.with_value { t('my_account.password_changed', date: current_user.password_last_changed) }
-        - row.with_action(text: 'Change password', href: edit_password_user_path, html_attributes: { id: :edit_password_user })
+    p.text-secondary
+      = t('my_account.name_information')
 
     = govuk_summary_list do |other_details|
       - other_details.with_row do |row|
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ec06dafcc..a3ad60362 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -296,6 +296,7 @@ en:
   # /my-account
   my_account:
     title: My account
+    name_information: This is the name that will appear on you end of module certificate. You can use this setting to change how your name appears. Changing your name on this account will not affect your Gov.UK One Login account.
     setting_details: |
       ## Your setting details
     email_preferences: |
diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb
index 5041dca24..bac2ac9a8 100644
--- a/spec/lib/seed_snippets_spec.rb
+++ b/spec/lib/seed_snippets_spec.rb
@@ -5,7 +5,7 @@
   subject(:locales) { described_class.new.call }
 
   it 'converts all translations' do
-    expect(locales.count).to be 191
+    expect(locales.count).to be 192
   end
 
   it 'dot separated key -> Page::Resource#name' do

From 26e7c5fe243ae918b7de0e240f349db299f9c3fe Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 21 Nov 2023 15:38:09 +0000
Subject: [PATCH 075/122] css update

---
 app/assets/stylesheets/application.scss | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index d1704f7c4..041d1e8a9 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -196,5 +196,5 @@ ul>li>ul>li {
 }
 
 .text-secondary {
-  color: $govuk-secondary-text-colour !important;
+  color: $govuk-secondary-text-colour;
 }

From 7f29b86088cfe740ba28ab04e0ff25070bf26268 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 22 Nov 2023 10:03:22 +0000
Subject: [PATCH 076/122] remove change password spec

---
 .../registered_user/changing_password_spec.rb | 55 -------------------
 1 file changed, 55 deletions(-)
 delete mode 100644 spec/system/registered_user/changing_password_spec.rb

diff --git a/spec/system/registered_user/changing_password_spec.rb b/spec/system/registered_user/changing_password_spec.rb
deleted file mode 100644
index 47679ba47..000000000
--- a/spec/system/registered_user/changing_password_spec.rb
+++ /dev/null
@@ -1,55 +0,0 @@
-require 'rails_helper'
-
-RSpec.describe 'Registered user changing password', type: :system do
-  subject(:user) { create :user, :registered, created_at: 1.month.ago }
-
-  let(:password) { 'Str0ngPa$$w0rd' }
-
-  include_context 'with user'
-
-  before do
-    visit '/my-account/edit-password'
-    fill_in 'Enter your current password', with: 'Str0ngPa$$w0rd'
-    fill_in 'Create a new password', with: password
-    fill_in 'Confirm password', with: password
-  end
-
-  context 'when cancelled' do
-    it 'returns to account page' do
-      click_link 'Cancel'
-      expect(page).to have_current_path '/my-account'
-      expect(page).not_to have_text 'Your new password has been saved.'
-    end
-  end
-
-  context 'when successful' do
-    let(:password) { '12!@NewPassword' }
-    let(:today) { Time.zone.today.to_formatted_s(:rfc822) } # 18 May 2022
-
-    it 'updates password' do
-      click_button 'Save'
-      expect(page).to have_current_path '/my-account'
-      expect(page).to have_text('Manage your account') # page heading
-        .and have_text('Your new password has been saved.') # flash message
-        .and have_text("Password last changed on #{today}") # event
-    end
-  end
-
-  context 'when too short' do
-    let(:password) { 'short' }
-
-    it 'renders an error message' do
-      click_button 'Save'
-      expect(page).to have_text 'Password must be at least 10 characters.'
-    end
-  end
-
-  context 'when blank' do
-    let(:password) { '' }
-
-    it 'renders an error message' do
-      click_button 'Save'
-      expect(page).to have_text 'Enter a password.'
-    end
-  end
-end

From b81ec3832a89e89cc5d5dba95c64773d598b4af7 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 22 Nov 2023 11:58:39 +0000
Subject: [PATCH 077/122] update bot spec

---
 spec/requests/bot_spec.rb | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/spec/requests/bot_spec.rb b/spec/requests/bot_spec.rb
index 7f46679c8..3a7109570 100644
--- a/spec/requests/bot_spec.rb
+++ b/spec/requests/bot_spec.rb
@@ -2,9 +2,10 @@
 
 RSpec.describe 'Automated bot', type: :request do
   let(:bot_email) { 'bot_token@example.com' }
+  let(:bot_name) { 'Bot' }
 
   before do
-    create :user, :registered, email: bot_email
+    create :user, :registered, email: bot_email, first_name: bot_name
   end
 
   context 'with header' do
@@ -14,7 +15,7 @@
 
     it 'is not redirected and can access secure pages' do
       expect(response).not_to redirect_to new_user_session_path
-      expect(response.body).to include bot_email
+      expect(response.body).to include bot_name
     end
   end
 

From 4a353bc8a613d1de1fb9e6b9ad26f6be4bf7a54d Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 23 Nov 2023 13:44:46 +0000
Subject: [PATCH 078/122] minor styling updates

---
 app/views/user/show.html.slim | 2 +-
 config/locales/en.yml         | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/views/user/show.html.slim b/app/views/user/show.html.slim
index b0ae1b03e..39f273dcc 100644
--- a/app/views/user/show.html.slim
+++ b/app/views/user/show.html.slim
@@ -7,7 +7,7 @@
   .govuk-grid-column-full
     h1.govuk-heading-l Manage your account
     h2.govuk-heading-m Your details
-    hr.govuk-section-break.govuk-section-break--l.govuk-section-break--visible
+    hr.govuk-section-break.govuk-section-break--l.govuk-section-break--visible class="govuk-!-margin-bottom-0"
 
     = govuk_summary_list do |your_details|
       - your_details.with_row do |row|
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a3ad60362..c4bdb22a8 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -296,7 +296,7 @@ en:
   # /my-account
   my_account:
     title: My account
-    name_information: This is the name that will appear on you end of module certificate. You can use this setting to change how your name appears. Changing your name on this account will not affect your Gov.UK One Login account.
+    name_information: This is the name that will appear on you end of module certificate. You can use this setting to change how your name appears. Changing your name on this account will not affect your GOV.UK One Login account.
     setting_details: |
       ## Your setting details
     email_preferences: |

From 16bb1583d4df1660861c0b6bf416d8333fc3cb7a Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 27 Nov 2023 09:43:45 +0000
Subject: [PATCH 079/122] add terms and conditions page

---
 .../terms_and_conditions_controller.rb        | 35 +++++++++++++++++++
 .../registration/terms_and_conditions_form.rb | 14 ++++++++
 .../terms_and_conditions/edit.html.slim       | 22 ++++++++++++
 3 files changed, 71 insertions(+)
 create mode 100644 app/controllers/registration/terms_and_conditions_controller.rb
 create mode 100644 app/forms/registration/terms_and_conditions_form.rb
 create mode 100644 app/views/registration/terms_and_conditions/edit.html.slim

diff --git a/app/controllers/registration/terms_and_conditions_controller.rb b/app/controllers/registration/terms_and_conditions_controller.rb
new file mode 100644
index 000000000..79166cabf
--- /dev/null
+++ b/app/controllers/registration/terms_and_conditions_controller.rb
@@ -0,0 +1,35 @@
+module Registration
+  class TermsAndConditionsController < BaseController
+    def edit; end
+
+    def update
+      form.terms_and_conditions_agreed_at = user_params[:terms_and_conditions_agreed_at]
+
+      if form.save
+        if current_user.registration_complete?
+          redirect_to user_path, notice: t(:details_updated)
+        else
+          redirect_to edit_registration_name_path
+        end
+      else
+        render :edit, status: :unprocessable_entity
+      end
+    end
+
+  private
+
+    # @return [Hash]
+    def user_params
+      params.require(:user).permit(:terms_and_conditions_agreed_at)
+    end
+
+    # @return [Registration::NameForm]
+    def form
+      @form ||=
+        TermsAndConditionsForm.new(
+          user: current_user,
+          terms_and_conditions_agreed_at: current_user.terms_and_conditions_agreed_at,
+        )
+    end
+  end
+end
diff --git a/app/forms/registration/terms_and_conditions_form.rb b/app/forms/registration/terms_and_conditions_form.rb
new file mode 100644
index 000000000..e0e0c3a7a
--- /dev/null
+++ b/app/forms/registration/terms_and_conditions_form.rb
@@ -0,0 +1,14 @@
+module Registration
+  class TermsAndConditionsForm < BaseForm
+    attr_accessor :terms_and_conditions_agreed_at
+
+    validates :terms_and_conditions_agreed_at, presence: true
+
+    # @return [Boolean]
+    def save
+      return false unless valid?
+
+      user.update!(terms_and_conditions_agreed_at: terms_and_conditions_agreed_at)
+    end
+  end
+end
diff --git a/app/views/registration/terms_and_conditions/edit.html.slim b/app/views/registration/terms_and_conditions/edit.html.slim
new file mode 100644
index 000000000..4d0e4f1a3
--- /dev/null
+++ b/app/views/registration/terms_and_conditions/edit.html.slim
@@ -0,0 +1,22 @@
+= render 'user/debug'
+
+- content_for :page_title do
+  = html_title 'Terms and Conditions'
+
+.govuk-grid-row
+  .govuk-grid-column-two-thirds-from-desktop
+    = form_for form, url: registration_terms_and_conditions_path, method: :patch do |f|
+      = f.govuk_error_summary
+
+      h1.govuk-heading-l = t('register_terms_and_conditions.heading')
+
+      h3 = t('register_terms_and_conditions.subheading')
+
+      = f.govuk_check_boxes_fieldset :terms_and_conditions_agreed_at,
+        legend: { class: 'govuk-visually-hidden', text: 'Terms and conditions'}, classes: 'light-grey-box' do
+        = m('register_terms_and_conditions.legend')
+        = f.terms_and_conditions_check_box
+
+
+      .govuk-button-group
+        = f.govuk_submit t('links.continue')

From f2ead426c0950189f7323b10292c344c368927c2 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 27 Nov 2023 11:18:05 +0000
Subject: [PATCH 080/122] update localisations and specs

---
 app/controllers/application_controller.rb           |  2 +-
 .../users/omniauth_callbacks_controller.rb          |  4 ++--
 app/controllers/users/sessions_controller.rb        |  2 +-
 config/locales/en.yml                               |  7 +++++++
 config/routes.rb                                    |  1 +
 .../users/omniauth_callbacks_controller_spec.rb     | 13 +++++++------
 spec/lib/seed_snippets_spec.rb                      |  2 +-
 .../confirmed_user/completing_registration_spec.rb  | 12 ++++++++++++
 8 files changed, 32 insertions(+), 11 deletions(-)

diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index b7ca4f15e..7b9ba514a 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -20,7 +20,7 @@ def authenticate_registered_user!
     authenticate_user! unless user_signed_in?
     return true if current_user.registration_complete?
 
-    redirect_to edit_registration_name_path, notice: 'Please complete registration'
+    redirect_to edit_registration_terms_and_conditions_path, notice: 'Please complete registration'
   end
 
   def configure_permitted_parameters
diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 2e6f0d920..2a3d189aa 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -12,7 +12,7 @@ def openid_connect
     return error_redirect unless valid_tokens?(tokens_response)
 
     id_token = auth_service.decode_id_token(tokens_response['id_token'])[0]
-    session[:id_token] = tokens_response['id_token']
+    session[:id_token] = id_token
     gov_one_id = id_token['sub']
     return error_redirect unless auth_service.valid_id_token?(id_token, session[:gov_one_auth_nonce])
 
@@ -78,7 +78,7 @@ def after_sign_in_path_for(resource)
     elsif resource.private_beta_registration_complete?
       static_path('new-registration')
     else
-      edit_registration_name_path
+      edit_registration_terms_and_conditions_path
     end
   end
 end
diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb
index d70bff7ee..a4db408cb 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -17,7 +17,7 @@ def after_sign_in_path_for(resource)
     elsif resource.private_beta_registration_complete?
       static_path('new-registration')
     else
-      edit_registration_name_path
+      edit_registration_terms_and_conditions_path
     end
   end
 end
diff --git a/config/locales/en.yml b/config/locales/en.yml
index ec06dafcc..f04bc9b73 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -449,6 +449,13 @@ en:
   complete_registration: Thank you for creating an Early years child development training account. You can now start your first module.
   update_registration: Thank you for updating your Early years child development training account. You can now continue.
 
+  # /registration/terms-and-conditions/edit
+  register_terms_and_conditions:
+    heading: Set up your training account
+    subheading: Agree to our terms and conditions
+    legend: |
+      To use this service, you must accept the [terms and conditions](/terms-and-conditions) and [privacy policy](/privacy-policy).
+
   # /registration/name/edit
   register_name:
     heading: About you
diff --git a/config/routes.rb b/config/routes.rb
index 9de3c4b5c..ec9655633 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -37,6 +37,7 @@
   end
 
   namespace :registration do
+    resource :terms_and_conditions, only: %i[edit update], path: 'terms-and-conditions'
     resource :name, only: %i[edit update]
     resource :setting_type, only: %i[edit update], path: 'setting-type'
     resource :setting_type_other, only: %i[edit update], path: 'setting-type-other'
diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index 339497fa4..132d453b9 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -4,6 +4,7 @@
   let(:auth_service) { instance_double(GovOneAuthService) }
   let(:access_token) { 'mock_access_token' }
   let(:id_token) { 'mock_id_token' }
+  let(:decoded_id_token) { { 'sub' => 'mock_sub' } }
   let(:email) { 'test@example.com' }
   let(:params) do
     { 'code' => 'mock_code', 'state' => 'mock_state' }
@@ -16,9 +17,9 @@
 
     allow(GovOneAuthService).to receive(:new).and_return(auth_service)
     allow(auth_service).to receive(:tokens).and_return({ 'access_token' => access_token, 'id_token' => id_token })
-    allow(auth_service).to receive(:user_info).and_return({ 'email' => email })
+    allow(auth_service).to receive(:user_info).and_return({ 'email' => email, 'sub' => 'mock_sub' })
     allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
-    allow(auth_service).to receive(:decode_id_token).and_return([{ 'sub' => 'mock_sub' }])
+    allow(auth_service).to receive(:decode_id_token).and_return([decoded_id_token])
     allow(auth_service).to receive(:valid_id_token?).and_return(true)
   end
 
@@ -33,8 +34,8 @@
     end
 
     it 'redirects to complete registration' do
-      expect(session[:id_token]).to eq id_token
-      expect(response).to redirect_to edit_registration_name_path
+      expect(session[:id_token]).to eq decoded_id_token
+      expect(response).to redirect_to edit_registration_terms_and_conditions_path
     end
   end
 
@@ -49,7 +50,7 @@
     end
 
     it 'redirects to /my-modules' do
-      expect(session[:id_token]).to eq id_token
+      expect(session[:id_token]).to eq decoded_id_token
       expect(response).to redirect_to my_modules_path
     end
   end
@@ -61,7 +62,7 @@
     end
 
     it 'redirects to /my-modules' do
-      expect(session[:id_token]).to eq id_token
+      expect(session[:id_token]).to eq decoded_id_token
       expect(response).to redirect_to my_modules_path
     end
   end
diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb
index 5041dca24..fbcf787bf 100644
--- a/spec/lib/seed_snippets_spec.rb
+++ b/spec/lib/seed_snippets_spec.rb
@@ -5,7 +5,7 @@
   subject(:locales) { described_class.new.call }
 
   it 'converts all translations' do
-    expect(locales.count).to be 191
+    expect(locales.count).to be 194
   end
 
   it 'dot separated key -> Page::Resource#name' do
diff --git a/spec/system/confirmed_user/completing_registration_spec.rb b/spec/system/confirmed_user/completing_registration_spec.rb
index 420391fb0..bcd43c72b 100644
--- a/spec/system/confirmed_user/completing_registration_spec.rb
+++ b/spec/system/confirmed_user/completing_registration_spec.rb
@@ -6,6 +6,18 @@
   let(:user) { create :user, :confirmed }
 
   it 'requires name and a setting type and email preferences and a complete' do
+    expect(page).to have_text('Terms and conditions')
+
+    click_button 'Continue'
+
+    expect(page).to have_text('There is a problem')
+      .and have_text('You must accept the terms and conditions and privacy policy to create an account.')
+
+    expect(page).to have_text('Agree to our terms and conditions')
+
+    check 'I confirm that I accept the terms and conditions and privacy policy.'
+    click_button 'Continue'
+
     expect(page).to have_text('About you')
     click_button 'Continue'
 

From 85baa09867c5622211ab676233cc60150a685552 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 27 Nov 2023 11:22:02 +0000
Subject: [PATCH 081/122] update callbacks controller

---
 .../users/omniauth_callbacks_controller.rb            |  2 +-
 .../users/omniauth_callbacks_controller_spec.rb       | 11 ++++++-----
 2 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 2e6f0d920..66a611c2a 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -12,7 +12,7 @@ def openid_connect
     return error_redirect unless valid_tokens?(tokens_response)
 
     id_token = auth_service.decode_id_token(tokens_response['id_token'])[0]
-    session[:id_token] = tokens_response['id_token']
+    session[:id_token] = id_token
     gov_one_id = id_token['sub']
     return error_redirect unless auth_service.valid_id_token?(id_token, session[:gov_one_auth_nonce])
 
diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index 339497fa4..8b7fb7682 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -4,6 +4,7 @@
   let(:auth_service) { instance_double(GovOneAuthService) }
   let(:access_token) { 'mock_access_token' }
   let(:id_token) { 'mock_id_token' }
+  let(:decoded_id_token) { { 'sub' => 'mock_sub' } }
   let(:email) { 'test@example.com' }
   let(:params) do
     { 'code' => 'mock_code', 'state' => 'mock_state' }
@@ -16,9 +17,9 @@
 
     allow(GovOneAuthService).to receive(:new).and_return(auth_service)
     allow(auth_service).to receive(:tokens).and_return({ 'access_token' => access_token, 'id_token' => id_token })
-    allow(auth_service).to receive(:user_info).and_return({ 'email' => email })
+    allow(auth_service).to receive(:user_info).and_return({ 'email' => email, 'sub' => 'mock_sub' })
     allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
-    allow(auth_service).to receive(:decode_id_token).and_return([{ 'sub' => 'mock_sub' }])
+    allow(auth_service).to receive(:decode_id_token).and_return([decoded_id_token])
     allow(auth_service).to receive(:valid_id_token?).and_return(true)
   end
 
@@ -33,7 +34,7 @@
     end
 
     it 'redirects to complete registration' do
-      expect(session[:id_token]).to eq id_token
+      expect(session[:id_token]).to eq decoded_id_token
       expect(response).to redirect_to edit_registration_name_path
     end
   end
@@ -49,7 +50,7 @@
     end
 
     it 'redirects to /my-modules' do
-      expect(session[:id_token]).to eq id_token
+      expect(session[:id_token]).to eq decoded_id_token
       expect(response).to redirect_to my_modules_path
     end
   end
@@ -61,7 +62,7 @@
     end
 
     it 'redirects to /my-modules' do
-      expect(session[:id_token]).to eq id_token
+      expect(session[:id_token]).to eq decoded_id_token
       expect(response).to redirect_to my_modules_path
     end
   end

From 28f80d561d0567e17d8ef83053d82a2393bf5ab1 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 27 Nov 2023 17:14:57 +0000
Subject: [PATCH 082/122] update specs

---
 config/sitemap.rb                    | 1 +
 spec/requests/authentication_spec.rb | 4 ++--
 spec/system/sign_in_spec.rb          | 2 +-
 3 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/config/sitemap.rb b/config/sitemap.rb
index 901358c90..733b813ea 100644
--- a/config/sitemap.rb
+++ b/config/sitemap.rb
@@ -71,6 +71,7 @@
   # edit registration/account
   add edit_email_user_path
   add edit_password_user_path
+  add edit_registration_terms_and_conditions_path
   add edit_registration_name_path
   add edit_registration_setting_type_path
   add edit_registration_setting_type_other_path
diff --git a/spec/requests/authentication_spec.rb b/spec/requests/authentication_spec.rb
index 66da4ccf2..d2ad5bb74 100644
--- a/spec/requests/authentication_spec.rb
+++ b/spec/requests/authentication_spec.rb
@@ -4,7 +4,7 @@
 #
 RSpec.describe 'Authentication', type: :request do
   describe 'viewing authenticate_user! controller action' do
-    let(:action_path) { edit_registration_name_path }
+    let(:action_path) { edit_registration_terms_and_conditions_path }
 
     context 'with User not signed in' do
       it 'redirects to sign in page' do
@@ -89,7 +89,7 @@
 
       it 'redirects to finish registration' do
         get action_path
-        expect(response).to redirect_to(edit_registration_name_path)
+        expect(response).to redirect_to(edit_registration_terms_and_conditions_path)
       end
 
       it 'displays message to complete registration' do
diff --git a/spec/system/sign_in_spec.rb b/spec/system/sign_in_spec.rb
index a55ae8c2c..3cd4eec9e 100644
--- a/spec/system/sign_in_spec.rb
+++ b/spec/system/sign_in_spec.rb
@@ -45,7 +45,7 @@
 
     context 'and enters valid credentials' do
       it 'signs in successfully' do
-        expect(page).to have_text('About you') # extra registration
+        expect(page).to have_text('Agree to our terms and conditions') # extra registration
       end
     end
 

From d50aed79df719f1729a540cfcbe1286828726558 Mon Sep 17 00:00:00 2001
From: Peter David Hamilton <pete@peterdavidhamilton.com>
Date: Thu, 30 Nov 2023 12:32:25 +0000
Subject: [PATCH 083/122] Refactor .find_or_create_from_gov_one spec for dry
 lazy loading

User
  .find_or_create_from_gov_one
    with an existing account
      and using GovOne with a new email
        updates email
      and using GovOne for the first time
        associates GovOne ID
    without an existing account
      creates a new user
---
 spec/models/user_spec.rb | 61 +++++++++++++++++++++++-----------------
 1 file changed, 35 insertions(+), 26 deletions(-)

diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index c1b68455c..f43490f5c 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -208,43 +208,52 @@
   end
 
   describe '.find_or_create_from_gov_one' do
-    subject(:find_or_create) { described_class.find_or_create_from_gov_one(email: email, gov_one_id: gov_one_id) }
+    let(:email) { 'current@test.com' }
+    let(:gov_one_id) { 'urn:fdc:gov.uk:2022:23-random-alpha-numeric' }
 
-    let(:email) { 'test@test.com' }
-    let(:gov_one_id) { '123' }
-    let(:user) { create(:user) }
+    before do
+      described_class.find_or_create_from_gov_one(**params)
+    end
 
-    context 'with an existing user having an email but no gov_one_id' do
-      before do
-        user.update_column(:email, email)
-        find_or_create
+    context 'without an existing account' do
+      let(:params) do
+        { email: email, gov_one_id: gov_one_id }
       end
 
-      it 'updates the user gov_one_id' do
-        expect(user.reload.gov_one_id).to eq(gov_one_id)
+      it 'creates a new user' do
+        expect(described_class.count).to eq 1
+        expect(described_class.first.email).to eq params[:email]
+        expect(described_class.first.gov_one_id).to eq params[:gov_one_id]
       end
     end
 
-    context 'with an existing user having a gov_one_id' do
-      before do
-        user.update_column(:gov_one_id, gov_one_id)
-        find_or_create
-      end
+    context 'with an existing account' do
+      context 'and using GovOne for the first time' do
+        let(:user) do
+          create :user, :registered, email: email
+        end
 
-      it 'updates the user email' do
-        expect(user.reload.email).to eq(email)
-      end
-    end
+        let(:params) do
+          { email: user.email, gov_one_id: gov_one_id }
+        end
 
-    context 'without an existing user' do
-      before do
-        find_or_create
+        it 'associates GovOne ID' do
+          expect(user.reload.gov_one_id).to eq gov_one_id
+        end
       end
 
-      it 'creates a new user' do
-        expect(described_class.count).to eq(1)
-        expect(described_class.first.email).to eq(email)
-        expect(described_class.first.gov_one_id).to eq(gov_one_id)
+      context 'and using GovOne with a new email' do
+        let(:user) do
+          create :user, :registered, email: 'old@test.com', gov_one_id: gov_one_id
+        end
+
+        let(:params) do
+          { email: email, gov_one_id: user.gov_one_id }
+        end
+
+        it 'updates email' do
+          expect(user.reload.email).to eq email
+        end
       end
     end
   end

From f529817ed47861cb87830f68cc27a34b599f3605 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 1 Dec 2023 08:54:32 +0000
Subject: [PATCH 084/122] refactor gov one jwks to remove memoization

---
 app/services/gov_one_auth_service.rb | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index 1b06383a2..c212865ec 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -91,7 +91,7 @@ def build_http(address)
 
   # GET /.well-known/jwks.json
   # @return [Hash]
-  def fetch_and_cache_jwks
+  def jwks
     Rails.cache.fetch('jwks', expires_in: 24.hours) do
       uri, http = build_http(ENDPOINTS[:jwks])
       response = http.request(Net::HTTP::Get.new(uri.path))
@@ -99,11 +99,6 @@ def fetch_and_cache_jwks
     end
   end
 
-  # @return [Hash]
-  def jwks
-    @jwks ||= fetch_and_cache_jwks
-  end
-
   # @return [String]
   def jwt_assertion
     rsa_private = OpenSSL::PKey::RSA.new(Rails.application.config.gov_one_private_key)

From dad5b3ae3d771d56e8017eefb2832996de4f9b87 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 1 Dec 2023 15:47:51 +0000
Subject: [PATCH 085/122] add feature flag to redirect

---
 app/controllers/application_controller.rb                | 6 +++++-
 app/controllers/users/sessions_controller.rb             | 4 +++-
 config/application.rb                                    | 5 +++++
 .../confirmed_user/completing_registration_spec.rb       | 9 ++++-----
 4 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 7b9ba514a..3dd694581 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -20,7 +20,11 @@ def authenticate_registered_user!
     authenticate_user! unless user_signed_in?
     return true if current_user.registration_complete?
 
-    redirect_to edit_registration_terms_and_conditions_path, notice: 'Please complete registration'
+    if Rails.application.gov_one_login?
+      redirect_to edit_registration_terms_and_conditions_path, notice: 'Please complete registration'
+    else
+      redirect_to edit_registration_name_path, notice: 'Please complete registration'
+    end
   end
 
   def configure_permitted_parameters
diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb
index a4db408cb..aeb2584a5 100644
--- a/app/controllers/users/sessions_controller.rb
+++ b/app/controllers/users/sessions_controller.rb
@@ -16,8 +16,10 @@ def after_sign_in_path_for(resource)
       end
     elsif resource.private_beta_registration_complete?
       static_path('new-registration')
-    else
+    elsif Rails.application.gov_one_login?
       edit_registration_terms_and_conditions_path
+    else
+      edit_registration_name_path
     end
   end
 end
diff --git a/config/application.rb b/config/application.rb
index 42c3e1478..8a0b7345f 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -101,6 +101,11 @@ def maintenance?
       Types::Params::Bool[ENV.fetch('MAINTENANCE', false)]
     end
 
+    # @return [Boolean]
+    def gov_one_login?
+      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', false)]
+    end
+
     # @return [ActiveSupport::TimeWithZone]
     def public_beta_launch_date
       Time.zone.local(2023, 2, 9, 15, 0, 0)
diff --git a/spec/system/confirmed_user/completing_registration_spec.rb b/spec/system/confirmed_user/completing_registration_spec.rb
index bcd43c72b..105e843c9 100644
--- a/spec/system/confirmed_user/completing_registration_spec.rb
+++ b/spec/system/confirmed_user/completing_registration_spec.rb
@@ -1,15 +1,16 @@
 require 'rails_helper'
 
 RSpec.describe 'Confirmed users completing registration' do
-  include_context 'with user'
+  before do
+    allow(Rails.application).to receive(:gov_one_login?).and_return(true)
+  end
 
+  include_context 'with user'
   let(:user) { create :user, :confirmed }
 
   it 'requires name and a setting type and email preferences and a complete' do
     expect(page).to have_text('Terms and conditions')
-
     click_button 'Continue'
-
     expect(page).to have_text('There is a problem')
       .and have_text('You must accept the terms and conditions and privacy policy to create an account.')
 
@@ -60,14 +61,12 @@
 
     expect(page).to have_text('What is your role?')
       .and have_text('Enter your job title.')
-
     click_button 'Continue'
 
     expect(page).to have_text('There is a problem')
       .and have_text('Enter your job title.')
 
     fill_in 'Enter your job title.', with: 'user defined job title'
-
     click_button 'Continue'
 
     expect(page).to have_text('Do you want to get email updates about this training course?')

From 1c9fd7c632f87d0cdbe0e7958018dd38b9b9f217 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 1 Dec 2023 16:05:42 +0000
Subject: [PATCH 086/122] update sign_in_spec

---
 spec/system/sign_in_spec.rb | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/spec/system/sign_in_spec.rb b/spec/system/sign_in_spec.rb
index 3cd4eec9e..9480f0168 100644
--- a/spec/system/sign_in_spec.rb
+++ b/spec/system/sign_in_spec.rb
@@ -43,6 +43,10 @@
   context 'when user is confirmed' do
     let(:user) { create :user, :confirmed }
 
+    before do
+      allow(Rails.application).to receive(:gov_one_login?).and_return(true)
+    end
+
     context 'and enters valid credentials' do
       it 'signs in successfully' do
         expect(page).to have_text('Agree to our terms and conditions') # extra registration

From 0c6fa4691a70ecf208674e6eaceb3e7df122714b Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 4 Dec 2023 11:22:49 +0000
Subject: [PATCH 087/122] update locales, rename controller method

---
 app/assets/stylesheets/application.scss | 12 -----------
 app/controllers/gov_one_controller.rb   |  2 +-
 app/helpers/gov_one_helper.rb           |  2 +-
 app/views/gov_one/info.html.slim        | 28 -------------------------
 app/views/gov_one/show.html.slim        | 14 +++++++++++++
 config/locales/en.yml                   |  5 ++---
 config/routes.rb                        |  2 +-
 spec/system/whats_new_page_spec.rb      |  2 --
 8 files changed, 19 insertions(+), 48 deletions(-)
 delete mode 100644 app/views/gov_one/info.html.slim
 create mode 100644 app/views/gov_one/show.html.slim

diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index a87d09295..632edbb79 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -135,14 +135,6 @@ pre.code-tip {
     margin-bottom: govuk-spacing(6);
   }
 
-  &.enrol-box {
-    border-radius: 6px;
-
-    .govuk-button {
-      margin: 0;
-    }
-  }
-
 }
 
 .float-right {
@@ -190,7 +182,3 @@ ul>li>ul>li {
 .govuk-footer__copyright-logo {
   display: none;
 }
-
-.white-space-pre-wrap {
-  white-space: pre-wrap;
-}
diff --git a/app/controllers/gov_one_controller.rb b/app/controllers/gov_one_controller.rb
index de6d437dd..25faf07d0 100644
--- a/app/controllers/gov_one_controller.rb
+++ b/app/controllers/gov_one_controller.rb
@@ -1,7 +1,7 @@
 class GovOneController < ApplicationController
   layout 'hero'
 
-  def info
+  def show
     redirect_to my_modules_path if current_user
   end
 end
diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index f24c16022..869ce4637 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -28,7 +28,7 @@ def logout_uri
 
   # @return [String]
   def login_button
-    govuk_button_link_to t('gov_one_info.sign_in_button'), login_uri.to_s
+    govuk_button_link_to t('gov_one_info.button.sign_in'), login_uri.to_s
   end
 
 private
diff --git a/app/views/gov_one/info.html.slim b/app/views/gov_one/info.html.slim
deleted file mode 100644
index f50a52ce6..000000000
--- a/app/views/gov_one/info.html.slim
+++ /dev/null
@@ -1,28 +0,0 @@
-- content_for :page_title do
-  = html_title t('home.title')
-
-- content_for :hero do
-  .govuk-grid-row class='govuk-!-padding-top-9'
-    .govuk-grid-column-three-quarters
-      h1.dfe-heading-xl class='govuk-!-margin-bottom-4'
-        = t('gov_one_info.hero_header')
-      p.govuk-body-l = t('gov_one_info.hero_body')
-
-
-.govuk-grid-row
-  .govuk-grid-column-full
-    . class='govuk-!-margin-bottom-5'
-        = m('gov_one_info.body')
-        hr
-
-    = login_button
-
-.govuk-grid-row
-  .govuk-grid-column-three-quarters class='govuk-!-margin-top-4 govuk-!-margin-bottom-9'
-    details.govuk-details data-module='govuk-details'
-        summary.govuk-details__summary
-          span.govuk-details__summary-text
-            = t('gov_one_info.details_summary')
-        .govuk-details__text
-          = t('gov_one_info.details_text')
-    
\ No newline at end of file
diff --git a/app/views/gov_one/show.html.slim b/app/views/gov_one/show.html.slim
new file mode 100644
index 000000000..4fd836421
--- /dev/null
+++ b/app/views/gov_one/show.html.slim
@@ -0,0 +1,14 @@
+= render 'learning/cms_debug'
+
+- content_for :page_title do
+  = html_title t('gov_one_info.title')
+
+.govuk-grid-row
+  .govuk-grid-column-one-half
+    = m('gov_one_info.body')
+
+    - if current_user
+      = logout_button
+    - else
+      = login_button
+  
\ No newline at end of file
diff --git a/config/locales/en.yml b/config/locales/en.yml
index cce7aa516..744e0866c 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -508,8 +508,6 @@ en:
   home:
     title: Home page
     hero: |
-      # %{service_name}
-      
       This free, online training provides an overview of child development and gives practical advice for supporting the development of children in your early years setting.
     thumb: |
       ![A practitioner smiling as they do an activity with children in an early years setting](//images.ctfassets.net/dvmeh832nmjc/2VRBZCHJYLeKx0a2CIcJzE/687fb0d28c71104820cd42f131d7dfe2/_assets_thumb-1161068272-320x240.jpg?fit=scale&w=380)
@@ -581,7 +579,8 @@ en:
     hero_header: How to access this training course
     hero_body: This service uses GOV.UK One Login which is managed by the Government Digital Service.
     body: You will be asked to sign in to your account, or create a One Login account, in this service
-    sign_in_button: Continue to GOV.UK One Login
+    button:
+      sign_in: Continue to GOV.UK One Login
     details_summary: How to access an existing training account
     details_text: If you have an existing early years child development training account but you do not yet have a GOV.UK One Login, you must use the same email address for both accounts. This will ensure that any progress you have made through the training is retained.
 
diff --git a/config/routes.rb b/config/routes.rb
index 9de3c4b5c..eb38e312a 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#show'
 
   get '/404', to: 'errors#not_found', via: :all
   get '/422', to: 'errors#unprocessable_entity', via: :all
diff --git a/spec/system/whats_new_page_spec.rb b/spec/system/whats_new_page_spec.rb
index 1a86631ff..07685bd52 100644
--- a/spec/system/whats_new_page_spec.rb
+++ b/spec/system/whats_new_page_spec.rb
@@ -20,7 +20,6 @@
     end
 
     describe 'with subsequent logins' do
-      skip 'wip' do
         before do
           click_on 'sign-out-desktop'
 
@@ -36,5 +35,4 @@
         end
       end
     end
-  end
 end

From 948fa98606f6487d309ced1f57de4a46e142a44f Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 4 Dec 2023 11:57:12 +0000
Subject: [PATCH 088/122] use feature flag in application_helper links

---
 app/helpers/application_helper.rb  | 18 +++++++++++----
 app/views/gov_one/show.html.slim   | 36 +++++++++++++++++++++---------
 config/application.rb              |  5 +++++
 spec/system/gov_one_info_spec.rb   | 10 ++++-----
 spec/system/whats_new_page_spec.rb | 22 +++++++++---------
 5 files changed, 60 insertions(+), 31 deletions(-)

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 1d4c66edb..a892f5b4a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -11,14 +11,14 @@ def navigation
       header.with_navigation_item(text: 'Home', href: root_path, classes: %w[dfe-header__navigation-item])
       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: logout_uri.to_s, options: { id: 'sign-out-desktop', data: { turbo_method: :get }, inverse: true })
+        header.with_action_link(text: 'Sign out', href: logout_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: %w[dfe-header__navigation-item dfe-header-f-mob])
-        header.with_navigation_item(text: 'Sign out', href: logout_uri.to_s, options: { data: { turbo_method: :get } }, classes: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' })
+        header.with_navigation_item(text: 'Sign out', href: logout_path, options: { data: { turbo_method: :get } }, classes: %w[dfe-header__navigation-item dfe-header-f-mob], html_attributes: { id: 'sign-out-f-mob' })
       else
-        header.with_action_link(text: 'Sign in', href: gov_one_info_path, options: { inverse: true })
-        header.with_navigation_item(text: 'Sign in', href: gov_one_info_path, classes: %w[dfe-header__navigation-item dfe-header-f-mob])
+        header.with_action_link(text: 'Sign in', href: login_path, options: { inverse: true })
+        header.with_navigation_item(text: 'Sign in', href: login_path, classes: %w[dfe-header__navigation-item dfe-header-f-mob])
       end
     end
   end
@@ -59,4 +59,14 @@ def html_title(*parts)
   def calculate_module_state
     CalculateModuleState.new(user: current_user).call
   end
+
+  # @return [String]
+  def login_path
+    Rails.application.gov_one_login? ? gov_one_info_path : new_user_session_path
+  end
+
+  # @return [String]
+  def logout_path
+    Rails.application.gov_one_login? ? logout_uri.to_s : destroy_user_session_path
+  end
 end
diff --git a/app/views/gov_one/show.html.slim b/app/views/gov_one/show.html.slim
index 4fd836421..f50a52ce6 100644
--- a/app/views/gov_one/show.html.slim
+++ b/app/views/gov_one/show.html.slim
@@ -1,14 +1,28 @@
-= render 'learning/cms_debug'
-
 - content_for :page_title do
-  = html_title t('gov_one_info.title')
+  = html_title t('home.title')
+
+- content_for :hero do
+  .govuk-grid-row class='govuk-!-padding-top-9'
+    .govuk-grid-column-three-quarters
+      h1.dfe-heading-xl class='govuk-!-margin-bottom-4'
+        = t('gov_one_info.hero_header')
+      p.govuk-body-l = t('gov_one_info.hero_body')
+
+
+.govuk-grid-row
+  .govuk-grid-column-full
+    . class='govuk-!-margin-bottom-5'
+        = m('gov_one_info.body')
+        hr
+
+    = login_button
 
 .govuk-grid-row
-  .govuk-grid-column-one-half
-    = m('gov_one_info.body')
-
-    - if current_user
-      = logout_button
-    - else
-      = login_button
-  
\ No newline at end of file
+  .govuk-grid-column-three-quarters class='govuk-!-margin-top-4 govuk-!-margin-bottom-9'
+    details.govuk-details data-module='govuk-details'
+        summary.govuk-details__summary
+          span.govuk-details__summary-text
+            = t('gov_one_info.details_summary')
+        .govuk-details__text
+          = t('gov_one_info.details_text')
+    
\ No newline at end of file
diff --git a/config/application.rb b/config/application.rb
index 42c3e1478..8a0b7345f 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -101,6 +101,11 @@ def maintenance?
       Types::Params::Bool[ENV.fetch('MAINTENANCE', false)]
     end
 
+    # @return [Boolean]
+    def gov_one_login?
+      Types::Params::Bool[ENV.fetch('GOV_ONE_LOGIN', false)]
+    end
+
     # @return [ActiveSupport::TimeWithZone]
     def public_beta_launch_date
       Time.zone.local(2023, 2, 9, 15, 0, 0)
diff --git a/spec/system/gov_one_info_spec.rb b/spec/system/gov_one_info_spec.rb
index bb252a4b7..0d75df6ef 100644
--- a/spec/system/gov_one_info_spec.rb
+++ b/spec/system/gov_one_info_spec.rb
@@ -1,11 +1,12 @@
 require 'rails_helper'
 
 RSpec.describe 'Gov One Info' do
-  context 'with an unauthenticated visitor' do
-    before do
-      visit '/gov-one/info'
-    end
+  before do
+    allow(Rails.application).to receive(:gov_one_login?).and_return(false)
+    visit '/gov-one/info'
+  end
 
+  context 'with an unauthenticated visitor' do
     it 'displays the correct content' do
       expect(page).to have_css('h1', text: 'How to access this training course')
       expect(page).to have_css('p', text: 'This service uses GOV.UK One Login which is managed by the Government Digital Service.')
@@ -23,7 +24,6 @@
     include_context 'with user'
 
     it 'redirects to the my modules page' do
-      visit '/gov-one/info'
       expect(page).to have_current_path('/my-modules')
     end
   end
diff --git a/spec/system/whats_new_page_spec.rb b/spec/system/whats_new_page_spec.rb
index 07685bd52..7fc194638 100644
--- a/spec/system/whats_new_page_spec.rb
+++ b/spec/system/whats_new_page_spec.rb
@@ -20,19 +20,19 @@
     end
 
     describe 'with subsequent logins' do
-        before do
-          click_on 'sign-out-desktop'
+      before do
+        click_on 'sign-out-desktop'
 
-          visit '/users/sign-in'
-          fill_in 'Email address', with: user.email
-          fill_in 'Password', with: user.password
-          click_button 'Sign in'
-        end
+        visit '/users/sign-in'
+        fill_in 'Email address', with: user.email
+        fill_in 'Password', with: user.password
+        click_button 'Sign in'
+      end
 
-        it 'does not appear' do
-          expect(page).not_to have_current_path '/whats-new'
-          expect(page).to have_current_path '/my-modules'
-        end
+      it 'does not appear' do
+        expect(page).not_to have_current_path '/whats-new'
+        expect(page).to have_current_path '/my-modules'
       end
     end
+  end
 end

From 932ba80b37ba8f01925694fa7f69488852aaad3b Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 4 Dec 2023 13:15:50 +0000
Subject: [PATCH 089/122] nest gov one info locales

---
 app/views/gov_one/show.html.slim | 4 ++--
 config/locales/en.yml            | 5 +++--
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/app/views/gov_one/show.html.slim b/app/views/gov_one/show.html.slim
index f50a52ce6..f6e82ab15 100644
--- a/app/views/gov_one/show.html.slim
+++ b/app/views/gov_one/show.html.slim
@@ -5,8 +5,8 @@
   .govuk-grid-row class='govuk-!-padding-top-9'
     .govuk-grid-column-three-quarters
       h1.dfe-heading-xl class='govuk-!-margin-bottom-4'
-        = t('gov_one_info.hero_header')
-      p.govuk-body-l = t('gov_one_info.hero_body')
+        = t('gov_one_info.hero.header')
+      p.govuk-body-l = t('gov_one_info.hero.body')
 
 
 .govuk-grid-row
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 4e6760916..d944df393 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -578,8 +578,9 @@ en:
   # /gov-one/info
   gov_one_info:
     title: Gov One Info
-    hero_header: How to access this training course
-    hero_body: This service uses GOV.UK One Login which is managed by the Government Digital Service.
+    hero:
+      header: How to access this training course
+      body: This service uses GOV.UK One Login which is managed by the Government Digital Service.
     body: You will be asked to sign in to your account, or create a One Login account, in this service
     button:
       sign_in: Continue to GOV.UK One Login

From 9c8bdda6073197a1b2acff021f9dce60d36a8778 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 4 Dec 2023 13:27:53 +0000
Subject: [PATCH 090/122] simplify gov_one_info spec

---
 spec/system/gov_one_info_spec.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/system/gov_one_info_spec.rb b/spec/system/gov_one_info_spec.rb
index 0d75df6ef..d5a87ff42 100644
--- a/spec/system/gov_one_info_spec.rb
+++ b/spec/system/gov_one_info_spec.rb
@@ -16,7 +16,7 @@
 
     it 'has the correct login link' do
       link = find_link('Continue to GOV.UK One Login')
-      expect(link[:href]).to start_with('https://oidc.test.account.gov.uk/authorize?redirect_uri=http%3A%2F%2Frecovery.app%2Fusers%2Fauth%2Fopenid_connect%2Fcallback&client_id=some_client_id&response_type=code&scope=email+openid&nonce=')
+      expect(link[:href]).to start_with('https://oidc.test.account.gov.uk/authorize?')
     end
   end
 

From a21cd788bdd5fe5a0ed88f7b6a597c2650dd2625 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 4 Dec 2023 13:33:49 +0000
Subject: [PATCH 091/122] remove link assertion from gov_one_info spec

---
 spec/system/gov_one_info_spec.rb | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/spec/system/gov_one_info_spec.rb b/spec/system/gov_one_info_spec.rb
index d5a87ff42..4107f9c25 100644
--- a/spec/system/gov_one_info_spec.rb
+++ b/spec/system/gov_one_info_spec.rb
@@ -13,11 +13,6 @@
       expect(page).to have_css('p', text: 'You will be asked to sign in to your account, or create a One Login account, in this service')
       expect(page).to have_css('a', text: 'Continue to GOV.UK One Login')
     end
-
-    it 'has the correct login link' do
-      link = find_link('Continue to GOV.UK One Login')
-      expect(link[:href]).to start_with('https://oidc.test.account.gov.uk/authorize?')
-    end
   end
 
   context 'with an authenticated user' do

From b915b6a66b5de260dd61047d41560f4d76d54e30 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 4 Dec 2023 17:05:14 +0000
Subject: [PATCH 092/122] update authentication_spec

---
 spec/requests/authentication_spec.rb | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/spec/requests/authentication_spec.rb b/spec/requests/authentication_spec.rb
index d2ad5bb74..466b3af5c 100644
--- a/spec/requests/authentication_spec.rb
+++ b/spec/requests/authentication_spec.rb
@@ -85,7 +85,10 @@
     end
 
     context 'with partially registered User' do
-      before { sign_in create(:user, :confirmed) }
+      before do
+        allow(Rails.application).to receive(:gov_one_login?).and_return(true)
+        sign_in create(:user, :confirmed)
+      end
 
       it 'redirects to finish registration' do
         get action_path

From d1b61eb89668ea2885cea826354b7a7a328e8417 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 5 Dec 2023 09:15:05 +0000
Subject: [PATCH 093/122] update sign_in system spec

---
 spec/system/sign_in_spec.rb | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/spec/system/sign_in_spec.rb b/spec/system/sign_in_spec.rb
index 9480f0168..b955ce97a 100644
--- a/spec/system/sign_in_spec.rb
+++ b/spec/system/sign_in_spec.rb
@@ -5,6 +5,7 @@
   let(:password) { 'Str0ngPa$$w0rd' }
 
   before do
+    allow(Rails.application).to receive(:gov_one_login?).and_return(true)
     visit '/users/sign-in'
     fill_in 'Email address', with: email_address
     fill_in 'Password', with: password
@@ -43,10 +44,6 @@
   context 'when user is confirmed' do
     let(:user) { create :user, :confirmed }
 
-    before do
-      allow(Rails.application).to receive(:gov_one_login?).and_return(true)
-    end
-
     context 'and enters valid credentials' do
       it 'signs in successfully' do
         expect(page).to have_text('Agree to our terms and conditions') # extra registration

From 1f0dd1e46e8dbf976fec0da3170bd706088a7109 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 5 Dec 2023 10:10:33 +0000
Subject: [PATCH 094/122] wip

---
 spec/system/registered_user/changing_password_spec.rb | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/spec/system/registered_user/changing_password_spec.rb b/spec/system/registered_user/changing_password_spec.rb
index 47679ba47..e1e58782c 100644
--- a/spec/system/registered_user/changing_password_spec.rb
+++ b/spec/system/registered_user/changing_password_spec.rb
@@ -5,9 +5,11 @@
 
   let(:password) { 'Str0ngPa$$w0rd' }
 
+  
   include_context 'with user'
 
   before do
+    puts user.email
     visit '/my-account/edit-password'
     fill_in 'Enter your current password', with: 'Str0ngPa$$w0rd'
     fill_in 'Create a new password', with: password

From 3eb81be1e14b5c949e286a015b0df1cb11729bb2 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 5 Dec 2023 10:13:59 +0000
Subject: [PATCH 095/122] revert change_password spec changes

---
 .../registered_user/changing_password_spec.rb | 57 +++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 spec/system/registered_user/changing_password_spec.rb

diff --git a/spec/system/registered_user/changing_password_spec.rb b/spec/system/registered_user/changing_password_spec.rb
new file mode 100644
index 000000000..e1e58782c
--- /dev/null
+++ b/spec/system/registered_user/changing_password_spec.rb
@@ -0,0 +1,57 @@
+require 'rails_helper'
+
+RSpec.describe 'Registered user changing password', type: :system do
+  subject(:user) { create :user, :registered, created_at: 1.month.ago }
+
+  let(:password) { 'Str0ngPa$$w0rd' }
+
+  
+  include_context 'with user'
+
+  before do
+    puts user.email
+    visit '/my-account/edit-password'
+    fill_in 'Enter your current password', with: 'Str0ngPa$$w0rd'
+    fill_in 'Create a new password', with: password
+    fill_in 'Confirm password', with: password
+  end
+
+  context 'when cancelled' do
+    it 'returns to account page' do
+      click_link 'Cancel'
+      expect(page).to have_current_path '/my-account'
+      expect(page).not_to have_text 'Your new password has been saved.'
+    end
+  end
+
+  context 'when successful' do
+    let(:password) { '12!@NewPassword' }
+    let(:today) { Time.zone.today.to_formatted_s(:rfc822) } # 18 May 2022
+
+    it 'updates password' do
+      click_button 'Save'
+      expect(page).to have_current_path '/my-account'
+      expect(page).to have_text('Manage your account') # page heading
+        .and have_text('Your new password has been saved.') # flash message
+        .and have_text("Password last changed on #{today}") # event
+    end
+  end
+
+  context 'when too short' do
+    let(:password) { 'short' }
+
+    it 'renders an error message' do
+      click_button 'Save'
+      expect(page).to have_text 'Password must be at least 10 characters.'
+    end
+  end
+
+  context 'when blank' do
+    let(:password) { '' }
+
+    it 'renders an error message' do
+      click_button 'Save'
+      expect(page).to have_text 'Enter a password.'
+    end
+  end
+end

From 9df3ed6b3915fd29d2cef051ea79bedca35ce200 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 6 Dec 2023 10:23:06 +0000
Subject: [PATCH 096/122] update change_password spec

---
 app/helpers/gov_one_helper.rb                         | 2 --
 spec/system/registered_user/changing_password_spec.rb | 4 +---
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/app/helpers/gov_one_helper.rb b/app/helpers/gov_one_helper.rb
index 6e36bab6f..23cd93f05 100644
--- a/app/helpers/gov_one_helper.rb
+++ b/app/helpers/gov_one_helper.rb
@@ -18,8 +18,6 @@ def login_uri
 
   # @return [URI]
   def logout_uri
-    puts "logout_uri: #{session[:id_token]}"
-    puts "logout_uri: #{session[:id_token]}"
     params = {
       post_logout_redirect_uri: GovOneAuthService::CALLBACKS[:logout],
       id_token_hint: session[:id_token],
diff --git a/spec/system/registered_user/changing_password_spec.rb b/spec/system/registered_user/changing_password_spec.rb
index e1e58782c..fec092658 100644
--- a/spec/system/registered_user/changing_password_spec.rb
+++ b/spec/system/registered_user/changing_password_spec.rb
@@ -5,7 +5,6 @@
 
   let(:password) { 'Str0ngPa$$w0rd' }
 
-  
   include_context 'with user'
 
   before do
@@ -32,8 +31,7 @@
       click_button 'Save'
       expect(page).to have_current_path '/my-account'
       expect(page).to have_text('Manage your account') # page heading
-        .and have_text('Your new password has been saved.') # flash message
-        .and have_text("Password last changed on #{today}") # event
+        .and have_text('Your new password has been saved.')
     end
   end
 

From 3eb13d5623bb4590cd6c8318a7b778510eb645df Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 6 Dec 2023 11:04:34 +0000
Subject: [PATCH 097/122] dry up auth service spec

---
 spec/services/gov_one_auth_service_spec.rb | 29 ++++++++++------------
 1 file changed, 13 insertions(+), 16 deletions(-)

diff --git a/spec/services/gov_one_auth_service_spec.rb b/spec/services/gov_one_auth_service_spec.rb
index 66b65187b..94e08d9ce 100644
--- a/spec/services/gov_one_auth_service_spec.rb
+++ b/spec/services/gov_one_auth_service_spec.rb
@@ -1,5 +1,12 @@
 require 'rails_helper'
 
+shared_examples 'a one login request' do
+  it 'returns a hash of the expected payload' do
+    expect(result).to eq(payload)
+    expect(auth_service).to have_received(:response).with(an_instance_of(request_type), an_instance_of(Net::HTTP))
+  end
+end
+
 RSpec.describe GovOneAuthService do
   let(:code) { 'mock_code' }
   let(:mock_response) { instance_double('response') }
@@ -13,42 +20,32 @@
 
   describe '#tokens' do
     let(:result) { auth_service.tokens }
+    let(:request_type) { Net::HTTP::Post }
 
     context 'when successful' do
-      it 'returns a hash of tokens' do
-        expect(result).to eq(payload)
-        expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Post), an_instance_of(Net::HTTP))
-      end
+      it_behaves_like 'a one login request'
     end
 
     context 'when unsuccessful' do
       let(:payload) { {} }
 
-      it 'returns an empty hash' do
-        expect(result).to eq(payload)
-        expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Post), an_instance_of(Net::HTTP))
-      end
+      it_behaves_like 'a one login request'
     end
   end
 
   describe '#user_info' do
     let(:access_token) { 'mock_access_token' }
     let(:result) { auth_service.user_info(access_token) }
+    let(:request_type) { Net::HTTP::Get }
 
     context 'when successful' do
-      it 'returns a hash of user info' do
-        expect(result).to eq(payload)
-        expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Get), an_instance_of(Net::HTTP))
-      end
+      it_behaves_like 'a one login request'
     end
 
     context 'when unsuccessful' do
       let(:payload) { {} }
 
-      it 'returns an empty hash' do
-        expect(result).to eq(payload)
-        expect(auth_service).to have_received(:response).with(an_instance_of(Net::HTTP::Get), an_instance_of(Net::HTTP))
-      end
+      it_behaves_like 'a one login request'
     end
   end
 

From a59d6c48c0941d37d4b7674cc397c51403adafdc Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 6 Dec 2023 11:28:47 +0000
Subject: [PATCH 098/122] update seed_snippets_spec locales count

---
 spec/lib/seed_snippets_spec.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb
index fbcf787bf..012f85abc 100644
--- a/spec/lib/seed_snippets_spec.rb
+++ b/spec/lib/seed_snippets_spec.rb
@@ -5,7 +5,7 @@
   subject(:locales) { described_class.new.call }
 
   it 'converts all translations' do
-    expect(locales.count).to be 194
+    expect(locales.count).to be 199
   end
 
   it 'dot separated key -> Page::Resource#name' do

From 39d565cf3432cca7c1611abb8e8b93f76dbb5c24 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 6 Dec 2023 11:33:02 +0000
Subject: [PATCH 099/122] fix merge conflicts

---
 .github/workflows/ci.yml       |  1 +
 Gemfile.lock                   | 69 ----------------------------------
 spec/lib/seed_snippets_spec.rb |  4 --
 3 files changed, 1 insertion(+), 73 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index afcda98a2..b0b10efcf 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -33,6 +33,7 @@ jobs:
       DATABASE_URL: postgres://postgres:password@localhost:5432/test
       DOMAIN: recovery.app
       BOT_TOKEN: bot_token
+
     services:
       postgres:
         image: postgres:15.4-alpine
diff --git a/Gemfile.lock b/Gemfile.lock
index 432493a7b..661ce8319 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -68,17 +68,11 @@ GEM
       tzinfo (~> 2.0)
     addressable (2.8.5)
       public_suffix (>= 2.0.2, < 6.0)
-<<<<<<< HEAD
-    aes_key_wrap (1.1.0)
-    ahoy_matey (5.0.1)
-=======
     ahoy_matey (5.0.2)
->>>>>>> origin
       activesupport (>= 6.1)
       device_detector (>= 1)
       safely_block (>= 0.4)
     ast (2.4.2)
-    attr_required (1.0.1)
     backports (3.24.1)
     base64 (0.1.1)
     bcrypt (3.1.19)
@@ -86,7 +80,6 @@ GEM
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
       rouge (>= 1.0.0)
-    bindata (2.4.15)
     bindex (0.8.1)
     binding_of_caller (1.0.0)
       debug_inspector (>= 0.0.1)
@@ -192,8 +185,6 @@ GEM
       base64
       faraday-net_http (>= 2.0, < 3.1)
       ruby2_keywords (>= 0.0.4)
-    faraday-follow_redirects (0.3.0)
-      faraday (>= 1, < 3)
     faraday-net_http (3.0.2)
     ffi (1.16.3)
     ffi-compiler (1.0.1)
@@ -286,12 +277,6 @@ GEM
     jsbundling-rails (1.2.1)
       railties (>= 6.0.0)
     json (2.6.3)
-    json-jwt (1.16.3)
-      activesupport (>= 4.2)
-      aes_key_wrap
-      bindata
-      faraday (~> 2.0)
-      faraday-follow_redirects
     jwt (2.7.1)
     language_server-protocol (3.17.0.3)
     launchy (2.5.2)
@@ -335,29 +320,6 @@ GEM
       racc (~> 1.4)
     notifications-ruby-client (5.4.0)
       jwt (>= 1.5, < 3)
-    omniauth (2.1.1)
-      hashie (>= 3.4.6)
-      rack (>= 2.2.3)
-      rack-protection
-    omniauth-rails_csrf_protection (1.0.1)
-      actionpack (>= 4.2)
-      omniauth (~> 2.0)
-    omniauth_openid_connect (0.7.1)
-      omniauth (>= 1.9, < 3)
-      openid_connect (~> 2.2)
-    openid_connect (2.2.0)
-      activemodel
-      attr_required (>= 1.0.0)
-      faraday (~> 2.0)
-      faraday-follow_redirects
-      json-jwt (>= 1.16)
-      net-smtp
-      rack-oauth2 (~> 2.2)
-      swd (~> 2.0)
-      tzinfo
-      validate_email
-      validate_url
-      webfinger (~> 2.0)
     orm_adapter (0.5.0)
     os (1.1.4)
     pagy (6.2.0)
@@ -392,15 +354,6 @@ GEM
     raabro (1.4.0)
     racc (1.7.3)
     rack (2.2.8)
-    rack-oauth2 (2.2.0)
-      activesupport
-      attr_required
-      faraday (~> 2.0)
-      faraday-follow_redirects
-      json-jwt (>= 1.11.0)
-      rack (>= 2.1.0)
-    rack-protection (3.1.0)
-      rack (~> 2.2, >= 2.2.4)
     rack-test (2.1.0)
       rack (>= 1.3)
     rails (7.0.8)
@@ -560,11 +513,6 @@ GEM
     stimulus-rails (1.2.2)
       railties (>= 6.0.0)
     stringio (3.0.8)
-    swd (2.0.2)
-      activesupport (>= 3)
-      attr_required (>= 0.0.5)
-      faraday (~> 2.0)
-      faraday-follow_redirects
     temple (0.10.3)
     thor (1.3.0)
     tilt (2.3.0)
@@ -581,17 +529,7 @@ GEM
       unf_ext
     unf_ext (0.0.8.2)
     unicode-display_width (2.5.0)
-<<<<<<< HEAD
-    validate_email (0.1.6)
-      activemodel (>= 3.0)
-      mail (>= 2.2.5)
-    validate_url (1.0.15)
-      activemodel (>= 3.0.0)
-      public_suffix
-    view_component (3.5.0)
-=======
     view_component (3.6.0)
->>>>>>> origin
       activesupport (>= 5.2.0, < 8.0)
       concurrent-ruby (~> 1.0)
       method_source (~> 1.0)
@@ -602,10 +540,6 @@ GEM
       activemodel (>= 6.0.0)
       bindex (>= 0.4.0)
       railties (>= 6.0.0)
-    webfinger (2.1.2)
-      activesupport
-      faraday (~> 2.0)
-      faraday-follow_redirects
     webrick (1.8.1)
     websocket (1.2.10)
     websocket-driver (0.7.6)
@@ -664,10 +598,7 @@ DEPENDENCIES
   importmap-rails
   jbuilder
   jsbundling-rails
-  jwt
   launchy
-  omniauth-rails_csrf_protection
-  omniauth_openid_connect
   pg
   pry-byebug
   pry-doc
diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb
index 77e22c09a..012f85abc 100644
--- a/spec/lib/seed_snippets_spec.rb
+++ b/spec/lib/seed_snippets_spec.rb
@@ -5,11 +5,7 @@
   subject(:locales) { described_class.new.call }
 
   it 'converts all translations' do
-<<<<<<< HEAD
     expect(locales.count).to be 199
-=======
-    expect(locales.count).to be 189
->>>>>>> origin
   end
 
   it 'dot separated key -> Page::Resource#name' do

From 239761219e5525823c0c106fa668fd349f7d9b22 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 7 Dec 2023 16:26:22 +0000
Subject: [PATCH 100/122] udpate Gemfile.lock

---
 Gemfile.lock | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 61 insertions(+)

diff --git a/Gemfile.lock b/Gemfile.lock
index 661ce8319..fe7a62830 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -68,11 +68,13 @@ GEM
       tzinfo (~> 2.0)
     addressable (2.8.5)
       public_suffix (>= 2.0.2, < 6.0)
+    aes_key_wrap (1.1.0)
     ahoy_matey (5.0.2)
       activesupport (>= 6.1)
       device_detector (>= 1)
       safely_block (>= 0.4)
     ast (2.4.2)
+    attr_required (1.0.1)
     backports (3.24.1)
     base64 (0.1.1)
     bcrypt (3.1.19)
@@ -80,6 +82,7 @@ GEM
       erubi (>= 1.0.0)
       rack (>= 0.9.0)
       rouge (>= 1.0.0)
+    bindata (2.4.15)
     bindex (0.8.1)
     binding_of_caller (1.0.0)
       debug_inspector (>= 0.0.1)
@@ -185,6 +188,8 @@ GEM
       base64
       faraday-net_http (>= 2.0, < 3.1)
       ruby2_keywords (>= 0.0.4)
+    faraday-follow_redirects (0.3.0)
+      faraday (>= 1, < 3)
     faraday-net_http (3.0.2)
     ffi (1.16.3)
     ffi-compiler (1.0.1)
@@ -277,6 +282,12 @@ GEM
     jsbundling-rails (1.2.1)
       railties (>= 6.0.0)
     json (2.6.3)
+    json-jwt (1.16.3)
+      activesupport (>= 4.2)
+      aes_key_wrap
+      bindata
+      faraday (~> 2.0)
+      faraday-follow_redirects
     jwt (2.7.1)
     language_server-protocol (3.17.0.3)
     launchy (2.5.2)
@@ -320,6 +331,29 @@ GEM
       racc (~> 1.4)
     notifications-ruby-client (5.4.0)
       jwt (>= 1.5, < 3)
+    omniauth (2.1.1)
+      hashie (>= 3.4.6)
+      rack (>= 2.2.3)
+      rack-protection
+    omniauth-rails_csrf_protection (1.0.1)
+      actionpack (>= 4.2)
+      omniauth (~> 2.0)
+    omniauth_openid_connect (0.7.1)
+      omniauth (>= 1.9, < 3)
+      openid_connect (~> 2.2)
+    openid_connect (2.2.0)
+      activemodel
+      attr_required (>= 1.0.0)
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      json-jwt (>= 1.16)
+      net-smtp
+      rack-oauth2 (~> 2.2)
+      swd (~> 2.0)
+      tzinfo
+      validate_email
+      validate_url
+      webfinger (~> 2.0)
     orm_adapter (0.5.0)
     os (1.1.4)
     pagy (6.2.0)
@@ -354,6 +388,15 @@ GEM
     raabro (1.4.0)
     racc (1.7.3)
     rack (2.2.8)
+    rack-oauth2 (2.2.0)
+      activesupport
+      attr_required
+      faraday (~> 2.0)
+      faraday-follow_redirects
+      json-jwt (>= 1.11.0)
+      rack (>= 2.1.0)
+    rack-protection (3.1.0)
+      rack (~> 2.2, >= 2.2.4)
     rack-test (2.1.0)
       rack (>= 1.3)
     rails (7.0.8)
@@ -513,6 +556,11 @@ GEM
     stimulus-rails (1.2.2)
       railties (>= 6.0.0)
     stringio (3.0.8)
+    swd (2.0.2)
+      activesupport (>= 3)
+      attr_required (>= 0.0.5)
+      faraday (~> 2.0)
+      faraday-follow_redirects
     temple (0.10.3)
     thor (1.3.0)
     tilt (2.3.0)
@@ -529,6 +577,12 @@ GEM
       unf_ext
     unf_ext (0.0.8.2)
     unicode-display_width (2.5.0)
+    validate_email (0.1.6)
+      activemodel (>= 3.0)
+      mail (>= 2.2.5)
+    validate_url (1.0.15)
+      activemodel (>= 3.0.0)
+      public_suffix
     view_component (3.6.0)
       activesupport (>= 5.2.0, < 8.0)
       concurrent-ruby (~> 1.0)
@@ -540,6 +594,10 @@ GEM
       activemodel (>= 6.0.0)
       bindex (>= 0.4.0)
       railties (>= 6.0.0)
+    webfinger (2.1.2)
+      activesupport
+      faraday (~> 2.0)
+      faraday-follow_redirects
     webrick (1.8.1)
     websocket (1.2.10)
     websocket-driver (0.7.6)
@@ -598,7 +656,10 @@ DEPENDENCIES
   importmap-rails
   jbuilder
   jsbundling-rails
+  jwt
   launchy
+  omniauth-rails_csrf_protection
+  omniauth_openid_connect
   pg
   pry-byebug
   pry-doc

From ae583a675301e8c5d151372fabbca64986b89e1e Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 7 Dec 2023 16:34:39 +0000
Subject: [PATCH 101/122] update seeds_snippets_spec locales

---
 spec/lib/seed_snippets_spec.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb
index 012f85abc..80ef91229 100644
--- a/spec/lib/seed_snippets_spec.rb
+++ b/spec/lib/seed_snippets_spec.rb
@@ -5,7 +5,7 @@
   subject(:locales) { described_class.new.call }
 
   it 'converts all translations' do
-    expect(locales.count).to be 199
+    expect(locales.count).to be 201
   end
 
   it 'dot separated key -> Page::Resource#name' do

From e19270b93e22fe709fa61a4cf59ee71c31503ff2 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 7 Dec 2023 16:46:00 +0000
Subject: [PATCH 102/122] remove print to console

---
 spec/system/registered_user/changing_password_spec.rb | 1 -
 1 file changed, 1 deletion(-)

diff --git a/spec/system/registered_user/changing_password_spec.rb b/spec/system/registered_user/changing_password_spec.rb
index fec092658..954654d8e 100644
--- a/spec/system/registered_user/changing_password_spec.rb
+++ b/spec/system/registered_user/changing_password_spec.rb
@@ -8,7 +8,6 @@
   include_context 'with user'
 
   before do
-    puts user.email
     visit '/my-account/edit-password'
     fill_in 'Enter your current password', with: 'Str0ngPa$$w0rd'
     fill_in 'Create a new password', with: password

From 7d7f435c4a07150b35f6bfba275cd7fc1e9870c1 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 8 Dec 2023 10:45:49 +0000
Subject: [PATCH 103/122] add request spec for terms and conditions

---
 .../registration/terms_and_conditions_spec.rb    | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)
 create mode 100644 spec/requests/registration/terms_and_conditions_spec.rb

diff --git a/spec/requests/registration/terms_and_conditions_spec.rb b/spec/requests/registration/terms_and_conditions_spec.rb
new file mode 100644
index 000000000..ba7c49cbc
--- /dev/null
+++ b/spec/requests/registration/terms_and_conditions_spec.rb
@@ -0,0 +1,16 @@
+require 'rails_helper'
+
+RSpec.describe 'Registration names', type: :request do
+  subject(:user) { create(:user, :confirmed) }
+
+  before do
+    sign_in user
+  end
+
+  describe 'GET /registration/terms_and_conditions/edit' do
+    it 'returns http success' do
+      get edit_registration_name_path
+      expect(response).to have_http_status(:success)
+    end
+  end
+end

From d461b513be05f3e43418c1f81db7e58bc87dc33a Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 11 Dec 2023 11:52:52 +0000
Subject: [PATCH 104/122] update account page to use feature flag and add
 system spec

---
 app/assets/stylesheets/application.scss |  4 +++
 app/views/user/show.html.slim           | 17 ++++++++++--
 spec/system/account_page_spec.rb        | 36 +++++++++++++++++++++++++
 3 files changed, 55 insertions(+), 2 deletions(-)
 create mode 100644 spec/system/account_page_spec.rb

diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 0140ccbc2..d7937cd2f 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -186,3 +186,7 @@ ul>li>ul>li {
 #available h2 .govuk-tag {
   margin-left: govuk-spacing(1);
 }
+
+.text-secondary {
+  color: $govuk-secondary-text-colour;
+}
diff --git a/app/views/user/show.html.slim b/app/views/user/show.html.slim
index 39f273dcc..a30d69052 100644
--- a/app/views/user/show.html.slim
+++ b/app/views/user/show.html.slim
@@ -14,8 +14,21 @@
         - row.with_key { 'Name' }
         - row.with_value(text: current_user.name, classes: %w[data-hj-suppress])
         - row.with_action(text: 'Change name', href: edit_registration_name_path, html_attributes: { id: :edit_name_registration })
-    p.text-secondary
-      = t('my_account.name_information')
+
+      - unless Rails.application.gov_one_login?
+        - your_details.with_row do |row|
+          - row.with_key { 'Email' }
+          - row.with_value(text: current_user.email, classes: %w[data-hj-suppress])
+          - row.with_action(text: 'Change email', href: edit_email_user_path, html_attributes: { id: :edit_email_user })
+        - your_details.with_row do |row|
+          - row.with_key { 'Password' }
+          - row.with_value { t('my_account.password_changed', date: current_user.password_last_changed) }
+          - row.with_action(text: 'Change password', href: edit_password_user_path, html_attributes: { id: :edit_password_user })
+          
+    - if Rails.application.gov_one_login?
+      p.text-secondary
+        = t('my_account.name_information')
+
 
     = govuk_summary_list do |other_details|
       - other_details.with_row do |row|
diff --git a/spec/system/account_page_spec.rb b/spec/system/account_page_spec.rb
new file mode 100644
index 000000000..ec36ad24e
--- /dev/null
+++ b/spec/system/account_page_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+RSpec.describe 'Account page', type: :system do
+  subject(:user) { create :user, :registered }
+
+  include_context 'with user'
+
+  context 'when gov_one login is disabled' do
+    before do
+      allow(Rails.application).to receive(:gov_one_login?).and_return(false)
+      visit '/my-account'
+    end
+
+    it 'displays account details and password options' do
+      expect(page).to have_text('Manage your account')
+      expect(page).to have_css('a', text: 'Change name')
+      expect(page).to have_css('a', text: 'Change password')
+      expect(page).to have_css('a', text: 'Change setting details')
+      expect(page).to have_css('a', text: 'Change email preferences')
+      expect(page).to have_text('Closing your account')
+    end
+  end
+
+  context 'when gov_one login is enabled' do
+    before do
+      allow(Rails.application).to receive(:gov_one_login?).and_return(true)
+      visit '/my-account'
+    end
+
+    it 'password options are not listed and helper text is displayed' do
+      expect(page).to have_text('Manage your account')
+      expect(page).not_to have_css('a', text: 'Change password')
+      expect(page).to have_content('Changing your name on this account will not affect your Gov.UK One Login account')
+    end
+  end
+end

From 776d8d5f5b23cf3d3f6f3524b35517794cfecbe4 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 11 Dec 2023 12:01:06 +0000
Subject: [PATCH 105/122] update locales content

---
 config/locales/en.yml            | 2 +-
 spec/system/account_page_spec.rb | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/config/locales/en.yml b/config/locales/en.yml
index 9583ad135..0964023ae 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -299,7 +299,7 @@ en:
   # /my-account
   my_account:
     title: My account
-    name_information: This is the name that will appear on you end of module certificate. You can use this setting to change how your name appears. Changing your name on this account will not affect your GOV.UK One Login account.
+    name_information: This is the name that will appear on you end of module certificate. You can use this setting to change how your name appears. Changing your name on this account will not affect your GOV.UK One Login
     setting_details: |
       ## Your setting details
     email_preferences: |
diff --git a/spec/system/account_page_spec.rb b/spec/system/account_page_spec.rb
index ec36ad24e..ac3a0547d 100644
--- a/spec/system/account_page_spec.rb
+++ b/spec/system/account_page_spec.rb
@@ -30,7 +30,7 @@
     it 'password options are not listed and helper text is displayed' do
       expect(page).to have_text('Manage your account')
       expect(page).not_to have_css('a', text: 'Change password')
-      expect(page).to have_content('Changing your name on this account will not affect your Gov.UK One Login account')
+      expect(page).to have_content('Changing your name on this account will not affect your Gov.UK One Login')
     end
   end
 end

From 76a6e62d574735ebb4f31116a7999a7d08d6208c Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 15 Dec 2023 16:14:06 +0000
Subject: [PATCH 106/122] refactor id_token verification and update specs

---
 .../users/omniauth_callbacks_controller.rb        | 14 ++++++++++++--
 app/services/gov_one_auth_service.rb              |  9 ---------
 .../users/omniauth_callbacks_controller_spec.rb   | 15 +++++++++++++--
 3 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 405db0772..ad37e3f73 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -5,16 +5,17 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
   # This method is called by Devise after successful Gov One Login authentication
   # @return [nil]
   def openid_connect
-    return unless session_params? && valid_params?
+    return error_redirect unless session_params? && valid_params?
 
     auth_service = GovOneAuthService.new(code: params['code'])
     tokens_response = auth_service.tokens
     return error_redirect unless valid_tokens?(tokens_response)
 
     id_token = auth_service.decode_id_token(tokens_response['id_token'])[0]
+    return error_redirect unless valid_id_token?(id_token)
+
     session[:id_token] = tokens_response['id_token']
     gov_one_id = id_token['sub']
-    return error_redirect unless auth_service.valid_id_token?(id_token, session[:gov_one_auth_nonce])
 
     user_info_response = auth_service.user_info(tokens_response['access_token'])
     email = user_info_response['email']
@@ -45,6 +46,15 @@ def valid_tokens?(tokens_response)
     tokens_response.present? && tokens_response['access_token'].present? && tokens_response['id_token'].present?
   end
 
+  # @param id_token [Hash]
+  # @return [Boolean]
+  def valid_id_token?(id_token)
+    id_token.present? &&
+      id_token['nonce'] == session[:gov_one_auth_nonce] &&
+      id_token['iss'] == ENV['GOV_ONE_ISSUER'] &&
+      id_token['aud'] == ENV['GOV_ONE_CLIENT_ID']
+  end
+
   # @param user_info_response [Hash]
   # @return [Boolean]
   def valid_user_info?(user_info_response, gov_one_id)
diff --git a/app/services/gov_one_auth_service.rb b/app/services/gov_one_auth_service.rb
index c212865ec..0c36dab39 100644
--- a/app/services/gov_one_auth_service.rb
+++ b/app/services/gov_one_auth_service.rb
@@ -38,15 +38,6 @@ def tokens
     {}
   end
 
-  # @param token [String]
-  # @param nonce [String]
-  # @return [Boolean]
-  def valid_id_token?(token, nonce)
-    token['iss'] == "#{Rails.application.config.gov_one_base_uri}/" &&
-      token['aud'] == Rails.application.config.gov_one_client_id &&
-      token['nonce'] == nonce
-  end
-
   # GET /userinfo
   # @param access_token [String]
   # @return [Hash]
diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index fd6c2b331..f42b04a05 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -4,7 +4,7 @@
   let(:auth_service) { instance_double(GovOneAuthService) }
   let(:access_token) { 'mock_access_token' }
   let(:id_token) { 'mock_id_token' }
-  let(:decoded_id_token) { { 'sub' => 'mock_sub' } }
+  let(:decoded_id_token) { { 'sub' => 'mock_sub', 'nonce' => 'mock_nonce', 'iss' => ENV['GOV_ONE_ISSUER'], 'aud' => ENV['GOV_ONE_CLIENT_ID'] } }
   let(:email) { 'test@example.com' }
   let(:params) do
     { 'code' => 'mock_code', 'state' => 'mock_state' }
@@ -20,7 +20,6 @@
     allow(auth_service).to receive(:user_info).and_return({ 'email' => email, 'sub' => 'mock_sub' })
     allow(auth_service).to receive(:jwt_assertion).and_return('mock_jwt_assertion')
     allow(auth_service).to receive(:decode_id_token).and_return([decoded_id_token])
-    allow(auth_service).to receive(:valid_id_token?).and_return(true)
   end
 
   context 'with a new user' do
@@ -66,4 +65,16 @@
       expect(response).to redirect_to my_modules_path
     end
   end
+
+  context 'with invalid session parameters' do
+    before do
+      session[:gov_one_auth_state] = nil
+      get :openid_connect, params: params
+    end
+  
+    it 'redirects to root path with an error message' do
+      expect(flash[:alert]).to eq 'There was a problem signing in. Please try again.'
+      expect(response).to redirect_to root_path
+    end
+  end
 end

From 49a5799d097f4468fe5cd8887045d78452b190ef Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Fri, 15 Dec 2023 16:26:03 +0000
Subject: [PATCH 107/122] rubocop

---
 spec/controllers/users/omniauth_callbacks_controller_spec.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index f42b04a05..c9863c1dc 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -71,7 +71,7 @@
       session[:gov_one_auth_state] = nil
       get :openid_connect, params: params
     end
-  
+
     it 'redirects to root path with an error message' do
       expect(flash[:alert]).to eq 'There was a problem signing in. Please try again.'
       expect(response).to redirect_to root_path

From ea0210ddaf3d5e34a4b0475407d996fbdbd38faf Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 18 Dec 2023 10:25:58 +0000
Subject: [PATCH 108/122] update id token verification

---
 app/controllers/users/omniauth_callbacks_controller.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index ad37e3f73..1b88c307d 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -51,8 +51,8 @@ def valid_tokens?(tokens_response)
   def valid_id_token?(id_token)
     id_token.present? &&
       id_token['nonce'] == session[:gov_one_auth_nonce] &&
-      id_token['iss'] == ENV['GOV_ONE_ISSUER'] &&
-      id_token['aud'] == ENV['GOV_ONE_CLIENT_ID']
+      id_token['iss'] == Rails.application.config.gov_one_base_uri + "/" &&
+      id_token['aud'] == Rails.application.config.gov_one_client_id
   end
 
   # @param user_info_response [Hash]

From 7154861d51c9c0ce6a8f38eb48f53895f883689d Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 18 Dec 2023 10:36:34 +0000
Subject: [PATCH 109/122] rubocop

---
 app/controllers/users/omniauth_callbacks_controller.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index 1b88c307d..a39506be8 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -51,7 +51,7 @@ def valid_tokens?(tokens_response)
   def valid_id_token?(id_token)
     id_token.present? &&
       id_token['nonce'] == session[:gov_one_auth_nonce] &&
-      id_token['iss'] == Rails.application.config.gov_one_base_uri + "/" &&
+      id_token['iss'] == "#{Rails.application.config.gov_one_base_uri}/" &&
       id_token['aud'] == Rails.application.config.gov_one_client_id
   end
 

From d10cee518c9567d82450f76b787dbff077b9c53d Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 18 Dec 2023 11:31:36 +0000
Subject: [PATCH 110/122] update spec

---
 spec/controllers/users/omniauth_callbacks_controller_spec.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/controllers/users/omniauth_callbacks_controller_spec.rb b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
index c9863c1dc..47eb35f58 100644
--- a/spec/controllers/users/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/users/omniauth_callbacks_controller_spec.rb
@@ -4,7 +4,7 @@
   let(:auth_service) { instance_double(GovOneAuthService) }
   let(:access_token) { 'mock_access_token' }
   let(:id_token) { 'mock_id_token' }
-  let(:decoded_id_token) { { 'sub' => 'mock_sub', 'nonce' => 'mock_nonce', 'iss' => ENV['GOV_ONE_ISSUER'], 'aud' => ENV['GOV_ONE_CLIENT_ID'] } }
+  let(:decoded_id_token) { { 'sub' => 'mock_sub', 'nonce' => 'mock_nonce', 'iss' => "#{Rails.application.config.gov_one_base_uri}/", 'aud' => Rails.application.config.gov_one_client_id } }
   let(:email) { 'test@example.com' }
   let(:params) do
     { 'code' => 'mock_code', 'state' => 'mock_state' }

From ba70feee924a369d3cb522fc5610e775f6ebbf64 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 18 Dec 2023 15:11:31 +0000
Subject: [PATCH 111/122] add feature flag to homepage changes

---
 app/assets/stylesheets/application.scss | 12 ++++
 app/views/home/index.html.slim          | 77 ++++++++++++++++++-------
 2 files changed, 67 insertions(+), 22 deletions(-)

diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index d7937cd2f..a614a139e 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -135,6 +135,14 @@ pre.code-tip {
     margin-bottom: govuk-spacing(6);
   }
 
+  &.enrol-box {
+    border-radius: 6px;
+
+    .govuk-button {
+      margin: 0;
+    }
+  }
+
 }
 
 .float-right {
@@ -183,6 +191,10 @@ ul>li>ul>li {
   display: none;
 }
 
+.white-space-pre-wrap {
+  white-space: pre-wrap;
+}
+
 #available h2 .govuk-tag {
   margin-left: govuk-spacing(1);
 }
diff --git a/app/views/home/index.html.slim b/app/views/home/index.html.slim
index 3cc7f73e2..e8ebba061 100644
--- a/app/views/home/index.html.slim
+++ b/app/views/home/index.html.slim
@@ -1,27 +1,60 @@
-- content_for :page_title do
-  = html_title t('home.title')
+- if Rails.application.gov_one_login?
+  - content_for :page_title do
+    = html_title t('home.title')
 
-- content_for :hero do
-  = render 'hero'
+  - content_for :hero do
+    = render 'hero'
 
-= render 'learning/cms_debug'
-= render 'debug'
+  = render 'learning/cms_debug'
+  = render 'debug'
 
-.govuk-grid-row
-  .govuk-grid-column-one-half
-    = m('home.about', headings_start_with: 'xl')
+  .govuk-grid-row
+    .govuk-grid-column-one-half
+      = m('home.about', headings_start_with: 'xl')
+
+  .prompt.prompt-home
+    .govuk-grid-row
+      .govuk-grid-column-one-quarter
+        i.fa-2x.fa-solid.fa-circle-info aria-describedby='info icon'
+
+      .govuk-grid-column-three-quarters
+        = m('home.prompt', headings_start_with: 'xl')
+
+  - unless current_user
+    .govuk-grid-row class="govuk-!-margin-top-9"
+      .govuk-grid-column-full
+        = govuk_button_link_to gov_one_info_path, class: "govuk-button--start" do
+          | #{t('home.gov_one_button')}
+          = render 'chevron'
+
+- else
+  - content_for :page_title do
+    = html_title t('home.title')
+
+  - content_for :hero do
+    = render 'hero'
+
+  = render 'learning/cms_debug'
+  = render 'debug'
 
-.prompt.prompt-home
   .govuk-grid-row
-    .govuk-grid-column-one-quarter
-      i.fa-2x.fa-solid.fa-circle-info aria-describedby='info icon'
-
-    .govuk-grid-column-three-quarters
-      = m('home.prompt', headings_start_with: 'xl')
-
-- unless current_user
-  .govuk-grid-row class="govuk-!-margin-top-9"
-    .govuk-grid-column-full
-      = govuk_button_link_to gov_one_info_path, class: "govuk-button--start" do
-        | #{t('home.gov_one_button')}
-        = render 'chevron'
+    .govuk-grid-column-one-half
+      = m('home.about', headings_start_with: 'xl')
+
+    - unless current_user
+      .govuk-grid-column-one-half
+        .light-grey-box.enrol-box
+          = m('home.login', headings_start_with: 'xl')
+
+          .govuk-button-group
+            = govuk_button_link_to 'Sign in', new_user_session_path
+            .white-space-pre-wrap= ' or '
+            = govuk_link_to 'create an account', new_user_registration_path
+
+  .prompt.prompt-home
+    .govuk-grid-row
+      .govuk-grid-column-one-quarter
+        i.fa-2x.fa-solid.fa-circle-info aria-describedby='info icon'
+
+      .govuk-grid-column-three-quarters
+        = m('home.prompt', headings_start_with: 'xl')

From 6b7bcc4139939a722c56e1048b379f462137671a Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 18 Dec 2023 15:21:44 +0000
Subject: [PATCH 112/122] revert changes to front_page_spec

---
 spec/system/front_page_spec.rb | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/spec/system/front_page_spec.rb b/spec/system/front_page_spec.rb
index 9216e8620..131f4c5b3 100644
--- a/spec/system/front_page_spec.rb
+++ b/spec/system/front_page_spec.rb
@@ -7,16 +7,16 @@
     it 'logged in content' do
       visit '/'
       expect(page).to have_text 'Learn more'
-      expect(page).not_to have_text 'Learn more about this training'
-      expect(page).not_to have_text 'Start your training now'
+      expect(page).not_to have_text 'Learn more and enrol'
+      expect(page).not_to have_text 'Sign in to continue learning'
     end
   end
 
   context 'with an unauthenticated visitor' do
     it 'log in content' do
       visit '/'
-      expect(page).to have_text('Learn more about this training')
-        .and have_text('Start your training now')
+      expect(page).to have_text('Learn more and enrol')
+        .and have_text('Sign in to continue learning')
     end
   end
-end
+end
\ No newline at end of file

From 273096c590ab14072efd44b061ed8b7ed4fcb24d Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Mon, 18 Dec 2023 15:51:03 +0000
Subject: [PATCH 113/122] add feature flag to homepage hero

---
 app/views/home/_hero.html.slim | 22 ++++++++++++++++------
 spec/system/front_page_spec.rb |  2 +-
 2 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/app/views/home/_hero.html.slim b/app/views/home/_hero.html.slim
index a148caac1..a67002d9c 100644
--- a/app/views/home/_hero.html.slim
+++ b/app/views/home/_hero.html.slim
@@ -7,12 +7,22 @@
     p.govuk-body-l
       = t('home.hero')
 
-    = link_to course_overview_path, class: 'govuk-!-margin-bottom-4 govuk-link--no-visited-state govuk-!-font-weight-bold govuk-body' do
-      - if current_user
-        | Learn more
-      - else
-        | Learn more about this training
-        p.govuk-visually-hidden on the course
+    - if Rails.application.gov_one_login?
+      = link_to course_overview_path, class: 'govuk-!-margin-bottom-4 govuk-link--no-visited-state govuk-!-font-weight-bold govuk-body' do
+        - if current_user
+          | Learn more
+        - else
+          | Learn more about this training
+          p.govuk-visually-hidden on the course
+    - else
+      = govuk_button_link_to course_overview_path, class: 'govuk-button--start govuk-!-margin-bottom-4' do
+        - if current_user
+          | Learn more
+        - else
+          | Learn more and enrol
+          p.govuk-visually-hidden on the course
+
+        = render 'chevron'
 
   .govuk-grid-column-one-third class='govuk-!-text-align-right'
     = m('home.thumb')
\ No newline at end of file
diff --git a/spec/system/front_page_spec.rb b/spec/system/front_page_spec.rb
index 131f4c5b3..b72275cc3 100644
--- a/spec/system/front_page_spec.rb
+++ b/spec/system/front_page_spec.rb
@@ -19,4 +19,4 @@
         .and have_text('Sign in to continue learning')
     end
   end
-end
\ No newline at end of file
+end

From 143491ee615d17531f88be35c67b9fde3b9c2ede Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Tue, 19 Dec 2023 16:40:11 +0000
Subject: [PATCH 114/122] add error checks for gov one login auth responses

---
 .../users/omniauth_callbacks_controller.rb       | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb
index a39506be8..e74c107c4 100644
--- a/app/controllers/users/omniauth_callbacks_controller.rb
+++ b/app/controllers/users/omniauth_callbacks_controller.rb
@@ -5,6 +5,11 @@ class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
   # This method is called by Devise after successful Gov One Login authentication
   # @return [nil]
   def openid_connect
+    if params['error'].present?
+      Rails.logger.error("Authentication error: #{params['error']}, #{params['error_description']}")
+      return error_redirect
+    end
+
     return error_redirect unless session_params? && valid_params?
 
     auth_service = GovOneAuthService.new(code: params['code'])
@@ -34,7 +39,6 @@ def valid_params?
     params['code'].present? && params['state'].present? && params['state'] == session[:gov_one_auth_state]
   end
 
-  # check state and nonce are saved in session
   # @return [Boolean]
   def session_params?
     session[:gov_one_auth_state].present? && session[:gov_one_auth_nonce].present?
@@ -43,7 +47,10 @@ def session_params?
   # @param tokens_response [Hash]
   # @return [Boolean]
   def valid_tokens?(tokens_response)
-    tokens_response.present? && tokens_response['access_token'].present? && tokens_response['id_token'].present?
+    tokens_response.present? &&
+      tokens_response['access_token'].present? &&
+      tokens_response['id_token'].present? &&
+      tokens_response['error'].blank?
   end
 
   # @param id_token [Hash]
@@ -58,7 +65,10 @@ def valid_id_token?(id_token)
   # @param user_info_response [Hash]
   # @return [Boolean]
   def valid_user_info?(user_info_response, gov_one_id)
-    user_info_response.present? && user_info_response['email'].present? && user_info_response['sub'] == gov_one_id
+    user_info_response.present? &&
+      user_info_response['email'].present? &&
+      user_info_response['sub'] == gov_one_id &&
+      user_info_response['error'].blank?
   end
 
   # @return [nil]

From c49d448979b69ea6e1d3b4240eb9fbcffee6be31 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 20 Dec 2023 13:52:07 +0000
Subject: [PATCH 115/122] add markdown file for gov one adr and tech checklist

---
 gov_one_login/GOV-ONE-LOGIN.md | 57 ++++++++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)
 create mode 100644 gov_one_login/GOV-ONE-LOGIN.md

diff --git a/gov_one_login/GOV-ONE-LOGIN.md b/gov_one_login/GOV-ONE-LOGIN.md
new file mode 100644
index 000000000..00bad6797
--- /dev/null
+++ b/gov_one_login/GOV-ONE-LOGIN.md
@@ -0,0 +1,57 @@
+# GOV.UK One Login
+
+## ADR
+### Context
+The integration of GOV.UK One Login user authentication is a requirement of the service going live. This single sign on will allow users to login to the service using their GOV.UK One Login account.
+
+### Decision
+We have decided to use the `omniauth_openid_connect` gem. This decision was based on the fact that `omniauth_openid_connect` is an off-the-shelf OIDC library as was recommended by GOV.UK One Login, it integrates well with Devise (which we are already using for user authentication) and it has a good level of community support. Auth URIs and client secrets are stored in Rails credentials files which is a secure way of storing sensitive information and allows us to use different credentials for different environments.
+
+### Consequences
+By using the `omniauth_openid_connect` gem we have been able to implement the GOV.UK One Login user authentication in a way that is consistent with the rest of the application and has allowed us to use the existing Devise functionality that we have already implemented for user authentication. 
+
+
+## Checklist
+
+### Authentication request requirements
+| Requirement | Response |
+| --- | --- |
+| __Describe how you’re using the state parameter to prevent CSRF attacks__ | The state is generated as a random uuid, which is then stored in the Rails session storage. The state parameter for authorisation request responses must match the state stored in the session before the user is authenticated. 
+| __Describe how you’re generating the nonce parameter__ | The nonce is a randomly generated alphanumeric string of 25 characters, it is used to verify the `id_token`. |
+| __Describe how you handle authorise endpoint errors__  | The errors are logged and the user is redirected to the homepage with an alert informing them of a problem  |
+| __Describe how you’re handling access_denied errors where session state is also missing__ | The user is redirected to the homepage with an alert informing them of a problem and encouraging them to try again |
+
+### Token request requirements 
+| Requirement | Response |
+| --- | --- |
+| __Describe how you ensure that your client secret / private key is not exposed to unauthorised parties__ | These are encrypted and stored in Rails credentials |
+| __For the private_key_jwt confirm that each jti claim value in the JWT assertion is used once.__ | <input type="checkbox" checked> |
+
+### Token validation requirements 
+| Requirement | Response |
+| --- | --- |
+| __Confirm you validate the iss claim is https://oidc.account.gov.uk/__ | <input type="checkbox" checked> |
+| __Confirm you validate the aud claim matches your client_id__ | <input type="checkbox" checked> |
+| __Confirm you validate the nonce claim matches the your application generated__ | <input type="checkbox" checked> |
+| __Confirm you validate the current time is before the time in the exp claim__  | <input type="checkbox" checked> |
+| __Confirm you validate the current time is between the time in the auth_time claim and the exp claim__ | <input type="checkbox" checked> |
+| __Confirm you validate the signature on the id-token__ | <input type="checkbox" checked> |
+| __Describe how you handle token endpoint errors__ | The error is logged and the user is redirected to the homepage with an alert informing them of a problem  |
+| __Describe how you ensure that the GOV.UK One Login Access Token is not exposed to unauthorised parties outside of your trusted backend server__  | The access token is not exposed to the user and is only used to make requests to the UserInfo endpoint during the user session. Communication with GOV.UK One Login is over HTTPS. |
+
+### UserInfo request requirements  
+| Requirement | Response |
+| --- | --- |
+| __Confirm you validate the sub claim in the UserInfo response matches the id-token sub claim__  | <input type="checkbox" checked> |
+| __Describe how you handle UserInfo endpoint errors__ | The error is logged and the user is redirected to the homepage with an alert informing them of a problem |
+| __If you’re using the email address scope, confirm that you’re aware this represents the GOV.UK One Login username and may not be the user’s preferred contact email address__ | <input type="checkbox" checked> |
+
+### Key management requirements 
+| Requirement | Response |
+| --- | --- |
+| __If using the GOV.UK One Login OpenID Provider JWKS Endpoint for signature validation describe your approach to key rotation__ | The keys are cached and the cache expires every 24 hours |
+
+### Session management requirements  
+| Requirement | Response |
+| --- | --- |
+| __Confirm that you’ve implemented logout functionality and that your service calls the GOV.UK One Login logout endpoint__ | <input type="checkbox" checked> |

From f85fd2b101ae7b916a0ee61643405424b8769004 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 20 Dec 2023 14:04:53 +0000
Subject: [PATCH 116/122] update markdown checkmark for github

---
 gov_one_login/GOV-ONE-LOGIN.md | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/gov_one_login/GOV-ONE-LOGIN.md b/gov_one_login/GOV-ONE-LOGIN.md
index 00bad6797..cfd55f7c9 100644
--- a/gov_one_login/GOV-ONE-LOGIN.md
+++ b/gov_one_login/GOV-ONE-LOGIN.md
@@ -25,26 +25,26 @@ By using the `omniauth_openid_connect` gem we have been able to implement the GO
 | Requirement | Response |
 | --- | --- |
 | __Describe how you ensure that your client secret / private key is not exposed to unauthorised parties__ | These are encrypted and stored in Rails credentials |
-| __For the private_key_jwt confirm that each jti claim value in the JWT assertion is used once.__ | <input type="checkbox" checked> |
+| __For the private_key_jwt confirm that each jti claim value in the JWT assertion is used once.__ | [x] |
 
 ### Token validation requirements 
 | Requirement | Response |
 | --- | --- |
-| __Confirm you validate the iss claim is https://oidc.account.gov.uk/__ | <input type="checkbox" checked> |
-| __Confirm you validate the aud claim matches your client_id__ | <input type="checkbox" checked> |
-| __Confirm you validate the nonce claim matches the your application generated__ | <input type="checkbox" checked> |
-| __Confirm you validate the current time is before the time in the exp claim__  | <input type="checkbox" checked> |
-| __Confirm you validate the current time is between the time in the auth_time claim and the exp claim__ | <input type="checkbox" checked> |
-| __Confirm you validate the signature on the id-token__ | <input type="checkbox" checked> |
+| __Confirm you validate the iss claim is https://oidc.account.gov.uk/__ | [x] |
+| __Confirm you validate the aud claim matches your client_id__ | [x] |
+| __Confirm you validate the nonce claim matches the your application generated__ | [x] |
+| __Confirm you validate the current time is before the time in the exp claim__  | [x] |
+| __Confirm you validate the current time is between the time in the auth_time claim and the exp claim__ | [x] |
+| __Confirm you validate the signature on the id-token__ | [x] |
 | __Describe how you handle token endpoint errors__ | The error is logged and the user is redirected to the homepage with an alert informing them of a problem  |
 | __Describe how you ensure that the GOV.UK One Login Access Token is not exposed to unauthorised parties outside of your trusted backend server__  | The access token is not exposed to the user and is only used to make requests to the UserInfo endpoint during the user session. Communication with GOV.UK One Login is over HTTPS. |
 
 ### UserInfo request requirements  
 | Requirement | Response |
 | --- | --- |
-| __Confirm you validate the sub claim in the UserInfo response matches the id-token sub claim__  | <input type="checkbox" checked> |
+| __Confirm you validate the sub claim in the UserInfo response matches the id-token sub claim__  | [x] |
 | __Describe how you handle UserInfo endpoint errors__ | The error is logged and the user is redirected to the homepage with an alert informing them of a problem |
-| __If you’re using the email address scope, confirm that you’re aware this represents the GOV.UK One Login username and may not be the user’s preferred contact email address__ | <input type="checkbox" checked> |
+| __If you’re using the email address scope, confirm that you’re aware this represents the GOV.UK One Login username and may not be the user’s preferred contact email address__ | [x] |
 
 ### Key management requirements 
 | Requirement | Response |
@@ -54,4 +54,4 @@ By using the `omniauth_openid_connect` gem we have been able to implement the GO
 ### Session management requirements  
 | Requirement | Response |
 | --- | --- |
-| __Confirm that you’ve implemented logout functionality and that your service calls the GOV.UK One Login logout endpoint__ | <input type="checkbox" checked> |
+| __Confirm that you’ve implemented logout functionality and that your service calls the GOV.UK One Login logout endpoint__ | [x] |

From ae48ab92cbad1b3d8b066cb6eddd19c4b51c8969 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 20 Dec 2023 15:38:10 +0000
Subject: [PATCH 117/122] update one login info markdown and add adr

---
 .yardopts                      |  1 +
 adr/0017-gov-one-login.md      | 19 +++++++++++++++++
 adr/ADR.md                     |  3 ++-
 gov_one_login/GOV-ONE-LOGIN.md | 37 ++++++++++++++++------------------
 4 files changed, 39 insertions(+), 21 deletions(-)
 create mode 100644 adr/0017-gov-one-login.md

diff --git a/.yardopts b/.yardopts
index 72d6af40f..6f31f194c 100644
--- a/.yardopts
+++ b/.yardopts
@@ -5,6 +5,7 @@
 --markup markdown
 -
 cms/*.md
+gov_one_login/*.md
 data/*.md
 uml/*.md
 adr/*.md
diff --git a/adr/0017-gov-one-login.md b/adr/0017-gov-one-login.md
new file mode 100644
index 000000000..951b07c06
--- /dev/null
+++ b/adr/0017-gov-one-login.md
@@ -0,0 +1,19 @@
+# GOV.UK One Login
+
+* Status: accepted
+
+## Context and Problem Statement
+The integration of GOV.UK One Login user authentication is a requirement of the service going live. This single sign on will allow users to login to the service using their GOV.UK One Login account.
+
+## Decision Drivers
+* GOV.UK One Login reccomends using an off-the-shelf OIDC library
+* We currently use Devise for user authentication
+* Omniauth would allow us to use Devise and integrate with GOV.UK One Login
+
+## Considered Options
+* [omniauth](https://github.com/omniauth/omniauth)
+* [omniauth_openid_connect](https://github.com/omniauth/omniauth_openid_connect)
+
+## Decision Outcome
+Chosen option: [omniauth_openid_connect](https://github.com/omniauth/omniauth_openid_connect)
+
diff --git a/adr/ADR.md b/adr/ADR.md
index fd3badbf9..2c19dfed9 100644
--- a/adr/ADR.md
+++ b/adr/ADR.md
@@ -20,7 +20,8 @@ This log lists the architectural decisions for EYFS Recovery
 * [ADR-0013](0013-sensitive-data-encryption.md) - Use Active Record Encryption to protect sensitive data
 * [ADR-0014](0014-user-tracking.md) - Use Hotjar for tracking user journeys
 * [ADR-0015](0015-background-jobs.md) - Use Que for processing background jobs
-* [ADR-0016](0016-devise-security-and-pwned-passwords-gems.md) - Use Devise Security and Devise Pwned Password gems
+* [ADR-0016](0016-devise-security-and-pwned-password-gems.md) - Use gems Devise Security and Deviese Pwned Passwords
+* [ADR-0017](0017-gov-one-login.md) - GOV.UK One Login
 
 <!-- adrlogstop -->
 
diff --git a/gov_one_login/GOV-ONE-LOGIN.md b/gov_one_login/GOV-ONE-LOGIN.md
index cfd55f7c9..8d8a1e2c7 100644
--- a/gov_one_login/GOV-ONE-LOGIN.md
+++ b/gov_one_login/GOV-ONE-LOGIN.md
@@ -1,17 +1,14 @@
 # GOV.UK One Login
 
-## ADR
-### Context
-The integration of GOV.UK One Login user authentication is a requirement of the service going live. This single sign on will allow users to login to the service using their GOV.UK One Login account.
+## Integration Environment
+- __Base URI__: https://oidc.integration.account.gov.uk
+- __Redirect URLs__:
+    - http://localhost:3000/users/auth/openid_connect/callback
+- __Post-logout redirect URLs__:
+    - http://localhost:3000/users/sign_out
 
-### Decision
-We have decided to use the `omniauth_openid_connect` gem. This decision was based on the fact that `omniauth_openid_connect` is an off-the-shelf OIDC library as was recommended by GOV.UK One Login, it integrates well with Devise (which we are already using for user authentication) and it has a good level of community support. Auth URIs and client secrets are stored in Rails credentials files which is a secure way of storing sensitive information and allows us to use different credentials for different environments.
 
-### Consequences
-By using the `omniauth_openid_connect` gem we have been able to implement the GOV.UK One Login user authentication in a way that is consistent with the rest of the application and has allowed us to use the existing Devise functionality that we have already implemented for user authentication. 
-
-
-## Checklist
+## Technical Checklist
 
 ### Authentication request requirements
 | Requirement | Response |
@@ -25,26 +22,26 @@ By using the `omniauth_openid_connect` gem we have been able to implement the GO
 | Requirement | Response |
 | --- | --- |
 | __Describe how you ensure that your client secret / private key is not exposed to unauthorised parties__ | These are encrypted and stored in Rails credentials |
-| __For the private_key_jwt confirm that each jti claim value in the JWT assertion is used once.__ | [x] |
+| __For the private_key_jwt confirm that each jti claim value in the JWT assertion is used once.__ | &check; |
 
 ### Token validation requirements 
 | Requirement | Response |
 | --- | --- |
-| __Confirm you validate the iss claim is https://oidc.account.gov.uk/__ | [x] |
-| __Confirm you validate the aud claim matches your client_id__ | [x] |
-| __Confirm you validate the nonce claim matches the your application generated__ | [x] |
-| __Confirm you validate the current time is before the time in the exp claim__  | [x] |
-| __Confirm you validate the current time is between the time in the auth_time claim and the exp claim__ | [x] |
-| __Confirm you validate the signature on the id-token__ | [x] |
+| __Confirm you validate the iss claim is https://oidc.account.gov.uk/__ | &check; |
+| __Confirm you validate the aud claim matches your client_id__ | &check; |
+| __Confirm you validate the nonce claim matches the your application generated__ | &check; |
+| __Confirm you validate the current time is before the time in the exp claim__  | &check; |
+| __Confirm you validate the current time is between the time in the auth_time claim and the exp claim__ | &check; |
+| __Confirm you validate the signature on the id-token__ | &check; |
 | __Describe how you handle token endpoint errors__ | The error is logged and the user is redirected to the homepage with an alert informing them of a problem  |
 | __Describe how you ensure that the GOV.UK One Login Access Token is not exposed to unauthorised parties outside of your trusted backend server__  | The access token is not exposed to the user and is only used to make requests to the UserInfo endpoint during the user session. Communication with GOV.UK One Login is over HTTPS. |
 
 ### UserInfo request requirements  
 | Requirement | Response |
 | --- | --- |
-| __Confirm you validate the sub claim in the UserInfo response matches the id-token sub claim__  | [x] |
+| __Confirm you validate the sub claim in the UserInfo response matches the id-token sub claim__  | &check; |
 | __Describe how you handle UserInfo endpoint errors__ | The error is logged and the user is redirected to the homepage with an alert informing them of a problem |
-| __If you’re using the email address scope, confirm that you’re aware this represents the GOV.UK One Login username and may not be the user’s preferred contact email address__ | [x] |
+| __If you’re using the email address scope, confirm that you’re aware this represents the GOV.UK One Login username and may not be the user’s preferred contact email address__ | &check; |
 
 ### Key management requirements 
 | Requirement | Response |
@@ -54,4 +51,4 @@ By using the `omniauth_openid_connect` gem we have been able to implement the GO
 ### Session management requirements  
 | Requirement | Response |
 | --- | --- |
-| __Confirm that you’ve implemented logout functionality and that your service calls the GOV.UK One Login logout endpoint__ | [x] |
+| __Confirm that you’ve implemented logout functionality and that your service calls the GOV.UK One Login logout endpoint__ | &check; |

From e4bc64ab4c532c1f918177403b261602ae9daec5 Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 20 Dec 2023 16:20:00 +0000
Subject: [PATCH 118/122] update sitemap

---
 config/sitemap.rb | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/config/sitemap.rb b/config/sitemap.rb
index 733b813ea..9641548b6 100644
--- a/config/sitemap.rb
+++ b/config/sitemap.rb
@@ -68,6 +68,9 @@
   # account
   add user_path
 
+  # GOV.UK one login
+  add gov_one_info_path
+
   # edit registration/account
   add edit_email_user_path
   add edit_password_user_path

From 9bd4c17db8db4c18a5ac02c14603efeb537fe85e Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 20 Dec 2023 16:23:30 +0000
Subject: [PATCH 119/122] fix typo

---
 adr/ADR.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/adr/ADR.md b/adr/ADR.md
index 2c19dfed9..646f0d746 100644
--- a/adr/ADR.md
+++ b/adr/ADR.md
@@ -20,7 +20,7 @@ This log lists the architectural decisions for EYFS Recovery
 * [ADR-0013](0013-sensitive-data-encryption.md) - Use Active Record Encryption to protect sensitive data
 * [ADR-0014](0014-user-tracking.md) - Use Hotjar for tracking user journeys
 * [ADR-0015](0015-background-jobs.md) - Use Que for processing background jobs
-* [ADR-0016](0016-devise-security-and-pwned-password-gems.md) - Use gems Devise Security and Deviese Pwned Passwords
+* [ADR-0016](0016-devise-security-and-pwned-password-gems.md) - Use gems Devise Security and Devise Pwned Passwords
 * [ADR-0017](0017-gov-one-login.md) - GOV.UK One Login
 
 <!-- adrlogstop -->

From 40f12adb6700d723aa7be0445f5346dd8ac0a41e Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Wed, 20 Dec 2023 16:24:48 +0000
Subject: [PATCH 120/122] revert unwanted adr changes

---
 adr/ADR.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/adr/ADR.md b/adr/ADR.md
index 646f0d746..f36b4cf60 100644
--- a/adr/ADR.md
+++ b/adr/ADR.md
@@ -20,7 +20,7 @@ This log lists the architectural decisions for EYFS Recovery
 * [ADR-0013](0013-sensitive-data-encryption.md) - Use Active Record Encryption to protect sensitive data
 * [ADR-0014](0014-user-tracking.md) - Use Hotjar for tracking user journeys
 * [ADR-0015](0015-background-jobs.md) - Use Que for processing background jobs
-* [ADR-0016](0016-devise-security-and-pwned-password-gems.md) - Use gems Devise Security and Devise Pwned Passwords
+* [ADR-0016](0016-devise-security-and-pwned-passwords-gems.md) - Use Devise Security and Devise Pwned Password gems
 * [ADR-0017](0017-gov-one-login.md) - GOV.UK One Login
 
 <!-- adrlogstop -->

From eb412893ac02c031cc66151b862e9094a8907a5d Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 21 Dec 2023 11:30:08 +0000
Subject: [PATCH 121/122] update seed snippets spec locales

---
 spec/lib/seed_snippets_spec.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/spec/lib/seed_snippets_spec.rb b/spec/lib/seed_snippets_spec.rb
index 80ef91229..eafcf2af5 100644
--- a/spec/lib/seed_snippets_spec.rb
+++ b/spec/lib/seed_snippets_spec.rb
@@ -5,7 +5,7 @@
   subject(:locales) { described_class.new.call }
 
   it 'converts all translations' do
-    expect(locales.count).to be 201
+    expect(locales.count).to be 204
   end
 
   it 'dot separated key -> Page::Resource#name' do

From 27fb9b145db68bdd896fd1068d6bc9ea5d5304cc Mon Sep 17 00:00:00 2001
From: "jack.coggin" <jack.coggin@and.digital>
Date: Thu, 21 Dec 2023 14:18:55 +0000
Subject: [PATCH 122/122] put domain back to app in docker compose

---
 docker-compose.dev.yml | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
index 11001e395..7287b9819 100644
--- a/docker-compose.dev.yml
+++ b/docker-compose.dev.yml
@@ -19,8 +19,7 @@ services:
       # app config
       - DATABASE_URL=postgres://postgres:password@db:5432/early_years_foundation_recovery_development
       - RAILS_ENV=development
-      # - DOMAIN=app:3000
-      - DOMAIN=localhost:3000
+      - DOMAIN=app:3000
       - RAILS_SERVE_STATIC_FILES=true
     volumes:
       - .:/srv