Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/controllers/idv/review_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 40 additions & 8 deletions app/controllers/verify_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,66 @@ 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
end

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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
export { SecretsContextProvider } from './context/secrets-context';
export { default as VerifyFlow } from './verify-flow';

export type { VerifyFlowValues } from './verify-flow';
2 changes: 2 additions & 0 deletions app/javascript/packages/verify-flow/verify-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
15 changes: 9 additions & 6 deletions app/javascript/packs/verify-flow.tsx
Original file line number Diff line number Diff line change
@@ -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 {
/**
Expand Down Expand Up @@ -53,17 +54,19 @@ const {
storeKey: storeKeyBase64,
} = appRoot.dataset;
const storeKey = s2ab(atob(storeKeyBase64));
const initialValues = JSON.parse(initialValuesJSON);
const initialValues: Partial<VerifyFlowValues> = 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;
Expand Down
8 changes: 1 addition & 7 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
2 changes: 1 addition & 1 deletion spec/controllers/idv/review_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
111 changes: 86 additions & 25 deletions spec/controllers/verify_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -16,53 +17,113 @@
}
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,
Copy link
Copy Markdown
Contributor Author

@aduth aduth May 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to rethink how we handle "completion URL", since it can be dynamic based on how the user proceeds through the flow. For example, if they opt to confirm address by mailed letter, the completion URL would be different than if they had verified by phone number.

Couple thoughts:

  • Move this handling into a controller whose route can be statically populated into the application, and whose action logic would determine where to redirect user
  • Change Personal Key step to submit to an endpoint which would be expected to respond with the redirect 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,
service_provider: nil,
)
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
Expand Down
6 changes: 3 additions & 3 deletions spec/features/accessibility/idv_pages_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion spec/features/idv/steps/review_step_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down