Skip to content

Allow user to verify phone OTP after signing out#1369

Merged
monfresh merged 1 commit intomasterfrom
pek-unfinished-idv-phone-otp
May 11, 2017
Merged

Allow user to verify phone OTP after signing out#1369
monfresh merged 1 commit intomasterfrom
pek-unfinished-idv-phone-otp

Conversation

@pkarman
Copy link
Contributor

@pkarman pkarman commented Apr 13, 2017

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.

@pkarman pkarman self-assigned this Apr 13, 2017
@pkarman pkarman force-pushed the pek-unfinished-idv-phone-otp branch from 02f8b0b to 24a123f Compare April 14, 2017 14:29
@pkarman pkarman changed the title [WIP] Allow user to verify phone OTP after signing out Allow user to verify phone OTP after signing out Apr 14, 2017
Copy link
Contributor

Choose a reason for hiding this comment

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

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/

Copy link
Contributor

Choose a reason for hiding this comment

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

put this below the methods that use it?

Copy link
Contributor

Choose a reason for hiding this comment

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

niiice

Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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

Copy link
Contributor

Choose a reason for hiding this comment

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

thanks for clarifying -- maybe something not as crazy verbose as your example but more verbose would be helpful: profile_or_verify_profile_path ?

Copy link
Contributor

Choose a reason for hiding this comment

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

what's this change about?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just memoizing consistently with other controllers.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think @zachmargolis likes it when we keep this RSpec for good reasons that I myself cannot articulate on this fine Friday afternoon

Copy link
Contributor

Choose a reason for hiding this comment

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

why is there so much more stubbing here than in spec/controllers/users/verify_profile_phone_controller_spec.rb ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

cool - perhaps an opp to create a helper method with all that stubbing and re-use it for each file?

Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it could be verify_profile_path or verify_profile_phone_path depending what is required to finish verifying the profile.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

CC says this is not tested.

Copy link
Contributor

Choose a reason for hiding this comment

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

CC says this is not tested.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

CC says this is not tested.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we also need an index?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

not seeing specs for either of these smaller methods

Copy link
Contributor

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

good call @monfresh -- added specs and found some bugs in the process.

@pkarman pkarman force-pushed the pek-unfinished-idv-phone-otp branch from 803014c to cb45671 Compare April 20, 2017 19:03
.reek Outdated
max_statements: 6
exclude:
- Users::PhoneConfirmationController
- OpenidConnect::AuthorizationController
Copy link
Contributor

Choose a reason for hiding this comment

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

(ノಠ益ಠ)ノ彡 ǝʇɐɯıʃɔǝpoɔ
(ノಠ益ಠ)ノ彡 ʞǝǝɹ

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok I have added the class length + method count exclusions in lieu of the method length error.

@pkarman
Copy link
Contributor Author

pkarman commented Apr 21, 2017

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
Copy link
Contributor

Choose a reason for hiding this comment

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

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
Copy link
Contributor

@monfresh monfresh Apr 21, 2017

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment here about stubbing pending_profile_requires_verification?.

@zachmargolis
Copy link
Contributor

@monfresh since Peter has rotated off the team and gone on vacation, do you want to just update the branch with your latest feedback?

@monfresh
Copy link
Contributor

Yes, will update.

@zachmargolis zachmargolis assigned monfresh and unassigned pkarman May 2, 2017
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
Copy link
Contributor

Choose a reason for hiding this comment

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

@monfresh for consistency, we've started renaming all the /profile URLs to /account and setting up redirects for old ones (see #1407). Since this is brand-new can we rename it to /account/verify_phone before it lands? (Also appears there are merge conflicts related to the rename....oops)

@monfresh monfresh force-pushed the pek-unfinished-idv-phone-otp branch 2 times, most recently from d8f9726 to ff26e9f Compare May 10, 2017 18:43
@monfresh
Copy link
Contributor

I incorporated my feedback in this PR, squashed, and rebased with master. Now I need to address the failing specs.

@monfresh monfresh force-pushed the pek-unfinished-idv-phone-otp branch 2 times, most recently from e84d123 to 42e3a33 Compare May 10, 2017 20:23
@monfresh
Copy link
Contributor

@zachmargolis This is good to go now. Please re-review. Thanks!

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 not blockers LGTM. Thanks for taking the time to clean all this up!

Copy link
Contributor

Choose a reason for hiding this comment

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

now that we've made the name account for the default page, should we call it account_or_verify_profile_route?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I thought about that. I can change it.

Copy link
Contributor

Choose a reason for hiding this comment

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

What if we made this !!phone_confirmed ?

The reason is passing an explicit false here will result in true:

!false.nil?
# => true
!!false
# => false

Copy link
Contributor

Choose a reason for hiding this comment

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

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?

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 used to !!value as a convention, but if you feel strongly, value || false works too

Copy link
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Contributor

Choose a reason for hiding this comment

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

¯\_(ツ)_/¯ 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)

@monfresh monfresh force-pushed the pek-unfinished-idv-phone-otp branch from 42e3a33 to 67fee0d Compare May 11, 2017 18:42
**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.
@monfresh monfresh force-pushed the pek-unfinished-idv-phone-otp branch from 67fee0d to f8233b7 Compare May 11, 2017 20:53
@monfresh monfresh merged commit c12fdeb into master May 11, 2017
@monfresh monfresh deleted the pek-unfinished-idv-phone-otp branch May 11, 2017 21:17
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.

4 participants