diff --git a/app/controllers/idv/review_controller.rb b/app/controllers/idv/review_controller.rb index 8688e2ac340..eb304dde471 100644 --- a/app/controllers/idv/review_controller.rb +++ b/app/controllers/idv/review_controller.rb @@ -115,7 +115,7 @@ def need_personal_key_confirmation? def next_step if idv_api_personal_key_step_enabled? - idv_app_root_url + idv_app_url else idv_personal_key_url end diff --git a/app/controllers/verify_controller.rb b/app/controllers/verify_controller.rb index 68cff80cc46..1f5e12e6632 100644 --- a/app/controllers/verify_controller.rb +++ b/app/controllers/verify_controller.rb @@ -2,11 +2,13 @@ class VerifyController < ApplicationController include RenderConditionConcern include IdvSession + check_or_render_not_found -> { FeatureManagement.idv_api_enabled? }, only: [:show] + + before_action :redirect_root_path_to_first_step + before_action :validate_step before_action :confirm_two_factor_authenticated before_action :confirm_idv_vendor_session_started - before_action :confirm_profile_has_been_created - - check_or_render_not_found -> { FeatureManagement.idv_api_enabled? }, only: [:show] + before_action :confirm_profile_has_been_created, if: :first_step_is_personal_key? def show @app_data = app_data @@ -14,22 +16,52 @@ def show private + def redirect_root_path_to_first_step + redirect_to idv_app_path(step: first_step) if params[:step].blank? + end + + def validate_step + render_not_found if !enabled_steps.include?(params[:step]) + end + def app_data user_session[:idv_api_store_key] ||= Base64.strict_encode64(random_encryption_key) { - base_path: idv_app_root_path, + base_path: idv_app_path, app_name: APP_NAME, completion_url: completion_url, - initial_values: { - 'personalKey' => personal_key, - 'userBundleToken' => user_bundle_token, - }, + initial_values: initial_values, enabled_step_names: IdentityConfig.store.idv_api_enabled_steps, store_key: user_session[:idv_api_store_key], } end + def initial_values + case first_step + when 'password_confirm' + { 'userBundleToken' => user_bundle_token } + when 'personal_key' + { 'personalKey' => personal_key } + end + end + + def first_step + enabled_steps.detect { |step| step_enabled?(step) } + end + + def first_step_is_personal_key? + first_step == 'personal_key' + end + + def enabled_steps + IdentityConfig.store.idv_api_enabled_steps + end + + def step_enabled?(step) + enabled_steps.include?(step) + end + def random_encryption_key Encryption::AesCipher.encryption_cipher.random_key end diff --git a/app/javascript/packages/verify-flow/index.tsx b/app/javascript/packages/verify-flow/index.ts similarity index 68% rename from app/javascript/packages/verify-flow/index.tsx rename to app/javascript/packages/verify-flow/index.ts index d98e4e84e5d..a412251b0cd 100644 --- a/app/javascript/packages/verify-flow/index.tsx +++ b/app/javascript/packages/verify-flow/index.ts @@ -1,2 +1,4 @@ export { SecretsContextProvider } from './context/secrets-context'; export { default as VerifyFlow } from './verify-flow'; + +export type { VerifyFlowValues } from './verify-flow'; diff --git a/app/javascript/packages/verify-flow/verify-flow.tsx b/app/javascript/packages/verify-flow/verify-flow.tsx index d1598c49db1..f4097973926 100644 --- a/app/javascript/packages/verify-flow/verify-flow.tsx +++ b/app/javascript/packages/verify-flow/verify-flow.tsx @@ -6,6 +6,8 @@ import VerifyFlowStepIndicator from './verify-flow-step-indicator'; import VerifyFlowAlert from './verify-flow-alert'; export interface VerifyFlowValues { + userBundleToken?: string; + personalKey?: string; personalKeyConfirm?: string; diff --git a/app/javascript/packs/verify-flow.tsx b/app/javascript/packs/verify-flow.tsx index 2abeccff602..99c8e9c4576 100644 --- a/app/javascript/packs/verify-flow.tsx +++ b/app/javascript/packs/verify-flow.tsx @@ -1,6 +1,7 @@ import { render } from 'react-dom'; import { VerifyFlow, SecretsContextProvider } from '@18f/identity-verify-flow'; import { s2ab } from '@18f/identity-secret-session-storage'; +import type { VerifyFlowValues } from '@18f/identity-verify-flow'; interface AppRootValues { /** @@ -53,17 +54,19 @@ const { storeKey: storeKeyBase64, } = appRoot.dataset; const storeKey = s2ab(atob(storeKeyBase64)); -const initialValues = JSON.parse(initialValuesJSON); +const initialValues: Partial = JSON.parse(initialValuesJSON); const enabledStepNames = JSON.parse(enabledStepNamesJSON) as string[]; const camelCase = (string: string) => string.replace(/[^a-z]([a-z])/gi, (_match, nextLetter) => nextLetter.toUpperCase()); -const jwtData = JSON.parse(atob(initialValues.userBundleToken.split('.')[1])); -const pii = Object.fromEntries( - Object.entries(jwtData.pii).map(([key, value]) => [camelCase(key), value]), -); -Object.assign(initialValues, pii); +if (initialValues.userBundleToken) { + const jwtData = JSON.parse(atob(initialValues.userBundleToken.split('.')[1])); + const pii = Object.fromEntries( + Object.entries(jwtData.pii).map(([key, value]) => [camelCase(key), value]), + ); + Object.assign(initialValues, pii); +} function onComplete() { window.location.href = completionURL; diff --git a/config/routes.rb b/config/routes.rb index 007180c9423..05652b03c88 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -324,13 +324,7 @@ post '/confirmations' => 'personal_key#update' end - scope '/verify/v2' do - get '/' => 'verify#show', as: :idv_app_root - %w[ - /personal_key - /personal_key_confirm - ].each { |step_path| get step_path => 'verify#show' } - end + get '/verify/v2(/:step)' => 'verify#show', as: :idv_app namespace :api do post '/verify/complete' => 'verify/complete#create' diff --git a/spec/controllers/idv/review_controller_spec.rb b/spec/controllers/idv/review_controller_spec.rb index 8a15471b633..cd866e877b7 100644 --- a/spec/controllers/idv/review_controller_spec.rb +++ b/spec/controllers/idv/review_controller_spec.rb @@ -358,7 +358,7 @@ def show it 'redirects to idv app personal key path' do put :create, params: { user: { password: ControllerHelper::VALID_PASSWORD } } - expect(response).to redirect_to idv_app_root_url + expect(response).to redirect_to idv_app_url end end end diff --git a/spec/controllers/verify_controller_spec.rb b/spec/controllers/verify_controller_spec.rb index 7a46452d12b..c785a50ce87 100644 --- a/spec/controllers/verify_controller_spec.rb +++ b/spec/controllers/verify_controller_spec.rb @@ -2,6 +2,7 @@ describe VerifyController do describe '#show' do + let(:idv_api_enabled_steps) { [] } let(:password) { 'sekrit phrase' } let(:user) { create(:user, :signed_up, password: password) } let(:applicant) do @@ -16,37 +17,106 @@ } end let(:profile) { subject.idv_session.profile } + let(:step) { '' } - subject(:response) { get :show } + subject(:response) { get :show, params: { step: step } } before do - stub_sign_in + allow(IdentityConfig.store).to receive(:idv_api_enabled_steps). + and_return(idv_api_enabled_steps) + stub_sign_in(user) stub_idv_session end - context 'with step feature-disabled' do - before do - allow(IdentityConfig.store).to receive(:idv_api_enabled_steps).and_return([]) - end + it 'renders 404' do + expect(response).to be_not_found + end + + context 'with idv api enabled' do + let(:idv_api_enabled_steps) { ['something'] } + + context 'invalid step' do + let(:step) { 'bad' } - it 'renders 404' do - expect(response).to be_not_found + it 'renders 404' do + expect(response).to be_not_found + end end - end - context 'with step feature-enabled' do - before do - allow(IdentityConfig.store).to receive(:idv_api_enabled_steps). - and_return(['personal_key', 'personal_key_confirm']) + context 'with personal key step enabled' do + let(:idv_api_enabled_steps) { ['personal_key', 'personal_key_confirm'] } + let(:step) { 'personal_key' } + + before do + profile_maker = Idv::ProfileMaker.new( + applicant: applicant, + user: user, + user_password: password, + ) + profile = profile_maker.save_profile + controller.idv_session.pii = profile_maker.pii_attributes + controller.idv_session.profile_id = profile.id + controller.idv_session.personal_key = profile.personal_key + end + + it 'renders view' do + expect(response).to render_template(:show) + end + + it 'sets app data' do + response + + expect(assigns[:app_data]).to include( + app_name: APP_NAME, + base_path: idv_app_path, + completion_url: idv_gpo_verify_url, + enabled_step_names: idv_api_enabled_steps, + initial_values: { 'personalKey' => kind_of(String) }, + store_key: kind_of(String), + ) + end + + context 'empty step' do + let(:step) { nil } + + it 'redirects to first step' do + expect(response).to redirect_to idv_app_path(step: 'personal_key') + end + end end - it 'renders view' do - expect(response).to render_template(:show) + context 'with password confirmation step enabled' do + let(:idv_api_enabled_steps) { ['password_confirm', 'personal_key', 'personal_key_confirm'] } + let(:step) { 'password_confirm' } + + it 'renders view' do + expect(response).to render_template(:show) + end + + it 'sets app data' do + response + + expect(assigns[:app_data]).to include( + app_name: APP_NAME, + base_path: idv_app_path, + completion_url: account_url, + enabled_step_names: idv_api_enabled_steps, + initial_values: { 'userBundleToken' => kind_of(String) }, + store_key: kind_of(String), + ) + end + + context 'empty step' do + let(:step) { nil } + + it 'redirects to first step' do + expect(response).to redirect_to idv_app_path(step: 'password_confirm') + end + end end end def stub_idv_session - stub_sign_in(user) idv_session = Idv::Session.new( user_session: controller.user_session, current_user: user, @@ -54,15 +124,6 @@ def stub_idv_session ) idv_session.applicant = applicant idv_session.resolution_successful = true - profile_maker = Idv::ProfileMaker.new( - applicant: applicant, - user: user, - user_password: password, - ) - profile = profile_maker.save_profile - idv_session.pii = profile_maker.pii_attributes - idv_session.profile_id = profile.id - idv_session.personal_key = profile.personal_key allow(controller).to receive(:idv_session).and_return(idv_session) end end diff --git a/spec/features/accessibility/idv_pages_spec.rb b/spec/features/accessibility/idv_pages_spec.rb index 398b30c1225..07892a34d88 100644 --- a/spec/features/accessibility/idv_pages_spec.rb +++ b/spec/features/accessibility/idv_pages_spec.rb @@ -57,7 +57,7 @@ fill_in t('idv.form.password'), with: Features::SessionHelper::VALID_PASSWORD click_continue - expect(current_path).to be_in([idv_personal_key_path, idv_app_root_path]) + expect(current_path).to be_in([idv_personal_key_path, idv_app_path]) expect(page).to be_axe_clean.according_to :section508, :"best-practice", :wcag21aa expect(page).to label_required_fields expect(page).to be_uniquely_titled @@ -71,7 +71,7 @@ fill_in t('idv.form.password'), with: Features::SessionHelper::VALID_PASSWORD click_continue - expect(current_path).to be_in([idv_personal_key_path, idv_app_root_path]) + expect(current_path).to be_in([idv_personal_key_path, idv_app_path]) expect(page).to be_axe_clean.according_to :section508, :"best-practice", :wcag21aa expect(page).to label_required_fields expect(page).to be_uniquely_titled @@ -85,7 +85,7 @@ fill_in t('idv.form.password'), with: Features::SessionHelper::VALID_PASSWORD click_continue - expect(current_path).to be_in([idv_personal_key_path, idv_app_root_path]) + expect(current_path).to be_in([idv_personal_key_path, idv_app_path]) expect(page).to be_axe_clean.according_to :section508, :"best-practice", :wcag21aa expect(page).to label_required_fields expect(page).to be_uniquely_titled diff --git a/spec/features/idv/steps/review_step_spec.rb b/spec/features/idv/steps/review_step_spec.rb index 375d961e77b..fb89d595d37 100644 --- a/spec/features/idv/steps/review_step_spec.rb +++ b/spec/features/idv/steps/review_step_spec.rb @@ -33,7 +33,7 @@ click_idv_continue expect(page).to have_content(t('headings.personal_key')) - expect(current_path).to be_in([idv_personal_key_path, idv_app_root_path]) + expect(current_path).to be_in([idv_personal_key_path, idv_app_path]) end context 'choosing to confirm address with phone' do