Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LG-538 New Document Authentication Design #2741

Merged
merged 11 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from 10 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: 2 additions & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ detectors:
- Idv::VendorResult
- CloudhsmKeyGenerator
- CloudhsmKeySharer
- UserMailer
- WebauthnSetupForm
- WebauthnVerificationForm
TooManyStatements:
Expand Down Expand Up @@ -166,6 +167,7 @@ detectors:
- SessionTimeoutWarningHelper#start
- SessionTimeoutWarningHelper#warning
- SessionDecorator
- SmsDocAuthLinkJob#perform
- UserEncryptedAttributeOverrides#create_fingerprint
- LocaleHelper#locale_url_param
- IdvSession#timed_out_vendor_error
Expand Down
Binary file added app/assets/images/idv/desktop.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/assets/images/idv/phone.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions app/decorators/identity_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ def return_to_sp_url
identity.sp_metadata[:return_to_sp_url]
end

def friendly_name
identity.sp_metadata[:friendly_name]
end

def created_at_in_words
UtcTimePresenter.new(created_at).to_s
end
Expand Down
17 changes: 17 additions & 0 deletions app/javascript/packs/image-preview.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import $ from 'jquery';

function imagePreview() {
$('#doc_auth_image').on('change', function(event) {
const files = event.target.files;
const image = files[0];
const reader = new FileReader();
reader.onload = function(file) {
const img = new Image();
img.src = file.target.result;
$('#target').html(img);
};
reader.readAsDataURL(image);
});
}

document.addEventListener('DOMContentLoaded', imagePreview);
16 changes: 16 additions & 0 deletions app/jobs/sms_doc_auth_link_job.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class SmsDocAuthLinkJob < ApplicationJob
queue_as :sms
include Rails.application.routes.url_helpers

def perform(phone:, link:, app:)
message = I18n.t(
'jobs.sms_doc_auth_link_job.message',
sp_link: link,
application: app,
)
TwilioService::Utils.new.send_sms(
to: phone,
body: message,
)
end
end
6 changes: 6 additions & 0 deletions app/mailers/user_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ def please_reset_password(email_address)
def undeliverable_address(email_address)
mail(to: email_address.email, subject: t('user_mailer.undeliverable_address.subject'))
end

def doc_auth_desktop_link_to_sp(email_address, application, link)
@link = link
@application = application
mail(to: email_address, subject: t('user_mailer.doc_auth_link.subject'))
end
end
5 changes: 3 additions & 2 deletions app/services/flow/base_flow.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Flow
class BaseFlow
attr_accessor :flow_session
attr_reader :steps, :actions, :current_user, :params
attr_reader :steps, :actions, :current_user, :params, :request

def initialize(steps, actions, session, current_user)
@current_user = current_user
Expand All @@ -18,11 +18,12 @@ def next_step
step
end

def handle(step, params)
def handle(step, request, params)
@flow_session[:error_message] = nil
handler = steps[step] || actions[step]
return failure("Unhandled step #{step}") unless handler
@params = params
@request = request
wrap_send(handler)
end

Expand Down
4 changes: 3 additions & 1 deletion app/services/flow/base_step.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module Flow
class BaseStep
include Rails.application.routes.url_helpers

def initialize(context, name)
@context = context
@form_response = nil
Expand Down Expand Up @@ -40,6 +42,6 @@ def reset
@context.flow_session = {}
end

delegate :flow_session, :current_user, :params, :steps, to: :@context
delegate :flow_session, :current_user, :params, :steps, :request, to: :@context
end
end
4 changes: 3 additions & 1 deletion app/services/flow/flow_state_machine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def show

def update
step = params[:step]
result = flow.handle(step, params)
result = flow.handle(step, request, params)
analytics.track_event(analytics_submitted, result.to_h.merge(step: step)) if @analytics_id
render_update(step, result)
end
Expand Down Expand Up @@ -50,6 +50,8 @@ def move_to_next_step
end

def render_step(step, flow_session)
@params = params
@request = request
render template: "#{@name}/#{step}", locals: { flow_session: flow_session }
end

Expand Down
7 changes: 7 additions & 0 deletions app/services/idv/flows/doc_auth_flow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,15 @@ module Idv
module Flows
class DocAuthFlow < Flow::BaseFlow
STEPS = {
welcome: Idv::Steps::WelcomeStep,
upload: Idv::Steps::UploadStep,
send_link: Idv::Steps::SendLinkStep,
link_sent: Idv::Steps::LinkSentStep,
email_sent: Idv::Steps::EmailSentStep,
front_image: Idv::Steps::FrontImageStep,
back_image: Idv::Steps::BackImageStep,
mobile_front_image: Idv::Steps::MobileFrontImageStep,
mobile_back_image: Idv::Steps::MobileBackImageStep,
ssn: Idv::Steps::SsnStep,
doc_failed: Idv::Steps::DocFailedStep,
doc_success: Idv::Steps::DocSuccessStep,
Expand Down
7 changes: 7 additions & 0 deletions app/services/idv/steps/email_sent_step.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Idv
module Steps
class EmailSentStep < DocAuthBaseStep
def call; end
end
end
end
7 changes: 7 additions & 0 deletions app/services/idv/steps/link_sent_step.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Idv
module Steps
class LinkSentStep < DocAuthBaseStep
def call; end
end
end
end
43 changes: 43 additions & 0 deletions app/services/idv/steps/mobile_back_image_step.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
module Idv
module Steps
class MobileBackImageStep < DocAuthBaseStep
def call
good, data = assure_id.post_back_image(image.read)
return failure(data) unless good

failure_data, data = verify_back_image
return failure_data if failure_data

extract_pii_from_doc(data)
end

private

def form_submit
Idv::ImageUploadForm.new(current_user).submit(permit(:image))
end

def extract_pii_from_doc(data)
pii_from_doc = Idv::Utils::PiiFromDoc.new(data).call(
current_user.phone_configurations.first.phone,
)
flow_session[:pii_from_doc] = pii_from_doc
end

def verify_back_image
back_image_verified, data = assure_id.results
return failure(data) unless back_image_verified

return [nil, data] if data['Result'] == 1

failure_alerts(data)
end

def failure_alerts(data)
failure(data['Alerts'].
reject { |res| res['Result'] == 2 }.
map { |act| act['Actions'] })
end
end
end
end
24 changes: 24 additions & 0 deletions app/services/idv/steps/mobile_front_image_step.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
module Idv
module Steps
class MobileFrontImageStep < DocAuthBaseStep
def call
success, instance_id_or_message = assure_id.create_document
return failure(instance_id_or_message) unless success

flow_session[:instance_id] = instance_id_or_message
upload_front_image
end

private

def form_submit
Idv::ImageUploadForm.new(current_user).submit(permit(:image))
end

def upload_front_image
success, message = assure_id.post_front_image(image.read)
return failure(message) unless success
end
end
end
end
31 changes: 31 additions & 0 deletions app/services/idv/steps/send_link_step.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
module Idv
module Steps
class SendLinkStep < DocAuthBaseStep
def call
SmsDocAuthLinkJob.perform_now(
phone: permit(:phone),
link: link,
app: app,
)
end

private

def form_submit
Idv::PhoneForm.new(previous_params: {}, user: current_user).submit(permit(:phone))
end

def link
identity&.return_to_sp_url || root_url
end

def app
identity&.friendly_name || 'login.gov'
end

def identity
current_user&.identities&.order('created_at DESC')&.limit(1)&.map(&:decorate)&.first
end
end
end
end
55 changes: 55 additions & 0 deletions app/services/idv/steps/upload_step.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
module Idv
module Steps
class UploadStep < DocAuthBaseStep
def call
have_mobile = mobile?
if params[:type] == 'desktop'
have_mobile ? mobile_to_desktop : desktop
else
have_mobile ? mobile : mark_step_complete(:email_sent)
end
end

private

def mobile?
client = DeviceDetector.new(request.user_agent)
client.device_type != 'desktop'
end

def identity
current_user&.identities&.order('created_at DESC')&.limit(1)&.map(&:decorate)&.first
end

def link
identity&.return_to_sp_url || root_url
end

def application
identity&.friendly_name || 'login.gov'
end

def mobile_to_desktop
mark_step_complete(:send_link)
mark_step_complete(:link_sent)
UserMailer.doc_auth_desktop_link_to_sp(current_user.email, application, link).deliver_later
end

def desktop
mark_step_complete(:send_link)
mark_step_complete(:link_sent)
mark_step_complete(:email_sent)
mark_step_complete(:mobile_front_image)
mark_step_complete(:mobile_back_image)
end

def mobile
mark_step_complete(:send_link)
mark_step_complete(:link_sent)
mark_step_complete(:email_sent)
mark_step_complete(:front_image)
mark_step_complete(:back_image)
end
end
end
end
7 changes: 7 additions & 0 deletions app/services/idv/steps/welcome_step.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Idv
module Steps
class WelcomeStep < DocAuthBaseStep
def call; end
end
end
end
6 changes: 5 additions & 1 deletion app/views/idv/doc_auth/back_image.html.slim
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
-title t('doc_auth.titles.doc_auth')

h1.h3 = t('doc_auth.headings.upload_back')
h1.h3.mb0 = t('doc_auth.headings.upload_back')

h5.mt0 = t('doc_auth.info.upload_image')
= simple_form_for(:doc_auth, url: idv_doc_auth_step_path(step: :back_image), method: 'PUT',
html: { autocomplete: 'off', role: 'form', class: 'mt2' }) do |f|
.clearfix.mxn1
Expand All @@ -10,8 +11,11 @@ h1.h3 = t('doc_auth.headings.upload_back')

p = flow_session[:error_message]

div id= 'target'
br
.mt4
button type='submit' class='btn btn-primary btn-wide sm-col-6 col-12'
= t('forms.buttons.continue')

= render 'start_over_or_cancel'
== javascript_pack_tag 'image-preview'
8 changes: 8 additions & 0 deletions app/views/idv/doc_auth/email_sent.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- title t('doc_auth.titles.doc_auth')

= image_tag(asset_url('[email protected]'),
alt: t('idv.titles.session.success'), width: 210)

h1.h3.mb2.mt3.my0 = t('doc_auth.instructions.email_sent', email: current_user.email)

= render 'start_over_or_cancel'
17 changes: 6 additions & 11 deletions app/views/idv/doc_auth/front_image.html.slim
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
- title t('doc_auth.titles.doc_auth')

= image_tag(asset_url('[email protected]'),
alt: t('idv.titles.jurisdiction'), width: 210)
h1.h3.mb0 = t('doc_auth.headings.upload_front')

h1.h3.mb0 = t('doc_auth.headings.intro')
p = flow_session[:error_message]

ul
li = t('doc_auth.forms.option_1')
li = t('doc_auth.forms.option_2')
li = link_to t('idv.messages.jurisdiction.no_id'), idv_jurisdiction_failure_path(:no_id)
br
h5.mt0 = t('doc_auth.headings.upload_front')
h5.mt0 = t('doc_auth.info.upload_image')
= simple_form_for(:doc_auth, url: idv_doc_auth_step_path(step: :front_image), method: 'PUT',
html: { autocomplete: 'off', role: 'form', class: 'mt2' }) do |f|
.clearfix.mxn1
.sm-col.sm-col-8.px1
= f.input :image, label: false, as: 'file', required: true
p.red.bold = flow_session[:error_message]
div id= 'target'
br
.mt0
button type='submit' class='btn btn-primary btn-wide sm-col-6 col-6'
= t('forms.buttons.continue')
br

= render 'start_over_or_cancel'
== javascript_pack_tag 'image-preview'
8 changes: 8 additions & 0 deletions app/views/idv/doc_auth/link_sent.html.slim
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- title t('doc_auth.titles.doc_auth')

= image_tag(asset_url('[email protected]'),
alt: t('idv.titles.session.success'), width: 210)

h1.h3.mb2.mt3.my0 = t('doc_auth.info.link_sent')

= render 'start_over_or_cancel'
Loading