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

Allow multiple USPS confirmation codes #1661

Merged
merged 1 commit into from
Sep 8, 2017

Conversation

jmhooper
Copy link
Member

@jmhooper jmhooper commented Sep 7, 2017

Why: So that when a user resends a letter, it does not invalidate
the USPS confirmation code in preceding letters. This means that if a
user resends a letter before receiving their first letter, the code in
the first letter can still be used to verify their account.

How: This commit adds a new model named UspsConfirmationCode. This
model is associated with a profile and has an asymmetrically encrypted
OTP. When the user enters an OTP, the record with the matching OTP
fingerprint is found. This is used to determine whether the entered OTP
is valid for verifying their account.

@@ -9,7 +9,7 @@ def index
@verify_account_form = VerifyAccountForm.new(user: current_user)

return unless FeatureManagement.reveal_usps_code?
@code = JSON.parse(user_session[:decrypted_pii])['otp']['raw']
@code = session[:last_usps_confirmation_code]
Copy link
Member Author

Choose a reason for hiding this comment

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

I kept OTP prefilling by sticking the OTP in the session. It's a little bit brittle, so I also considered displaying it in flash message on the come back later screen. No problem making it work that way if we think that's better.

@@ -0,0 +1,16 @@
class UspsConfirmationCode < ApplicationRecord
Copy link
Member Author

Choose a reason for hiding this comment

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

I am definitely not in love with this name. It is a bit confusing since we have a UspsConfirmation model. Happy to change it if someone has a better idea.

belongs_to :profile

def self.find_with_otp(otp)
find do |usps_confirmation_code|
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm super unfamiliar with find with a block --- is this a table scan?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh I see this is used on a relation -- makes wayyyy more sense, but still used the wrong way could be a table scan.

Do you think it would be clearer if we made this a scope? Same result but hopefully it would make it more obvious to users that it's suppsed to be combined with other scopes and not just be a finder mehtod like our others (that are used for point queries)

scope :first_with_otp, ->(otp) { to_a.find { |usps_confirmation_code| ... } }

Copy link
Member Author

Choose a reason for hiding this comment

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

Good idea 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, this is turning out to be problematic since scopes have to return a relation and find returns a record 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

ah dangit, my bad. maybe just a rename to first_with_otp will make it clearer it's not a point query

Copy link
Member Author

Choose a reason for hiding this comment

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

Done ✅

Copy link
Contributor

@zachmargolis zachmargolis left a comment

Choose a reason for hiding this comment

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

Some small questions, but no blockers, LGTM!

This does not include the logic about "only allow 3 in flight at a time" or anything, and we can add that later, right?

confirmation_maker.perform

return unless FeatureManagement.reveal_usps_code?
session[:last_usps_confirmation_code] = confirmation_maker.otp
Copy link
Contributor

Choose a reason for hiding this comment

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

CodeClimate is reporting this as not tested, do you think we should add test coverage? Since we expect to use this in our lower envs & development?

Copy link
Member Author

Choose a reason for hiding this comment

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

👍. I'll write up a test

Copy link
Member Author

Choose a reason for hiding this comment

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

@zachmargolis: I actually found a bug writing the test (yay!) and I had to do some wacky things in the idv review controller to surface the OTP and put in the session. Mind taking another look to lmk what you think?

create_table :usps_confirmation_codes do |t|
t.integer :profile_id, null: false
t.string :otp_fingerprint, null: false
t.datetime :code_sent_at, null: false, default: ->{ 'CURRENT_TIMESTAMP' }
Copy link
Contributor

Choose a reason for hiding this comment

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

does this need to be a function? CURRENT_TIMESTAMP() ? Also does it need to be a proc or can it be a literal value?

Copy link
Member Author

Choose a reason for hiding this comment

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

CURRENT_TIMESTAMP is the Postgres syntax. It ends up becoming now() in the schema. We have to use the proc because otherwise Rails tries to treat it like a primitive and sets the default to 'CURRENT_TIMESTAMP' instead of CURRENT_TIMESTAMP.

ref: rails/rails#27077


factory :usps_confirmation_code do
profile
sequence(:otp_fingerprint) { |n| Pii::Fingerprinter.fingerprint(n.to_s.rjust(6, '0')) }
Copy link
Contributor

Choose a reason for hiding this comment

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

there's no unique index ... would it be easier it we just fingerprinted a fixed value like '000000' or something? And we can override in tests where we need a specific value?

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch. Originally I had a uniqueness constraint on this column before I realized that was a bad idea and this is leftover from that.

Copy link
Contributor

@zachmargolis zachmargolis left a comment

Choose a reason for hiding this comment

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

LGTM!

**Why**: So that when a user resends a letter, it does not invalidate
the USPS confirmation code in preceding letters. This means that if a
user resends a letter before receiving their first letter, the code in
the first letter can still be used to verify their account.

**How**: This commit adds a new model named `UspsConfirmationCode`. This
model is associated with a profile and has an asymmetrically encrypted
OTP. When the user enters an OTP, the record with the matching OTP
fingerprint is found. This is used to determine whether the entered OTP
is valid for verifying their account.
@jmhooper jmhooper force-pushed the jmhooper-remove-otp-from-encrypted-attributes branch from 5cdf174 to 5c3ea90 Compare September 8, 2017 20:50
@jmhooper jmhooper merged commit b1bcd64 into master Sep 8, 2017
@jmhooper jmhooper deleted the jmhooper-remove-otp-from-encrypted-attributes branch December 12, 2017 20:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants