diff --git a/app/controllers/api/verify/complete_controller.rb b/app/controllers/api/verify/complete_controller.rb index 8e7e08b0b3e..0cfd610a172 100644 --- a/app/controllers/api/verify/complete_controller.rb +++ b/app/controllers/api/verify/complete_controller.rb @@ -4,7 +4,7 @@ class CompleteController < Api::BaseController def create result, personal_key = Api::ProfileCreationForm.new( password: verify_params[:password], - jwt: verify_params[:details], + jwt: verify_params[:user_bundle_token], user_session: user_session, service_provider: current_sp, ).submit @@ -23,7 +23,7 @@ def create private def verify_params - params.permit(:password, :details) + params.permit(:password, :user_bundle_token) end def add_proofing_component(user) diff --git a/app/controllers/verify_controller.rb b/app/controllers/verify_controller.rb index 6bc019ecc27..68cff80cc46 100644 --- a/app/controllers/verify_controller.rb +++ b/app/controllers/verify_controller.rb @@ -21,7 +21,10 @@ def app_data base_path: idv_app_root_path, app_name: APP_NAME, completion_url: completion_url, - initial_values: { 'personalKey' => personal_key }, + initial_values: { + 'personalKey' => personal_key, + 'userBundleToken' => user_bundle_token, + }, enabled_step_names: IdentityConfig.store.idv_api_enabled_steps, store_key: user_session[:idv_api_store_key], } @@ -51,4 +54,11 @@ def completion_url after_sign_in_path_for(current_user) end end + + def user_bundle_token + Idv::UserBundleTokenizer.new( + user: current_user, + idv_session: idv_session, + ).token + end end diff --git a/app/javascript/packages/verify-flow/verify-flow.tsx b/app/javascript/packages/verify-flow/verify-flow.tsx index 54623ebdde7..88a18800b0d 100644 --- a/app/javascript/packages/verify-flow/verify-flow.tsx +++ b/app/javascript/packages/verify-flow/verify-flow.tsx @@ -10,6 +10,24 @@ export interface VerifyFlowValues { personalKey?: string; personalKeyConfirm?: string; + + firstName?: string; + + lastName?: string; + + address1?: string; + + address2?: string; + + city?: string; + + state?: string; + + zipcode?: string; + + phone?: string; + + ssn?: string; } interface VerifyFlowProps { diff --git a/app/javascript/packs/verify-flow.tsx b/app/javascript/packs/verify-flow.tsx index 495aad0f70e..2abeccff602 100644 --- a/app/javascript/packs/verify-flow.tsx +++ b/app/javascript/packs/verify-flow.tsx @@ -32,6 +32,11 @@ interface AppRootValues { * Base64-encoded encryption key for secret session store. */ storeKey: string; + + /** + * Signed JWT containing user data. + */ + userBundleToken: string; } interface AppRootElement extends HTMLElement { @@ -51,6 +56,15 @@ const storeKey = s2ab(atob(storeKeyBase64)); const initialValues = 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); + function onComplete() { window.location.href = completionURL; } diff --git a/app/services/idv/user_bundle_tokenizer.rb b/app/services/idv/user_bundle_tokenizer.rb new file mode 100644 index 00000000000..c077232f137 --- /dev/null +++ b/app/services/idv/user_bundle_tokenizer.rb @@ -0,0 +1,40 @@ +module Idv + class UserBundleTokenizer + def initialize(user:, idv_session:) + @user = user + @idv_session = idv_session + end + + def token + JWT.encode( + { + # for now, load whatever pii is saved in the session + pii: idv_session.applicant, + metadata: metadata, + }, + private_key, + 'RS256', + sub: user.uuid, + ) + end + + private + + attr_reader :user, :idv_session + + def private_key + OpenSSL::PKey::RSA.new(Base64.strict_decode64(IdentityConfig.store.idv_private_key)) + end + + def metadata + # populate with anything from the session we'll need later on + data = {} + + data[:address_verification_mechanism] = idv_session.address_verification_mechanism + data[:user_phone_confirmation] = idv_session.user_phone_confirmation + data[:vendor_phone_confirmation] = idv_session.vendor_phone_confirmation + + data + end + end +end diff --git a/spec/controllers/api/verify/complete_controller_spec.rb b/spec/controllers/api/verify/complete_controller_spec.rb index e5dd15f0900..ddb66b0a3d5 100644 --- a/spec/controllers/api/verify/complete_controller_spec.rb +++ b/spec/controllers/api/verify/complete_controller_spec.rb @@ -46,7 +46,7 @@ def stub_idv_session describe '#create' do context 'when the user is not signed in and submits the password' do it 'does not create a profile or return a key' do - post :create, params: { password: 'iambatman', details: jwt } + post :create, params: { password: 'iambatman', user_bundle_token: jwt } expect(JSON.parse(response.body)['personal_key']).to be_nil expect(response.status).to eq 401 expect(JSON.parse(response.body)['error']).to eq 'user is not fully authenticated' @@ -59,13 +59,13 @@ def stub_idv_session end it 'creates a profile and returns a key' do - post :create, params: { password: 'iambatman', details: jwt } + post :create, params: { password: 'iambatman', user_bundle_token: jwt } expect(JSON.parse(response.body)['personal_key']).not_to be_nil expect(response.status).to eq 200 end it 'does not create a profile and return a key when it has the wrong password' do - post :create, params: { password: 'iamnotbatman', details: jwt } + post :create, params: { password: 'iamnotbatman', user_bundle_token: jwt } expect(JSON.parse(response.body)['personal_key']).to be_nil expect(response.status).to eq 400 end @@ -77,7 +77,7 @@ def stub_idv_session end it 'responds with not found' do - post :create, params: { password: 'iambatman', details: jwt }, as: :json + post :create, params: { password: 'iambatman', user_bundle_token: jwt }, as: :json expect(response.status).to eq 404 expect(JSON.parse(response.body)['error']). to eq "The page you were looking for doesn't exist" diff --git a/spec/services/idv/user_bundle_tokenizer_spec.rb b/spec/services/idv/user_bundle_tokenizer_spec.rb new file mode 100644 index 00000000000..58678741f99 --- /dev/null +++ b/spec/services/idv/user_bundle_tokenizer_spec.rb @@ -0,0 +1,39 @@ +require 'rails_helper' + +RSpec.describe Idv::UserBundleTokenizer do + let(:public_key) do + OpenSSL::PKey::RSA.new(Base64.strict_decode64(IdentityConfig.store.idv_public_key)) + end + let(:user) { create(:user) } + let(:sp) { create(:service_provider) } + let(:user_session) do + { + idv: { + applicant: { + 'first_name' => 'Ada', + 'last_name' => 'Lovelace', + 'ssn' => '900900900', + 'phone' => '+1 410-555-1212', + }, + address_verification_mechanism: 'phone', + user_phone_confirmation: true, + vendor_phone_confirmation: true, + }, + } + end + let(:idv_session) do + Idv::Session.new(user_session: user_session, current_user: user, service_provider: sp) + end + subject do + Idv::UserBundleTokenizer.new(user: user, idv_session: idv_session) + end + + context 'when initialized with data' do + it 'encodes a signed JWT' do + token = subject.token + decorator = Api::UserBundleDecorator.new(user_bundle: token, public_key: public_key) + + expect(decorator.pii).to eq user_session[:idv][:applicant] + end + end +end