diff --git a/.reek b/.reek index fa0c167d887..8489015c1a5 100644 --- a/.reek +++ b/.reek @@ -50,6 +50,7 @@ TooManyMethods: - Users::ConfirmationsController - ApplicationController - OpenidConnectAuthorizeForm + - OpenidConnect::AuthorizationController - Idv::Session - User UncommunicativeMethodName: @@ -103,6 +104,9 @@ UtilityFunction: enabled: false TooManyStatements: enabled: false + UncommunicativeMethodName: + exclude: + - visit_idp_from_sp_with_loa1 UncommunicativeParameterName: exclude: - begin_sign_up_with_sp_and_loa diff --git a/.rubocop.yml b/.rubocop.yml index bba92860087..f52cbfadd9d 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -52,8 +52,11 @@ Metrics/ClassLength: Exclude: - spec/**/* - app/controllers/application_controller.rb + - app/controllers/openid_connect/authorization_controller.rb - app/controllers/users/confirmations_controller.rb - app/controllers/devise/two_factor_authentication_controller.rb + - app/decorators/user_decorator.rb + - app/services/idv/session.rb Metrics/LineLength: Description: Limit lines to 100 characters. diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 115dd99e9d6..24986d7c45a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,6 @@ class ApplicationController < ActionController::Base include UserSessionContext + include VerifyProfileConcern FLASH_KEYS = %w[alert error notice success warning].freeze @@ -83,7 +84,7 @@ def after_sign_in_path_for(user) end def signed_in_path - user_fully_authenticated? ? account_path : user_two_factor_authentication_path + user_fully_authenticated? ? account_or_verify_profile_path : user_two_factor_authentication_path end def reauthn_param diff --git a/app/controllers/concerns/two_factor_authenticatable.rb b/app/controllers/concerns/two_factor_authenticatable.rb index ff2e74021f4..86cb204010f 100644 --- a/app/controllers/concerns/two_factor_authenticatable.rb +++ b/app/controllers/concerns/two_factor_authenticatable.rb @@ -77,7 +77,7 @@ def reset_attempt_count_if_user_no_longer_locked_out def handle_valid_otp if authentication_context? handle_valid_otp_for_authentication_context - elsif idv_or_confirmation_context? + elsif idv_or_confirmation_context? || profile_context? handle_valid_otp_for_confirmation_context end @@ -132,7 +132,7 @@ def handle_valid_otp_for_authentication_context end def assign_phone - @updating_existing_number = old_phone.present? + @updating_existing_number = old_phone.present? && !profile_context? if @updating_existing_number && confirmation_context? phone_changed @@ -157,18 +157,27 @@ def phone_confirmed end def update_phone_attributes - current_time = Time.zone.now - - if idv_context? - Idv::Session.new(user_session, current_user).params['phone_confirmed_at'] = current_time + if idv_or_profile_context? + update_idv_state else UpdateUser.new( user: current_user, - attributes: { phone: user_session[:unconfirmed_phone], phone_confirmed_at: current_time } + attributes: { phone: user_session[:unconfirmed_phone], phone_confirmed_at: Time.zone.now } ).call end end + def update_idv_state + now = Time.zone.now + if idv_context? + Idv::Session.new(user_session, current_user).params['phone_confirmed_at'] = now + elsif profile_context? + profile = current_user.decorate.pending_profile + profile.verified_at = now + profile.activate + end + end + def reset_otp_session_data user_session.delete(:unconfirmed_phone) user_session[:context] = 'authentication' @@ -213,7 +222,7 @@ def direct_otp_code end def personal_key_unavailable? - idv_or_confirmation_context? || current_user.personal_key.blank? + idv_or_confirmation_context? || profile_context? || current_user.personal_key.blank? end def unconfirmed_phone? diff --git a/app/controllers/concerns/user_session_context.rb b/app/controllers/concerns/user_session_context.rb index a8616a29e78..e7f58b53970 100644 --- a/app/controllers/concerns/user_session_context.rb +++ b/app/controllers/concerns/user_session_context.rb @@ -25,4 +25,12 @@ def idv_context? def idv_or_confirmation_context? confirmation_context? || idv_context? end + + def idv_or_profile_context? + idv_context? || profile_context? + end + + def profile_context? + context == 'profile' + end end diff --git a/app/controllers/concerns/verify_profile_concern.rb b/app/controllers/concerns/verify_profile_concern.rb new file mode 100644 index 00000000000..3b827929f97 --- /dev/null +++ b/app/controllers/concerns/verify_profile_concern.rb @@ -0,0 +1,31 @@ +module VerifyProfileConcern + private + + def account_or_verify_profile_path + public_send "#{account_or_verify_profile_route}_path" + end + + def account_or_verify_profile_url + public_send "#{account_or_verify_profile_route}_url" + end + + def account_or_verify_profile_route + return 'account' if idv_context? || profile_context? + return 'account' unless current_user.decorate.pending_profile_requires_verification? + verify_profile_route + end + + def verify_profile_route + decorated_user = current_user.decorate + if decorated_user.needs_profile_phone_verification? + flash[:notice] = t('account.index.verification.instructions') + return 'verify_profile_phone' + end + return 'verify_account' if decorated_user.needs_profile_usps_verification? + end + + def profile_needs_verification? + return false if current_user.blank? + current_user.decorate.pending_profile_requires_verification? + end +end diff --git a/app/controllers/openid_connect/authorization_controller.rb b/app/controllers/openid_connect/authorization_controller.rb index 0173d1b624f..cc447fd34f8 100644 --- a/app/controllers/openid_connect/authorization_controller.rb +++ b/app/controllers/openid_connect/authorization_controller.rb @@ -1,6 +1,7 @@ module OpenidConnect class AuthorizationController < ApplicationController include FullyAuthenticatable + include VerifyProfileConcern before_action :build_authorize_form_from_params, only: [:index] before_action :store_request, only: [:index] @@ -11,10 +12,9 @@ class AuthorizationController < ApplicationController def index return confirm_two_factor_authenticated(request_id) unless user_fully_authenticated? - return redirect_to verify_url if identity_needs_verification? + return redirect_to_account_or_verify_profile_url if profile_or_identity_needs_verification? track_index_action_analytics - return create if already_allowed? render(@success ? :index : :error) @@ -45,6 +45,15 @@ def destroy private + def redirect_to_account_or_verify_profile_url + return redirect_to account_or_verify_profile_url if profile_needs_verification? + redirect_to verify_url if identity_needs_verification? + end + + def profile_or_identity_needs_verification? + profile_needs_verification? || identity_needs_verification? + end + def track_index_action_analytics @success = @authorize_form.valid? diff --git a/app/controllers/saml_idp_controller.rb b/app/controllers/saml_idp_controller.rb index 5ac27ccd809..14d7ff28e7f 100644 --- a/app/controllers/saml_idp_controller.rb +++ b/app/controllers/saml_idp_controller.rb @@ -7,14 +7,16 @@ class SamlIdpController < ApplicationController include SamlIdpAuthConcern include SamlIdpLogoutConcern include FullyAuthenticatable + include VerifyProfileConcern skip_before_action :verify_authenticity_token skip_before_action :handle_two_factor_authentication, only: :logout def auth return confirm_two_factor_authenticated(request_id) unless user_fully_authenticated? - process_fully_authenticated_user do |needs_idv| - return store_location_and_redirect_to_verify_url if needs_idv + process_fully_authenticated_user do |needs_idv, needs_profile_finish| + return store_location_and_redirect_to(verify_url) if needs_idv && !needs_profile_finish + return store_location_and_redirect_to(account_or_verify_profile_url) if needs_profile_finish end delete_branded_experience render_template_for(saml_response, saml_request.response_url, 'SAMLResponse') @@ -40,14 +42,16 @@ def process_fully_authenticated_user link_identity_from_session_data needs_idv = identity_needs_verification? - analytics.track_event(Analytics::SAML_AUTH, @result.to_h.merge(idv: needs_idv)) + needs_profile_finish = profile_needs_verification? + analytics_payload = @result.to_h.merge(idv: needs_idv, finish_profile: needs_profile_finish) + analytics.track_event(Analytics::SAML_AUTH, analytics_payload) - yield needs_idv + yield needs_idv, needs_profile_finish end - def store_location_and_redirect_to_verify_url + def store_location_and_redirect_to(url) store_location_for(:user, request.original_url) - redirect_to verify_url + redirect_to url end def render_template_for(message, action_url, type) diff --git a/app/controllers/users/verify_account_controller.rb b/app/controllers/users/verify_account_controller.rb index 9b03b6f0157..4fdecb4a3ef 100644 --- a/app/controllers/users/verify_account_controller.rb +++ b/app/controllers/users/verify_account_controller.rb @@ -1,5 +1,6 @@ module Users class VerifyAccountController < ApplicationController + before_action :confirm_two_factor_authenticated before_action :confirm_verification_needed def index @@ -10,7 +11,7 @@ def create @verify_account_form = build_verify_account_form if @verify_account_form.submit flash[:success] = t('account.index.verification.success') - redirect_to account_path + redirect_to after_sign_in_path_for(current_user) else render :index end @@ -31,12 +32,15 @@ def params_otp end def confirm_verification_needed - current_user.active_profile.blank? && current_user.decorate.pending_profile.present? + return if current_user.decorate.pending_profile_requires_verification? + redirect_to account_url end def decrypted_pii - cacher = Pii::Cacher.new(current_user, user_session) - cacher.fetch + @_decrypted_pii ||= begin + cacher = Pii::Cacher.new(current_user, user_session) + cacher.fetch + end end end end diff --git a/app/controllers/users/verify_profile_phone_controller.rb b/app/controllers/users/verify_profile_phone_controller.rb new file mode 100644 index 00000000000..050c703b655 --- /dev/null +++ b/app/controllers/users/verify_profile_phone_controller.rb @@ -0,0 +1,44 @@ +module Users + class VerifyProfilePhoneController < ApplicationController + include PhoneConfirmation + + before_action :confirm_two_factor_authenticated + before_action :confirm_phone_verification_needed + + def index + prompt_to_confirm_phone(phone: profile_phone, context: 'profile') + end + + private + + def confirm_phone_verification_needed + return if unverified_phone? + redirect_to account_url + end + + def pending_profile_requires_verification? + current_user.decorate.pending_profile_requires_verification? + end + + def unverified_phone? + pending_profile_requires_verification? && + pending_profile.phone_confirmed? && + current_user.phone != profile_phone + end + + def profile_phone + @_profile_phone ||= decrypted_pii.phone.to_s + end + + def pending_profile + @_pending_profile ||= current_user.decorate.pending_profile + end + + def decrypted_pii + @_decrypted_pii ||= begin + cacher = Pii::Cacher.new(current_user, user_session) + cacher.fetch + end + end + end +end diff --git a/app/controllers/verify/sessions_controller.rb b/app/controllers/verify/sessions_controller.rb index ce79641bccb..6d944c73fea 100644 --- a/app/controllers/verify/sessions_controller.rb +++ b/app/controllers/verify/sessions_controller.rb @@ -11,6 +11,7 @@ class SessionsController < ApplicationController delegate :attempts_exceeded?, to: :step, prefix: true def new + user_session[:context] = 'idv' @view_model = view_model analytics.track_event(Analytics::IDV_BASIC_INFO_VISIT) end diff --git a/app/decorators/user_decorator.rb b/app/decorators/user_decorator.rb index ed0f6bb4c91..de6840386a0 100644 --- a/app/decorators/user_decorator.rb +++ b/app/decorators/user_decorator.rb @@ -38,24 +38,43 @@ def masked_two_factor_phone_number masked_number(user.phone) end - def identity_verified? - user.active_profile.present? + def active_identity_for(service_provider) + user.active_identities.find_by(service_provider: service_provider.issuer) end - def identity_not_verified? - !identity_verified? + def active_or_pending_profile + user.active_profile || pending_profile end - def active_identity_for(service_provider) - user.active_identities.find_by(service_provider: service_provider.issuer) + def pending_profile_requires_verification? + return false if pending_profile.blank? + return true if identity_not_verified? + return false if active_profile_newer_than_pending_profile? + true end def pending_profile user.profiles.verification_pending.order(created_at: :desc).first end - def active_or_pending_profile - user.active_profile || pending_profile + def identity_not_verified? + !identity_verified? + end + + def identity_verified? + user.active_profile.present? + end + + def active_profile_newer_than_pending_profile? + user.active_profile.activated_at >= pending_profile.created_at + end + + def needs_profile_phone_verification? + pending_profile_requires_verification? && pending_profile.phone_confirmed? + end + + def needs_profile_usps_verification? + pending_profile_requires_verification? && !pending_profile.phone_confirmed? end # This user's most recently activated profile that has also been deactivated diff --git a/app/forms/verify_account_form.rb b/app/forms/verify_account_form.rb index c10950b678f..31671329da0 100644 --- a/app/forms/verify_account_form.rb +++ b/app/forms/verify_account_form.rb @@ -48,6 +48,7 @@ def reset_sensitive_fields end def activate_profile + pending_profile.verified_at = Time.zone.now pending_profile.activate end end diff --git a/app/services/idv/profile_maker.rb b/app/services/idv/profile_maker.rb index 426e0e4855e..71cd2f9171b 100644 --- a/app/services/idv/profile_maker.rb +++ b/app/services/idv/profile_maker.rb @@ -2,10 +2,12 @@ module Idv class ProfileMaker attr_reader :pii_attributes, :profile - def initialize(applicant:, user:, normalized_applicant:) + def initialize(applicant:, user:, normalized_applicant:, vendor:, phone_confirmed:) @profile = Profile.new(user: user, deactivation_reason: :verification_pending) @pii_attributes = pii_from_applicant(applicant, normalized_applicant) profile.encrypt_pii(user.user_access_key, pii_attributes) + profile.vendor = vendor + profile.phone_confirmed = phone_confirmed profile.save! end diff --git a/app/services/idv/session.rb b/app/services/idv/session.rb index 06330fbc955..03995384b60 100644 --- a/app/services/idv/session.rb +++ b/app/services/idv/session.rb @@ -43,11 +43,6 @@ def proofing_started? end def cache_applicant_profile_id - profile_maker = Idv::ProfileMaker.new( - applicant: Proofer::Applicant.new(applicant_params), - normalized_applicant: Proofer::Applicant.new(normalized_applicant_params), - user: current_user - ) profile = profile_maker.profile self.pii = profile_maker.pii_attributes self.profile_id = profile.id @@ -78,13 +73,15 @@ def complete_session def complete_profile profile.verified_at = Time.zone.now - profile.vendor = vendor profile.activate move_pii_to_user_session end def create_usps_entry move_pii_to_user_session + if pii.is_a?(String) + self.pii = Pii::Attributes.new_from_json(user_session[:decrypted_pii]) + end UspsConfirmationMaker.new(pii: pii).perform end @@ -120,5 +117,15 @@ def applicant_params def applicant_params_ascii Hash[applicant_params.map { |key, value| [key, value.to_ascii] }] end + + def profile_maker + @_profile_maker ||= Idv::ProfileMaker.new( + applicant: Proofer::Applicant.new(applicant_params), + normalized_applicant: Proofer::Applicant.new(normalized_applicant_params), + phone_confirmed: phone_confirmation || false, + user: current_user, + vendor: vendor + ) + end end end diff --git a/app/view_models/account_show.rb b/app/view_models/account_show.rb index d0a10d22ecb..92764704841 100644 --- a/app/view_models/account_show.rb +++ b/app/view_models/account_show.rb @@ -32,8 +32,10 @@ def password_reset_partial end def pending_profile_partial - if decorated_user.pending_profile.present? - 'accounts/pending_profile' + if decorated_user.needs_profile_usps_verification? + 'accounts/pending_profile_usps' + elsif decorated_user.needs_profile_phone_verification? + 'accounts/pending_profile_phone' else 'shared/null' end diff --git a/app/views/accounts/_pending_profile_phone.html.slim b/app/views/accounts/_pending_profile_phone.html.slim new file mode 100644 index 00000000000..14dc02731c8 --- /dev/null +++ b/app/views/accounts/_pending_profile_phone.html.slim @@ -0,0 +1,3 @@ +.mb4.alert.alert-warning + p = t('account.index.verification.instructions') + p.mb0 = link_to t('account.index.verification.with_phone_button'), verify_profile_phone_path diff --git a/app/views/accounts/_pending_profile.html.slim b/app/views/accounts/_pending_profile_usps.html.slim similarity index 100% rename from app/views/accounts/_pending_profile.html.slim rename to app/views/accounts/_pending_profile_usps.html.slim diff --git a/config/locales/account/en.yml b/config/locales/account/en.yml index 877479d9322..1ce26234d0f 100644 --- a/config/locales/account/en.yml +++ b/config/locales/account/en.yml @@ -21,6 +21,7 @@ en: instructions: Your account requires a secret code to be verified. reactivate_button: Enter the code you received via US mail success: Your account has been verified. + with_phone_button: Verify with your phone items: personal_key: Personal key links: diff --git a/config/locales/account/es.yml b/config/locales/account/es.yml index 0c930206652..cb46ed57631 100644 --- a/config/locales/account/es.yml +++ b/config/locales/account/es.yml @@ -21,6 +21,7 @@ es: instructions: NOT TRANSLATED YET reactivate_button: NOT TRANSLATED YET success: NOT TRANSLATED YET + with_phone_button: NOT TRANSLATED YET items: personal_key: NOT TRANSLATED YET links: diff --git a/config/routes.rb b/config/routes.rb index 9b25f174201..c84c295cc61 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -49,6 +49,8 @@ post '/account/reactivate' => 'users/reactivate_account#create' get '/account/verify' => 'users/verify_account#index', as: :verify_account post '/account/verify' => 'users/verify_account#create' + get '/account/verify_phone' => 'users/verify_profile_phone#index', as: :verify_profile_phone + post '/account/verify_phone' => 'users/verify_profile_phone#create' get '/api/health/workers' => 'health/workers#index' get '/api/openid_connect/certs' => 'openid_connect/certs#index' diff --git a/db/migrate/20170413152832_add_profile_phone_confirmed.rb b/db/migrate/20170413152832_add_profile_phone_confirmed.rb new file mode 100644 index 00000000000..80423be7db4 --- /dev/null +++ b/db/migrate/20170413152832_add_profile_phone_confirmed.rb @@ -0,0 +1,5 @@ +class AddProfilePhoneConfirmed < ActiveRecord::Migration + def change + add_column :profiles, :phone_confirmed, :boolean, default: false, null: false + end +end diff --git a/db/schema.rb b/db/schema.rb index f019d5e8b81..904aa466644 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20170321170517) do +ActiveRecord::Schema.define(version: 20170413152832) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -80,6 +80,7 @@ t.string "ssn_signature", limit: 64 t.text "encrypted_pii_recovery" t.integer "deactivation_reason" + t.boolean "phone_confirmed", default: false, null: false end add_index "profiles", ["ssn_signature", "active"], name: "index_profiles_on_ssn_signature_and_active", unique: true, where: "(active = true)", using: :btree diff --git a/spec/controllers/saml_idp_controller_spec.rb b/spec/controllers/saml_idp_controller_spec.rb index ec4e225a80e..85c464b8b6e 100644 --- a/spec/controllers/saml_idp_controller_spec.rb +++ b/spec/controllers/saml_idp_controller_spec.rb @@ -225,6 +225,7 @@ authn_context: Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', idv: false, + finish_profile: false, } expect(@analytics).to have_received(:track_event). @@ -756,6 +757,7 @@ def stub_auth authn_context: Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', idv: true, + finish_profile: false, } expect(@analytics).to receive(:track_event). @@ -778,6 +780,30 @@ def stub_auth authn_context: Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF, service_provider: 'http://localhost:3000', idv: false, + finish_profile: false, + } + + expect(@analytics).to receive(:track_event).with(Analytics::SAML_AUTH, analytics_hash) + + generate_saml_response(user) + end + end + + context 'user has not finished verifying profile' do + it 'tracks the authentication with finish_profile==true' do + user = create(:user, :signed_up) + + stub_analytics + allow(controller).to receive(:identity_needs_verification?).and_return(false) + allow(controller).to receive(:profile_needs_verification?).and_return(true) + + analytics_hash = { + success: true, + errors: {}, + authn_context: Saml::Idp::Constants::LOA1_AUTHN_CONTEXT_CLASSREF, + service_provider: 'http://localhost:3000', + idv: false, + finish_profile: true, } expect(@analytics).to receive(:track_event).with(Analytics::SAML_AUTH, analytics_hash) diff --git a/spec/controllers/users/verify_account_controller_spec.rb b/spec/controllers/users/verify_account_controller_spec.rb index 05fe1c0ae36..3526d76cee4 100644 --- a/spec/controllers/users/verify_account_controller_spec.rb +++ b/spec/controllers/users/verify_account_controller_spec.rb @@ -3,35 +3,66 @@ RSpec.describe Users::VerifyAccountController do include Features::LocalizationHelper + let(:has_pending_profile) { true } + let(:success) { true } + let(:otp) { 'abc123' } + let(:submitted_otp) { otp } + let(:pii_attributes) { Pii::Attributes.new_from_hash(otp: otp) } + let(:pending_profile) { build(:profile) } + + before do + user = stub_sign_in + decorated_user = stub_decorated_user_with_pending_profile(user) + allow(decorated_user).to receive(:needs_profile_phone_verification?).and_return(false) + allow(decorated_user).to receive(:needs_profile_usps_verification?). + and_return(has_pending_profile) + allow(controller).to receive(:decrypted_pii).and_return(pii_attributes) + end + + describe '#index' do + subject(:action) do + get(:index) + end + + context 'user has pending profile' do + it 'renders page' do + action + + expect(response).to render_template('users/verify_account/index') + end + end + + context 'user does not have pending profile' do + let(:has_pending_profile) { false } + + it 'redirects to account page' do + action + + expect(response).to redirect_to(account_url) + end + end + end + describe '#create' do subject(:action) do post( :create, verify_account_form: { - otp: 'abc123', + otp: submitted_otp, } ) end - before do - stub_sign_in - - form = instance_double('VerifyAccountForm', submit: success) - expect(controller).to receive(:build_verify_account_form).and_return(form) - end - context 'with a valid form' do - let(:success) { true } - - it 'redirects to the profile page' do + it 'redirects to the account verification page' do action - expect(response).to redirect_to(account_path) + expect(response).to redirect_to(verify_account_path) end end context 'with an invalid form' do - let(:success) { false } + let(:submitted_otp) { 'the-wrong-otp' } it 'renders the index page to show errors' do action diff --git a/spec/controllers/users/verify_profile_phone_controller_spec.rb b/spec/controllers/users/verify_profile_phone_controller_spec.rb new file mode 100644 index 00000000000..a7621443198 --- /dev/null +++ b/spec/controllers/users/verify_profile_phone_controller_spec.rb @@ -0,0 +1,56 @@ +require 'rails_helper' + +RSpec.describe Users::VerifyProfilePhoneController do + include Features::LocalizationHelper + + let(:has_pending_profile) { true } + let(:user) { create(:user) } + let(:profile_phone) { user.phone } + let(:phone_confirmed) { false } + let(:pii_attributes) { Pii::Attributes.new_from_hash(phone: profile_phone) } + let(:pending_profile) { build(:profile, phone_confirmed: phone_confirmed) } + + before do + stub_sign_in(user) + decorated_user = stub_decorated_user_with_pending_profile(user) + allow(decorated_user).to receive(:needs_profile_phone_verification?). + and_return(has_pending_profile) + allow(decorated_user).to receive(:needs_profile_usps_verification?).and_return(false) + allow(controller).to receive(:decrypted_pii).and_return(pii_attributes) + end + + describe '#index' do + context 'user has pending profile' do + context 'phone is not confirmed' do + it 'redirects to profile page' do + get :index + + expect(response).to redirect_to(account_url) + end + end + + context 'phone is confirmed and different than 2FA' do + let(:profile_phone) { '555-555-9999' } + let(:phone_confirmed) { true } + + it 'redirects to OTP confirmation flow' do + get :index + + expect(response).to redirect_to( + otp_send_path(otp_delivery_selection_form: { otp_delivery_preference: 'sms' }) + ) + end + end + end + + context 'user does not have pending profile' do + let(:has_pending_profile) { false } + + it 'redirects to profile page' do + get :index + + expect(response).to redirect_to(account_url) + end + end + end +end diff --git a/spec/controllers/verify/confirmations_controller_spec.rb b/spec/controllers/verify/confirmations_controller_spec.rb index 99bb9fccf53..c3bde9321b7 100644 --- a/spec/controllers/verify/confirmations_controller_spec.rb +++ b/spec/controllers/verify/confirmations_controller_spec.rb @@ -15,7 +15,9 @@ def stub_idv_session profile_maker = Idv::ProfileMaker.new( applicant: applicant, user: user, - normalized_applicant: normalized_applicant + normalized_applicant: normalized_applicant, + vendor: :mock, + phone_confirmed: true ) profile = profile_maker.profile idv_session.pii = profile_maker.pii_attributes diff --git a/spec/decorators/user_decorator_spec.rb b/spec/decorators/user_decorator_spec.rb index e8e2cf60726..994e203fd49 100644 --- a/spec/decorators/user_decorator_spec.rb +++ b/spec/decorators/user_decorator_spec.rb @@ -63,12 +63,222 @@ end end + describe '#pending_profile_requires_verification?' do + it 'returns false when no pending profile exists' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile).and_return(nil) + + expect(user_decorator.pending_profile_requires_verification?).to eq false + end + + it 'returns true when pending profile exists and identity is not verified' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile).and_return('profile') + allow(user_decorator).to receive(:identity_not_verified?).and_return(true) + + expect(user_decorator.pending_profile_requires_verification?).to eq true + end + + it 'returns false when active profile is newer than pending profile' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile).and_return('profile') + allow(user_decorator).to receive(:identity_not_verified?).and_return(false) + allow(user_decorator).to receive(:active_profile_newer_than_pending_profile?). + and_return(true) + + expect(user_decorator.pending_profile_requires_verification?).to eq false + end + + it 'returns true when pending profile is newer than active profile' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile).and_return('profile') + allow(user_decorator).to receive(:identity_not_verified?).and_return(false) + allow(user_decorator).to receive(:active_profile_newer_than_pending_profile?). + and_return(false) + + expect(user_decorator.pending_profile_requires_verification?).to eq true + end + end + describe '#pending_profile' do - it 'returns Profile awaiting USPS confirmation' do - profile = create(:profile, deactivation_reason: :verification_pending) - user_decorator = UserDecorator.new(profile.user) + context 'when a profile with a verification_pending deactivation_reason exists' do + it 'returns the most recent profile' do + user = User.new + _old_profile = create( + :profile, + deactivation_reason: :verification_pending, + created_at: 1.day.ago, + user: user + ) + new_profile = create( + :profile, + deactivation_reason: :verification_pending, + user: user + ) + user_decorator = UserDecorator.new(user) + + expect(user_decorator.pending_profile).to eq new_profile + end + end + + context 'when a verification_pending profile does not exist' do + it 'returns nil' do + user = User.new + create( + :profile, + deactivation_reason: :password_reset, + created_at: 1.day.ago, + user: user + ) + create( + :profile, + deactivation_reason: :encryption_error, + user: user + ) + user_decorator = UserDecorator.new(user) + + expect(user_decorator.pending_profile).to be_nil + end + end + end + + describe '#identity_not_verified?' do + it 'returns true if identity_verified returns false' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:identity_verified?).and_return(false) + + expect(user_decorator.identity_not_verified?).to eq true + end + + it 'returns false if identity_verified returns true' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:identity_verified?).and_return(true) + + expect(user_decorator.identity_not_verified?).to eq false + end + end + + describe '#identity_verified?' do + it 'returns true if user has an active profile' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user).to receive(:active_profile).and_return(Profile.new) + + expect(user_decorator.identity_verified?).to eq true + end + + it 'returns false if user does not have an active profile' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user).to receive(:active_profile).and_return(nil) - expect(user_decorator.pending_profile).to eq profile + expect(user_decorator.identity_verified?).to eq false + end + end + + describe '#active_profile_newer_than_pending_profile?' do + it 'returns true if the active profile is newer than the pending profile' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user).to receive(:active_profile).and_return(Profile.new(activated_at: Time.zone.now)) + allow(user_decorator).to receive(:pending_profile). + and_return(Profile.new(created_at: 1.day.ago)) + + expect(user_decorator.active_profile_newer_than_pending_profile?).to eq true + end + + it 'returns false if the active profile is older than the pending profile' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user).to receive(:active_profile).and_return(Profile.new(activated_at: 1.day.ago)) + allow(user_decorator).to receive(:pending_profile). + and_return(Profile.new(created_at: Time.zone.now)) + + expect(user_decorator.active_profile_newer_than_pending_profile?).to eq false + end + end + + describe '#needs_profile_phone_verification?' do + context 'pending profile does not require verification' do + it 'returns false' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile_requires_verification?). + and_return(false) + + expect(user_decorator.needs_profile_phone_verification?).to eq false + end + end + + context 'pending profile requires verification and phone is confirmed' do + it 'returns true' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile_requires_verification?). + and_return(true) + allow(user_decorator).to receive(:pending_profile). + and_return(Profile.new(phone_confirmed: true)) + + expect(user_decorator.needs_profile_phone_verification?).to eq true + end + end + + context 'pending profile requires verification and phone is not confirmed' do + it 'returns false' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile_requires_verification?). + and_return(true) + allow(user_decorator).to receive(:pending_profile). + and_return(Profile.new(phone_confirmed: false)) + + expect(user_decorator.needs_profile_phone_verification?).to eq false + end + end + end + + describe '#needs_profile_usps_verification?' do + context 'pending profile does not require verification' do + it 'returns false' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile_requires_verification?). + and_return(false) + + expect(user_decorator.needs_profile_usps_verification?).to eq false + end + end + + context 'pending profile requires verification and phone is not confirmed' do + it 'returns true' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile_requires_verification?). + and_return(true) + allow(user_decorator).to receive(:pending_profile). + and_return(Profile.new(phone_confirmed: false)) + + expect(user_decorator.needs_profile_usps_verification?).to eq true + end + end + + context 'pending profile requires verification and phone is confirmed' do + it 'returns false' do + user = User.new + user_decorator = UserDecorator.new(user) + allow(user_decorator).to receive(:pending_profile_requires_verification?). + and_return(true) + allow(user_decorator).to receive(:pending_profile). + and_return(Profile.new(phone_confirmed: true)) + + expect(user_decorator.needs_profile_usps_verification?).to eq false + end end end diff --git a/spec/features/idv/flow_spec.rb b/spec/features/idv/flow_spec.rb index c7c5f01e433..30ef0851447 100644 --- a/spec/features/idv/flow_spec.rb +++ b/spec/features/idv/flow_spec.rb @@ -42,7 +42,7 @@ fill_out_phone_form_ok(user.phone) click_idv_continue fill_in :user_password, with: user_password - click_button t('forms.buttons.submit.default') + click_submit_default expect(current_url).to eq verify_confirmations_url expect(page).to have_content(t('headings.personal_key')) @@ -358,6 +358,15 @@ click_on t('idv.buttons.send_letter') expect(current_path).to eq verify_review_path + + fill_in :user_password, with: user_password + click_submit_default + + expect(current_url).to eq verify_confirmations_url + click_acknowledge_personal_key + + expect(current_url).to eq(account_url) + expect(page).to have_content(t('account.index.verification.reactivate_button')) end context 'cancel from USPS/Phone verification screen' do @@ -396,6 +405,35 @@ end end end + + scenario 'continue phone OTP verification after cancel' do + different_phone = '555-555-9876' + user = sign_in_live_with_2fa + visit verify_session_path + + fill_out_idv_form_ok + click_idv_continue + fill_out_financial_form_ok + click_idv_continue + click_idv_address_choose_phone + fill_out_phone_form_ok(different_phone) + click_idv_continue + fill_in :user_password, with: user_password + click_submit_default + + click_on t('links.cancel') + + expect(current_path).to eq root_path + + sign_in_live_with_2fa(user) + + expect(page).to have_content('9876') + expect(page).to have_content(t('account.index.verification.instructions')) + + enter_correct_otp_code_for_user(user) + + expect(current_path).to eq account_path + end end def complete_idv_profile_fail diff --git a/spec/features/openid_connect/openid_connect_spec.rb b/spec/features/openid_connect/openid_connect_spec.rb index 29794ab26fe..d34731e1551 100644 --- a/spec/features/openid_connect/openid_connect_spec.rb +++ b/spec/features/openid_connect/openid_connect_spec.rb @@ -249,6 +249,67 @@ end end + context 'LOA3 continuation' do + let(:user) { profile.user } + let(:otp) { 'abc123' } + let(:profile) do + create( + :profile, + deactivation_reason: :verification_pending, + phone_confirmed: phone_confirmed, + pii: { otp: otp, ssn: '6666', dob: '1920-01-01' } + ) + end + let(:oidc_auth_url) do + client_id = 'urn:gov:gsa:openidconnect:sp:server' + state = SecureRandom.hex + nonce = SecureRandom.hex + + openid_connect_authorize_path( + client_id: client_id, + response_type: 'code', + acr_values: Saml::Idp::Constants::LOA3_AUTHN_CONTEXT_CLASSREF, + scope: 'openid email profile:name social_security_number', + redirect_uri: 'http://localhost:7654/auth/result', + state: state, + prompt: 'select_account', + nonce: nonce + ) + end + + context 'USPS verification' do + let(:phone_confirmed) { false } + + it 'prompts to finish verifying profile, then redirects to SP' do + visit oidc_auth_url + + sign_in_live_with_2fa(user) + + fill_in 'Secret code', with: otp + click_button t('forms.verify_profile.submit') + click_button t('openid_connect.authorization.index.allow') + + redirect_uri = URI(current_url) + expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result') + end + end + + context 'phone verification' do + let(:phone_confirmed) { true } + + it 'prompts to finish verifying profile, then redirects to SP' do + visit oidc_auth_url + + sign_in_live_with_2fa(user) + enter_correct_otp_code_for_user(user) + click_button t('openid_connect.authorization.index.allow') + + redirect_uri = URI(current_url) + expect(redirect_uri.to_s).to start_with('http://localhost:7654/auth/result') + end + end + end + context 'LOA3 signup' do it 'redirects back to SP' do client_id = 'urn:gov:gsa:openidconnect:sp:server' diff --git a/spec/features/saml/loa3_sso_spec.rb b/spec/features/saml/loa3_sso_spec.rb index 096bcbc9478..f9938475bb4 100644 --- a/spec/features/saml/loa3_sso_spec.rb +++ b/spec/features/saml/loa3_sso_spec.rb @@ -101,6 +101,54 @@ end end + context 'continuing verification' do + let(:user) { profile.user } + let(:otp) { 'abc123' } + let(:profile) do + create( + :profile, + deactivation_reason: :verification_pending, + phone_confirmed: phone_confirmed, + pii: { otp: otp, ssn: '6666', dob: '1920-01-01' } + ) + end + + context 'having previously selected USPS verification' do + let(:phone_confirmed) { false } + + it 'prompts for OTP at sign in' do + saml_authn_request = auth_request.create(loa3_with_bundle_saml_settings) + + visit saml_authn_request + + sign_in_live_with_2fa(user) + + expect(current_path).to eq verify_account_path + + fill_in 'Secret code', with: otp + click_button t('forms.verify_profile.submit') + + expect(current_url).to eq saml_authn_request + end + end + + context 'having previously cancelled phone verification' do + let(:phone_confirmed) { true } + + it 'prompts for OTP at sign in, then continues' do + saml_authn_request = auth_request.create(loa3_with_bundle_saml_settings) + + visit saml_authn_request + + sign_in_live_with_2fa(user) + + enter_correct_otp_code_for_user(user) + + expect(current_url).to eq saml_authn_request + end + end + end + context 'visiting sign_up_completed path before proofing' do it 'redirects to verify_path' do sign_in_and_2fa_user diff --git a/spec/features/users/verify_profile_spec.rb b/spec/features/users/verify_profile_spec.rb index afecc0e6b66..5ac5b93b0fc 100644 --- a/spec/features/users/verify_profile_spec.rb +++ b/spec/features/users/verify_profile_spec.rb @@ -8,41 +8,55 @@ create( :profile, deactivation_reason: :verification_pending, - pii: { otp: otp, ssn: '666-66-1234', dob: '1920-01-01' }, + pii: { otp: otp, ssn: '666-66-1234', dob: '1920-01-01', phone: '555-555-9999' }, + phone_confirmed: phone_confirmed, user: user ) end - scenario 'received OTP via USPS' do - sign_in_live_with_2fa(user) + context 'USPS letter' do + let(:phone_confirmed) { false } - expect(current_path).to eq account_path + scenario 'received OTP via USPS' do + sign_in_live_with_2fa(user) - click_on t('account.index.verification.reactivate_button') + expect(current_path).to eq verify_account_path - expect(current_path).to eq verify_account_path + fill_in 'Secret code', with: otp + click_button t('forms.verify_profile.submit') - fill_in 'Secret code', with: otp - click_button t('forms.verify_profile.submit') + expect(current_path).to eq account_path + expect(page).to_not have_content(t('account.index.verification.reactivate_button')) + end - expect(current_path).to eq account_path - expect(page).to_not have_content(t('account.index.verification.reactivate_button')) - end + xscenario 'OTP has expired' do + # see https://github.com/18F/identity-private/issues/1108#issuecomment-293328267 + end + + scenario 'wrong OTP used' do + sign_in_live_with_2fa(user) + fill_in 'Secret code', with: 'the wrong code' + click_button t('forms.verify_profile.submit') - xscenario 'OTP has expired' do - # see https://github.com/18F/identity-private/issues/1108#issuecomment-293328267 + expect(current_path).to eq verify_account_path + expect(page).to have_content(t('errors.messages.otp_incorrect')) + expect(page.body).to_not match('the wrong code') + end end - scenario 'wrong OTP used' do - sign_in_live_with_2fa(user) + context 'profile phone confirmed' do + let(:phone_confirmed) { true } - click_on t('account.index.verification.reactivate_button') + before do + allow(FeatureManagement).to receive(:prefill_otp_codes?).and_return(true) + end - fill_in 'Secret code', with: 'the wrong code' - click_button t('forms.verify_profile.submit') + scenario 'not yet verified with user' do + sign_in_live_with_2fa(user) + click_submit_default - expect(current_path).to eq verify_account_path - expect(page).to have_content(t('errors.messages.otp_incorrect')) - expect(page.body).to_not match('the wrong code') + expect(current_path).to eq account_path + expect(page).to_not have_content(t('account.index.verification.with_phone_button')) + end end end diff --git a/spec/services/idv/profile_maker_spec.rb b/spec/services/idv/profile_maker_spec.rb index 5a5efcf3807..aa3b8e8bc50 100644 --- a/spec/services/idv/profile_maker_spec.rb +++ b/spec/services/idv/profile_maker_spec.rb @@ -11,7 +11,9 @@ profile_maker = described_class.new( applicant: applicant, user: user, - normalized_applicant: normalized_applicant + normalized_applicant: normalized_applicant, + vendor: :mock, + phone_confirmed: false ) profile = profile_maker.profile diff --git a/spec/support/controller_helper.rb b/spec/support/controller_helper.rb index bc351b2acb1..a2f1ca0a4f2 100644 --- a/spec/support/controller_helper.rb +++ b/spec/support/controller_helper.rb @@ -43,6 +43,15 @@ def stub_verify_steps_one_and_two(user) allow(subject).to receive(:idv_session).and_return(idv_session) allow(subject).to receive(:user_session).and_return(user_session) end + + def stub_decorated_user_with_pending_profile(user) + decorated_user = instance_double(UserDecorator) + allow(user).to receive(:decorate).and_return(decorated_user) + allow(decorated_user).to receive(:pending_profile).and_return(pending_profile) + allow(decorated_user).to receive(:pending_profile_requires_verification?). + and_return(has_pending_profile) + decorated_user + end end RSpec.configure do |config| diff --git a/spec/view_models/account_show_spec.rb b/spec/view_models/account_show_spec.rb index 4fecd16d0c9..12d89ee59bf 100644 --- a/spec/view_models/account_show_spec.rb +++ b/spec/view_models/account_show_spec.rb @@ -1,188 +1,203 @@ require 'rails_helper' describe AccountShow do - let(:unverified_view_model) { unverified_account_show } - describe '#header_partial' do - it 'returns a basic header when user\'s identity is unverified' do - expect(unverified_view_model.header_partial).to eq('accounts/header') + context 'user has a verified identity' do + it 'returns the verified header partial' do + user = User.new + allow(user).to receive(:identity_verified?).and_return(true) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) + + expect(profile_index.header_partial).to eq 'accounts/verified_header' + end end - it 'returns a verified header when user identity is verified' do - view_model = verified_profile_index + context 'user does not have a verified identity' do + it 'returns the unverified header partial' do + user = User.new + allow(user).to receive(:identity_verified?).and_return(false) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) - expect(view_model.header_partial).to eq('accounts/verified_header') + expect(profile_index.header_partial).to eq 'accounts/header' + end end end describe '#personal_key_partial' do - it 'returns a null partial when a new personal key is not present' do - expect_null_partial(unverified_view_model, 'personal_key_partial') + context 'AccountShow instance has a personal_key' do + it 'returns the personal_key partial' do + user = User.new + profile_index = AccountShow.new( + decrypted_pii: {}, personal_key: 'foo', decorated_user: user + ) + + expect(profile_index.personal_key_partial).to eq 'accounts/personal_key' + end end - it 'returns personal_key partial when a new personal key is present' do - view_model = unverified_account_show(personal_key: '123') + context 'AccountShow instance does not have a personal_key' do + it 'returns the shared/null partial' do + user = User.new + profile_index = AccountShow.new( + decrypted_pii: {}, personal_key: '', decorated_user: user + ) - expect(view_model.personal_key_partial).to eq('accounts/personal_key') + expect(profile_index.personal_key_partial).to eq 'shared/null' + end end end describe '#password_reset_partial' do - it 'returns null partial when reset password flag is not present' do - expect_null_partial(unverified_view_model, 'password_reset_partial') + context 'user has a password_reset_profile' do + it 'returns the accounts/password_reset partial' do + user = User.new + allow(user).to receive(:password_reset_profile).and_return('profile') + profile_index = AccountShow.new( + decrypted_pii: {}, personal_key: 'foo', decorated_user: user + ) + + expect(profile_index.password_reset_partial).to eq 'accounts/password_reset' + end end - it 'returns password reset alert partial when password reset flag present' do - user = create(:profile, deactivation_reason: 1).user.decorate - view_model = AccountShow.new( - decrypted_pii: nil, - personal_key: nil, - decorated_user: user - ) + context 'user does not have a password_reset_profile' do + it 'returns the shared/null partial' do + user = User.new + allow(user).to receive(:password_reset_profile).and_return(nil) + profile_index = AccountShow.new( + decrypted_pii: {}, personal_key: '', decorated_user: user + ) - expect(view_model.password_reset_partial).to eq('accounts/password_reset') + expect(profile_index.password_reset_partial).to eq 'shared/null' + end end end describe '#pending_profile_partial' do - it 'returns null partial when pending profile flag is not present' do - expect_null_partial(unverified_view_model, 'pending_profile_partial') + context 'user needs profile usps verification' do + it 'returns the accounts/pending_profile_usps partial' do + user = User.new + allow(user).to receive(:needs_profile_usps_verification?).and_return(true) + allow(user).to receive(:needs_profile_phone_verification?).and_return(false) + profile_index = AccountShow.new( + decrypted_pii: {}, personal_key: 'foo', decorated_user: user + ) + + expect(profile_index.pending_profile_partial).to eq 'accounts/pending_profile_usps' + end end - it 'returns pending profile alert partial when pending profile flag present' do - user = create(:profile, deactivation_reason: 3).user.decorate - view_model = AccountShow.new( - decrypted_pii: nil, - personal_key: nil, - decorated_user: user - ) + context 'user needs profile phone verification' do + it 'returns the accounts/pending_profile_phone partial' do + user = User.new + allow(user).to receive(:needs_profile_usps_verification?).and_return(false) + allow(user).to receive(:needs_profile_phone_verification?).and_return(true) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) - expect(view_model.pending_profile_partial).to eq('accounts/pending_profile') + expect(profile_index.pending_profile_partial).to eq 'accounts/pending_profile_phone' + end end - end - describe '#pii_partial' do - it 'returns a null partial when the user is unverified' do - expect_null_partial(unverified_view_model, 'pii_partial') - end + context 'user does not need profile verification' do + it 'returns the shared/null partial' do + user = User.new + allow(user).to receive(:needs_profile_phone_verification?).and_return(false) + allow(user).to receive(:needs_profile_usps_verification?).and_return(false) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) - it 'returns pii partial when user is verified' do - expect(verified_profile_index.pii_partial).to eq('accounts/pii') + expect(profile_index.pending_profile_partial).to eq 'shared/null' + end end end - describe '#edit_action_partial' do - it 'returns edit action button partial' do - expect(unverified_view_model.edit_action_partial).to( - eq('accounts/actions/edit_action_button') - ) - end - end + describe '#pii_partial' do + context 'AccountShow instance has decrypted_pii' do + it 'returns the accounts/password_reset partial' do + user = User.new + profile_index = AccountShow.new( + decrypted_pii: { foo: 'bar' }, personal_key: '', decorated_user: user + ) - describe '#personal_key_action_partial' do - it 'returns manage personal key action partial' do - expect(unverified_view_model.personal_key_action_partial).to( - eq('accounts/actions/manage_personal_key') - ) + expect(profile_index.pii_partial).to eq 'accounts/pii' + end end - end - describe '#personal_key_item_partial' do - it 'returns personal key item heading partial' do - expect(unverified_view_model.personal_key_item_partial).to( - eq('accounts/personal_key_item_heading') - ) - end - end + context 'AccountShow instance does not have decrypted_pii' do + it 'returns the shared/null partial' do + user = User.new + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) - describe '#recent_event_partial' do - it 'returns partial to format a single recent user event' do - expect(unverified_view_model.recent_event_partial).to eq('accounts/event_item') + expect(profile_index.pii_partial).to eq 'shared/null' + end end end - context 'totp related methods' do - context 'with totp enabled' do - before do - user = build_stubbed(:user, otp_secret_key: '123').decorate - @view_model = AccountShow.new( - decrypted_pii: nil, - personal_key: nil, - decorated_user: user - ) - end - - describe '#totp_partial' do - it 'returns a partial to disable totp if active' do - expect(@view_model.totp_partial).to eq('accounts/actions/disable_totp') - end - end + describe '#totp_partial' do + context 'user has enabled an authenticator app' do + it 'returns the disable_totp partial' do + user = User.new + allow(user).to receive(:totp_enabled?).and_return(true) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) - describe '#totp_content' do - it 'returns auth app enabled message' do - expect(@view_model.totp_content).to eq('account.index.auth_app_enabled') - end + expect(profile_index.totp_partial).to eq 'accounts/actions/disable_totp' end end - context 'with totp disabled' do - describe '#totp_partial' do - it 'returns a partial to enable totp' do - expect(unverified_view_model.totp_partial).to eq('accounts/actions/enable_totp') - end - end + context 'user does not have an authenticator app enabled' do + it 'returns the enable_totp partial' do + user = User.new + allow(user).to receive(:totp_enabled?).and_return(false) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) - describe '#totp_content' do - it 'returns auth app disabled message' do - expect(unverified_view_model.totp_content).to eq('account.index.auth_app_disabled') - end + expect(profile_index.totp_partial).to eq 'accounts/actions/enable_totp' end end end describe '#header_personalization' do - it 'returns an email address when user does not have a verified profile' do - user = unverified_view_model.decorated_user - expect(unverified_view_model.header_personalization).to eq(user.email) - end + context 'AccountShow instance has decrypted_pii' do + it "returns the user's first name" do + user = User.new + first_name = 'John' + decrypted_pii = Pii::Attributes.new_from_json({ first_name: first_name }.to_json) + profile_index = AccountShow.new( + decrypted_pii: decrypted_pii, personal_key: '', decorated_user: user + ) - it 'returns the users first name when they have a verified profile' do - expect(verified_profile_index.header_personalization).to eq('Alex') + expect(profile_index.header_personalization).to eq first_name + end end - end - describe '#recent_events' do - it 'exposes recent_events method from decorated_user' do - expect(unverified_account_show).to respond_to(:recent_events) + context 'AccountShow instance does not have decrypted_pii' do + it "returns the user's email" do + email = 'john@smith.com' + user = User.new(email: email) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) + + expect(profile_index.header_personalization).to eq email + end end end - def verified_profile_index(personal_key: nil) - profile = create(:profile, :active, :verified, pii: { first_name: 'Alex' }) - user = profile.user - user_access_key = user.unlock_user_access_key(user.password) - decrypted_pii = profile.decrypt_pii(user_access_key) - - AccountShow.new( - decrypted_pii: decrypted_pii, - personal_key: personal_key, - decorated_user: user.decorate - ) - end + describe '#totp_content' do + context 'user has enabled an authenticator app' do + it 'returns profile.index.auth_app_enabled ' do + user = User.new + allow(user).to receive(:totp_enabled?).and_return(true) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) - def unverified_account_show(personal_key: nil) - AccountShow.new( - decrypted_pii: nil, - personal_key: personal_key, - decorated_user: unverified_decorated_user - ) - end + expect(profile_index.totp_content).to eq 'account.index.auth_app_enabled' + end + end - def expect_null_partial(view_model, method) - expect(view_model.send(method.to_sym)).to eq('shared/null') - end + context 'user does not have an authenticator app enabled' do + it 'returns profile.index.auth_app_disabled' do + user = User.new + allow(user).to receive(:totp_enabled?).and_return(false) + profile_index = AccountShow.new(decrypted_pii: {}, personal_key: '', decorated_user: user) - def unverified_decorated_user - build_stubbed(:user).decorate + expect(profile_index.totp_content).to eq 'account.index.auth_app_disabled' + end + end end end diff --git a/spec/views/accounts/show.html.slim_spec.rb b/spec/views/accounts/show.html.slim_spec.rb index 12c0cb330ce..5448af70854 100644 --- a/spec/views/accounts/show.html.slim_spec.rb +++ b/spec/views/accounts/show.html.slim_spec.rb @@ -100,7 +100,7 @@ context 'when current user has pending_profile' do before do - allow(decorated_user).to receive(:pending_profile).and_return(true) + allow(decorated_user).to receive(:pending_profile).and_return(build(:profile)) end it 'contains a link to activate profile' do