Allow user to verify phone OTP after signing out#1369
Conversation
02f8b0b to
24a123f
Compare
There was a problem hiding this comment.
public_send instead?
see
TO SEND OR TO PUBLIC SEND? THAT IS THE QUESTION.
via http://vaidehijoshi.github.io/blog/2015/05/05/metaprogramming-dynamic-methods-using-public-send/
There was a problem hiding this comment.
put this below the methods that use it?
app/services/idv/session.rb
Outdated
There was a problem hiding this comment.
wondering if there might be better names for this. When I first saw it in this PR, I was very curious about what finishing a profile means. Perhaps finish_verifying_profile_path instead?
There was a problem hiding this comment.
I agree about the naming.
The original method was just profile_path and I needed something more nuanced. Maybe it's the profile_path. Maybe it's some derivation of profile_path depending on the status of your Profile record (pending vs active). If it's pending, then the route shunts you to finish verifying your profile, making it active.
So it isn't necessarily finish_verifying_profile_path. It's more like, find_the_next_path_either_to_profile_or_to_finish_verifying_profile
There was a problem hiding this comment.
thanks for clarifying -- maybe something not as crazy verbose as your example but more verbose would be helpful: profile_or_verify_profile_path ?
There was a problem hiding this comment.
what's this change about?
There was a problem hiding this comment.
just memoizing consistently with other controllers.
There was a problem hiding this comment.
I think @zachmargolis likes it when we keep this RSpec for good reasons that I myself cannot articulate on this fine Friday afternoon
There was a problem hiding this comment.
why is there so much more stubbing here than in spec/controllers/users/verify_profile_phone_controller_spec.rb ?
There was a problem hiding this comment.
I was trying to avoid create(:user) and create(:profile) per @monfresh's quest to hit the db minimally. It means lots more stubbing required.
The other controller is not so honorable and just creates the db data.
There was a problem hiding this comment.
cool - perhaps an opp to create a helper method with all that stubbing and re-use it for each file?
There was a problem hiding this comment.
If I'm reading the logic correctly, if needs_profile_finish is true, then profile_or_verify_profile_url will always be verify_profile_route, so might as well just go there directly without going through the logic, right?
There was a problem hiding this comment.
it could be verify_profile_path or verify_profile_phone_path depending what is required to finish verifying the profile.
There was a problem hiding this comment.
Yep, and that logic comes from verify_profile_route, right? I'm saying call the verify_profile_route method directly instead of going through the logic in profile_or_verify_profile_url, which will always return verify_profile_route in this scenario.
There was a problem hiding this comment.
We also need this new logic in OpenidConnect::AuthorizationController. Since both SAML and OIDC need to be kept in sync, I wonder if it's time to extract the functionality so it can automatically be shared. Not in this PR, but soon.
There was a problem hiding this comment.
CC says this is not tested.
There was a problem hiding this comment.
CC says this is not tested.
There was a problem hiding this comment.
I'm not sure there's a way, under the current business rules, to construct logic to ever reach that fallback profile return value. I put it there so that there is sane default should logical changes ever happen elsewhere. Alternately, we could throw an exception if we get to that point, since there's no logical reason to reach it.
There was a problem hiding this comment.
I would vote for removing it entirely. If there's no logical reason to reach it, and there's no sane test we can write for it, why have it?
There was a problem hiding this comment.
I would rather have an exception than drop it entirely. Otherwise, it's like a series of if/elsif with no final else -- which leaves open the possibility that someone changes the logic elsewhere and we end up breaking this routing logic.
There was a problem hiding this comment.
I don't think an explicit exception is necessary. The final else will be nil if we remove this last line. If somehow this nil is reached, an error will be thrown automatically. As long as we have robust tests, it should protect us from someone breaking this logic.
app/view_models/profile_index.rb
Outdated
There was a problem hiding this comment.
CC says this is not tested.
There was a problem hiding this comment.
Do we also need an index?
There was a problem hiding this comment.
IME the index is only required if you intend to search or sort on a column. Otherwise it adds extraneous db overhead to keep the index updated. We only ever check the value of this flag after we've searched by user_id.
app/decorators/user_decorator.rb
Outdated
There was a problem hiding this comment.
wow! the logic in here is sufficiently complicated that I wonder if we should consider breaking out a separate class. If not now, maybe l8r.
app/decorators/user_decorator.rb
Outdated
There was a problem hiding this comment.
not seeing specs for either of these smaller methods
There was a problem hiding this comment.
What about the finish_profile: true scenario? Maybe also add a feature spec in spec/features/saml/loa3_sso_spec.rb to test this whole flow?
There was a problem hiding this comment.
good call @monfresh -- added specs and found some bugs in the process.
803014c to
cb45671
Compare
.reek
Outdated
| max_statements: 6 | ||
| exclude: | ||
| - Users::PhoneConfirmationController | ||
| - OpenidConnect::AuthorizationController |
There was a problem hiding this comment.
(ノಠ益ಠ)ノ彡 ǝʇɐɯıʃɔǝpoɔ
(ノಠ益ಠ)ノ彡 ʞǝǝɹ
There was a problem hiding this comment.
If I had to choose between too many statements and too many methods/class too long, I would prefer smaller methods and then later take a look at the class and see if it really is doing too much.
There was a problem hiding this comment.
ok I have added the class length + method count exclusions in lieu of the method length error.
|
This needs an official GH review |
| def index | ||
| return confirm_two_factor_authenticated(request_id) unless user_fully_authenticated? | ||
| return redirect_to verify_url if identity_needs_verification? | ||
| return if idv_needed_or_in_process |
There was a problem hiding this comment.
I would expect this method to return true or false and for it to end in a question mark, but this method is redirecting, which is confusing. When I see return if [some condition is true], I expect the method to be exited without any side effects.
Maybe something like this?
return redirect_to_profile_or_verify_profile_url if profile_or_identity_needs_verification?
def redirect_to_profile_or_verify_profile_url
return redirect_to profile_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| end | ||
| end | ||
|
|
||
| context 'phone is same as 2FA' do |
There was a problem hiding this comment.
I don't see any difference between this test and the one above. Did you mean to only set the profile_phone equal to the user phone in this test, and not in the test above as well? That way, we can test each scenario independently if it makes a difference.
| expect(user_decorator.pending_profile_requires_verification?).to eq false | ||
| end | ||
|
|
||
| it 'returns true when no active profile exists' do |
There was a problem hiding this comment.
This spec title confused me at first. I thought the user did not have any profiles at all. Since there are 3 different reasons why a profile can be deactivated, how about a more specific title like "returns true when the user has a deactivated profile due to pending verification"?
And then please add 2 more tests to verify that if the deactivated_reason is password_reset or encryption_error, then this returns false.
There was a problem hiding this comment.
I just realized there is a conditional in the decorator that checks if identity_not_verified?, which I think this test was meant for, but the test set up seems to be testing something else.
To test the identity_not_verified? conditional, I would create a profile with active: false, as opposed to one with a deactivation_reason.
To test the scenario where a user has a pending_profile, I would perhaps stub the decorator to receive pending_profile and return true, and then add more tests to the #pending_profile section to test the 2 other deactivation_reason scenarios.
There was a problem hiding this comment.
Also, what do you think about using build_stubbed instead of create for the profiles to speed up the tests?
| it 'returns true' do | ||
| expect(user_decorator.needs_profile_phone_verification?).to eq true | ||
| end | ||
| end |
There was a problem hiding this comment.
Since the pending_profile_requires_verification? method is unit tested above, and since the needs_profile_phone_verification? method depends on that method, what do you think about stubbing the decorator to receive pending_profile_requires_verification? with true and false and testing those scenarios, as opposed to creating a profile with a deactivated_reason. This would make this test less brittle.
| it 'returns false' do | ||
| expect(user_decorator.needs_profile_usps_verification?).to eq false | ||
| end | ||
| end |
There was a problem hiding this comment.
Same comment here about stubbing pending_profile_requires_verification?.
|
@monfresh since Peter has rotated off the team and gone on vacation, do you want to just update the branch with your latest feedback? |
|
Yes, will update. |
config/routes.rb
Outdated
| post '/profile/reactivate' => 'users/reactivate_profile#create' | ||
| get '/profile/verify' => 'users/verify_profile#index', as: :verify_profile | ||
| post '/profile/verify' => 'users/verify_profile#create' | ||
| get '/profile/verify_phone' => 'users/verify_profile_phone#index', as: :verify_profile_phone |
There was a problem hiding this comment.
d8f9726 to
ff26e9f
Compare
|
I incorporated my feedback in this PR, squashed, and rebased with master. Now I need to address the failing specs. |
e84d123 to
42e3a33
Compare
|
@zachmargolis This is good to go now. Please re-review. Thanks! |
zachmargolis
left a comment
There was a problem hiding this comment.
Some small questions, but not blockers LGTM. Thanks for taking the time to clean all this up!
There was a problem hiding this comment.
now that we've made the name account for the default page, should we call it account_or_verify_profile_route?
There was a problem hiding this comment.
Yeah, I thought about that. I can change it.
app/services/idv/profile_maker.rb
Outdated
There was a problem hiding this comment.
What if we made this !!phone_confirmed ?
The reason is passing an explicit false here will result in true:
!false.nil?
# => true
!!false
# => falseThere was a problem hiding this comment.
phone_confirmation in Idv::Session can either be nil or true. How about we force it to false if nil either when we call ProfileMaker in IdVSession, or change line 10 above to phone_confirmed || false?
There was a problem hiding this comment.
I'm used to !!value as a convention, but if you feel strongly, value || false works too
There was a problem hiding this comment.
I find that the double bang requires more mental effort. It's also discouraged by the Ruby Style Guide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang
There was a problem hiding this comment.
¯\_(ツ)_/¯ up to you. I like the Ruby Style Guide as a suggestion, but I definitely disagree with it on certain parts (ex about the readability of unless)
42e3a33 to
67fee0d
Compare
**Why**: If a user completes the vendor verification process but has not yet verified possession of a new phone number via IdV, the user is prompted at next sign-in to verify their phone. This sets up a parallel process to USPS verification, where there is some indefinite amount of time where a profile is in a pending state while the user verifies physical access to their confirmed address (street or phone). **How**: Adds a new boolean column to the `Profile` table called `phone_confirmed` that indicates whether they have supplied a vendor-confirmed phone number as part of their PII. There are 2 steps for all asserted PII: confirmation with vendor, and verification via OTP. The 2nd step does not necessarily have to happen in the same IdV session, as it cannot with USPS delivery. Now phone supports the same scenario.
67fee0d to
f8233b7
Compare
Why: If a user completes the vendor verification process
but has not yet verified possession of a new phone number
via IdV, the user is prompted at next sign-in to verify their
phone. This sets up a parallel process to USPS verification,
where there is some indefinite amount of time where a profile
is in a pending state while the user verifies physical
access to their confirmed address (street or phone).
How: Adds a new boolean column to the
Profiletable calledphone_confirmedthat indicates whether they have supplieda vendor-confirmed phone number as part of their PII. There are
2 steps for all asserted PII: confirmation with vendor, and
verification via OTP. The 2nd step does not necessarily
have to happen in the same IdV session, as it cannot with
USPS delivery. Now phone supports the same scenario.