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
10 changes: 6 additions & 4 deletions app/controllers/users/two_factor_authentication_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ def phone_configuration
end

def validate_otp_delivery_preference_and_send_code
delivery_preference = phone_configuration.delivery_preference
result = otp_delivery_selection_form.submit(otp_delivery_preference: delivery_preference)
analytics.track_event(Analytics::OTP_DELIVERY_SELECTION, result.to_h)

Expand All @@ -57,6 +56,10 @@ def validate_otp_delivery_preference_and_send_code
end
end

def delivery_preference
phone_configuration&.delivery_preference || current_user.otp_delivery_preference
end

def update_otp_delivery_preference_if_needed
OtpDeliveryPreferenceUpdater.new(
user: current_user,
Expand All @@ -67,8 +70,7 @@ def update_otp_delivery_preference_if_needed

def handle_invalid_otp_delivery_preference(result)
flash[:error] = result.errors[:phone].first
preference = current_user.phone_configuration.delivery_preference
redirect_to login_two_factor_url(otp_delivery_preference: preference)
redirect_to login_two_factor_url(otp_delivery_preference: delivery_preference)
end

def invalid_phone_number(exception, action:)
Expand Down Expand Up @@ -179,7 +181,7 @@ def delivery_params
end

def phone_to_deliver_to
return current_user.phone_configuration.phone if authentication_context?
return phone_configuration&.phone if authentication_context?

user_session[:unconfirmed_phone]
end
Expand Down
3 changes: 2 additions & 1 deletion app/models/concerns/user_encrypted_attribute_overrides.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ def find_with_email(email)

email = email.downcase.strip
email_fingerprint = create_fingerprint(email)
find_by(email_fingerprint: email_fingerprint)
resource = find_by(email_fingerprint: email_fingerprint)
resource if resource&.email == email
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.

This seems like an odd change. Why is it necessary, and how is this related to phone configurations?

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.

It is a result of the changes to the UpdateUser service. With this change, we're not decrypting the phone number there (good) but that means we aren't raising an attribute encryption error if there's an issue decrypting attributes. So, a user can sign in without needing any attributes decrypted and a seemingly unrelated 500 pops up further down the line.

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.

Do you mean we aren't raising an error when trying to encrypt the phone when updating the user? Why would we need to decrypt attributes when setting them?

It seems strange to me that we are looking for a match twice. In what case would there be a match for the email fingerprint but resource.email would not equal email? This new code is 3.62x slower than the old code, and this is a heavily used method in the app.

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.

Digging into this, I can confirm this code is not needed. This is in fact masking a bug in our SessionsController. I will open a new PR and explain everything there.

end

def create_fingerprint(email)
Expand Down
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class User < ApplicationRecord
self.ignored_columns = %w[
encrypted_password password_salt password_cost encryption_key
recovery_code recovery_cost recovery_salt
encrypted_phone phone_confirmed_at
]

include NonNullUuid
Expand All @@ -22,7 +23,6 @@ class User < ApplicationRecord

include EncryptableAttribute

encrypted_attribute(name: :phone)
encrypted_attribute(name: :otp_secret_key)
encrypted_attribute_without_setter(name: :email)

Expand Down
5 changes: 4 additions & 1 deletion app/services/pii/cacher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ def rotate_fingerprints(profile)

def rotate_encrypted_attributes
KeyRotator::AttributeEncryption.new(user).rotate
phone_configuration = user.phone_configuration
return if phone_configuration.blank?
KeyRotator::AttributeEncryption.new(phone_configuration).rotate
end

def stale_fingerprints?(profile)
Expand All @@ -49,7 +52,7 @@ def stale_email_fingerprint?
end

def stale_attributes?
user.stale_encrypted_phone? || user.stale_encrypted_email? ||
user.phone_configuration&.stale_encrypted_phone? || user.stale_encrypted_email? ||
user.stale_encrypted_otp_secret_key?
end

Expand Down
41 changes: 0 additions & 41 deletions app/services/populate_phone_configurations_table.rb

This file was deleted.

16 changes: 5 additions & 11 deletions app/services/update_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def initialize(user:, attributes:)
end

def call
result = user.update!(attributes)
result = user.update!(attributes.except(:phone, :phone_confirmed_at))
manage_phone_configuration
result
end
Expand All @@ -23,13 +23,7 @@ def manage_phone_configuration
end

def update_phone_configuration
configuration = user.phone_configuration
if phone_attributes[:phone].present?
configuration.update!(phone_attributes)
else
configuration.destroy
user.reload
end
user.phone_configuration.update!(phone_attributes)
end

def create_phone_configuration
Expand All @@ -39,10 +33,10 @@ def create_phone_configuration

def phone_attributes
@phone_attributes ||= {
phone: attribute(:phone),
confirmed_at: attribute(:phone_confirmed_at),
phone: attributes[:phone],
confirmed_at: attributes[:phone_confirmed_at],
delivery_preference: attribute(:otp_delivery_preference),
}
}.delete_if { |_, value| value.nil? }
end

# This returns the named attribute if it's included in the changes, even if
Expand Down
4 changes: 1 addition & 3 deletions lib/tasks/create_test_accounts.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ def create_account(email: 'joe.smith@email.com', password: 'salty pickles', mfa_
user = User.create!(email: email)
user.skip_confirmation!
user.reset_password(password, password)
user.phone = mfa_phone || phone
user.phone_confirmed_at = Time.zone.now
user.save!
user.create_phone_configuration(
phone: mfa_phone || phone,
confirmed_at: user.phone_confirmed_at,
confirmed_at: Time.zone.now,
delivery_preference: user.otp_delivery_preference
)
Event.create(user_id: user.id, event_type: :account_created)
Expand Down
16 changes: 5 additions & 11 deletions lib/tasks/dev.rake
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,11 @@ namespace :dev do
user.encrypted_email = args[:ee].encrypted
user.skip_confirmation!
user.reset_password(args[:pw], args[:pw])
user.phone = format('+1 (415) 555-%04d', args[:num])
user.phone_confirmed_at = Time.zone.now
create_phone_configuration_for(user)
user.create_phone_configuration(
delivery_preference: user.otp_delivery_preference,
phone: format('+1 (415) 555-%04d', args[:num]),
confirmed_at: Time.zone.now
)
Event.create(user_id: user.id, event_type: :account_created)
end

Expand All @@ -105,12 +107,4 @@ namespace :dev do
def fingerprint(email)
Pii::Fingerprinter.fingerprint(email)
end

def create_phone_configuration_for(user)
user.create_phone_configuration(
phone: user.phone,
confirmed_at: user.phone_confirmed_at,
delivery_preference: user.otp_delivery_preference
)
end
end
7 changes: 0 additions & 7 deletions lib/tasks/migrate_phone_configurations.rake

This file was deleted.

2 changes: 1 addition & 1 deletion spec/controllers/account_recovery_setup_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

context 'user is piv_cac enabled but not phone enabled' do
it 'redirects to account_url' do
user = build(:user, :signed_up, :with_piv_or_cac, phone: nil)
user = build(:user, :signed_up, :with_piv_or_cac, with: { mfa_enabled: false })
stub_sign_in(user)

get :index
Expand Down
32 changes: 23 additions & 9 deletions spec/controllers/idv/phone_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
end

describe '#new' do
let(:user) { build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now) }
let(:user) do
build(:user, :with_phone,
with: { phone: good_phone, confirmed_at: Time.zone.now })
end

before do
stub_verify_steps_one_and_two(user)
Expand Down Expand Up @@ -64,7 +67,7 @@
describe '#create' do
context 'when form is invalid' do
before do
user = build(:user, phone: '+1 (415) 555-0130')
user = build(:user, :with_phone, with: { phone: '+1 (415) 555-0130' })
stub_verify_steps_one_and_two(user)
stub_analytics
allow(@analytics).to receive(:track_event)
Expand Down Expand Up @@ -105,7 +108,7 @@
end

it 'tracks event with valid phone' do
user = build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now)
user = build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now })
stub_verify_steps_one_and_two(user)

put :create, params: { idv_phone_form: { phone: good_phone } }
Expand All @@ -124,7 +127,9 @@

context 'when same as user phone' do
it 'redirects to result page and sets phone_confirmed_at' do
user = build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now)
user = build(:user, :with_phone, with: {
phone: good_phone, confirmed_at: Time.zone.now
})
stub_verify_steps_one_and_two(user)

put :create, params: { idv_phone_form: { phone: good_phone } }
Expand All @@ -141,7 +146,9 @@

context 'when different from user phone' do
it 'redirects to otp page and does not set phone_confirmed_at' do
user = build(:user, phone: '+1 (415) 555-0130', phone_confirmed_at: Time.zone.now)
user = build(:user, :with_phone, with: {
phone: '+1 (415) 555-0130', confirmed_at: Time.zone.now
})
stub_verify_steps_one_and_two(user)

put :create, params: { idv_phone_form: { phone: good_phone } }
Expand All @@ -159,7 +166,9 @@
end

describe '#show' do
let(:user) { build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now) }
let(:user) do
build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now })
end
let(:params) { { phone: good_phone } }

before do
Expand Down Expand Up @@ -232,7 +241,9 @@
end

let(:params) { { phone: bad_phone } }
let(:user) { build(:user, phone: bad_phone, phone_confirmed_at: Time.zone.now) }
let(:user) do
build(:user, :with_phone, with: { phone: bad_phone, confirmed_at: Time.zone.now })
end

it 'tracks event with invalid phone' do
stub_analytics
Expand All @@ -257,7 +268,9 @@

context 'attempt window has expired, previous attempts == max-1' do
let(:two_days_ago) { Time.zone.now - 2.days }
let(:user) { build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now) }
let(:user) do
build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now })
end

before do
user.idv_attempts = max_attempts - 1
Expand All @@ -275,7 +288,8 @@
end

it 'passes the normalized phone to the background job' do
user = build(:user, phone: good_phone, phone_confirmed_at: Time.zone.now)
user = build(:user, :with_phone, with: { phone: good_phone, confirmed_at: Time.zone.now })

stub_verify_steps_one_and_two(user)

subject.params = { phone: normalized_phone }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
end

it 'tracks the page visit and context' do
user = build_stubbed(:user, phone: '+1 (703) 555-0100')
user = build_stubbed(:user, :with_phone, with: { phone: '+1 (703) 555-0100' })
stub_sign_in_before_2fa(user)

stub_analytics
Expand Down Expand Up @@ -322,8 +322,6 @@
end

it 'does not update user phone or phone_confirmed_at attributes' do
expect(subject.current_user.phone).to eq('+1 202-555-1212')
expect(subject.current_user.phone_confirmed_at).to eq(@previous_phone_confirmed_at)
expect(subject.current_user.phone_configuration.phone).to eq('+1 202-555-1212')
expect(
subject.current_user.phone_configuration.confirmed_at
Expand Down Expand Up @@ -355,8 +353,6 @@

context 'when user does not have an existing phone number' do
before do
subject.current_user.phone = nil
subject.current_user.phone_confirmed_at = nil
subject.current_user.phone_configuration.destroy
subject.current_user.phone_configuration = nil
subject.current_user.create_direct_otp
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
let(:payload) { { personal_key_form: personal_key } }

before do
stub_sign_in_before_2fa(build(:user, phone: '+1 (703) 555-1212'))
stub_sign_in_before_2fa(build(:user, :with_phone, with: { phone: '+1 (703) 555-1212' }))
form = instance_double(PersonalKeyForm)
response = FormResponse.new(
success: false, errors: {}, extra: { multi_factor_auth_method: 'personal key' }
Expand All @@ -100,7 +100,7 @@

context 'when the user enters an invalid personal key' do
before do
stub_sign_in_before_2fa(build(:user, phone: '+1 (703) 555-1212'))
stub_sign_in_before_2fa(build(:user, :with_phone, with: { phone: '+1 (703) 555-1212' }))
form = instance_double(PersonalKeyForm)
response = FormResponse.new(
success: false, errors: {}, extra: { multi_factor_auth_method: 'personal key' }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
describe TwoFactorAuthentication::PivCacVerificationController do
let(:user) do
create(:user, :signed_up, :with_piv_or_cac,
phone: '+1 (703) 555-0000')
with: { phone: '+1 (703) 555-0000' })
end

let(:nonce) { 'once' }
Expand Down
Loading