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
4 changes: 2 additions & 2 deletions app/controllers/api/verify/complete_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
12 changes: 11 additions & 1 deletion app/controllers/verify_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Separately, we should start thinking about how to only include the user bundle and remove personalKey. That being said, we'll probably still need it as long as personal key is the only enabled step, if we ship it before others.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yup, eventually we won't even need the bundle - this is all temporary.

'userBundleToken' => user_bundle_token,
},
enabled_step_names: IdentityConfig.store.idv_api_enabled_steps,
store_key: user_session[:idv_api_store_key],
}
Expand Down Expand Up @@ -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
18 changes: 18 additions & 0 deletions app/javascript/packages/verify-flow/verify-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
14 changes: 14 additions & 0 deletions app/javascript/packs/verify-flow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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]),
);
Comment on lines 59 to 65
Copy link
Copy Markdown
Contributor

@aduth aduth May 5, 2022

Choose a reason for hiding this comment

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

As discussed in our working session, really tempting to pull in Lodash here:

Suggested change
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]),
);
import { mapKeys, camelCase } from 'lodash-es';
const jwtData = JSON.parse(atob(initialValues.userBundleToken.split('.')[1]));
const pii = mapKeys(jwtData.pii, camelCase);

Object.assign(initialValues, pii);

function onComplete() {
window.location.href = completionURL;
}
Expand Down
40 changes: 40 additions & 0 deletions app/services/idv/user_bundle_tokenizer.rb
Original file line number Diff line number Diff line change
@@ -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
8 changes: 4 additions & 4 deletions spec/controllers/api/verify/complete_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand All @@ -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"
Expand Down
39 changes: 39 additions & 0 deletions spec/services/idv/user_bundle_tokenizer_spec.rb
Original file line number Diff line number Diff line change
@@ -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