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

Gutenboarding: Add push 2fa support to auth store #40114

Merged
merged 8 commits into from
Mar 27, 2020

Conversation

p-jackson
Copy link
Member

@p-jackson p-jackson commented Mar 13, 2020

Changes proposed in this Pull Request

  • Add support for "push" 2nd login factor
  • Polls ever 5 seconds to see if push notification has been handed (this is the poll time currently used by /log-in)
  • Add downlevelIteration to TS config so we can use yield* in generator functions
  • Every time we poll update the nonce that we'll use for the next time we poll

"Push" means when we send a push notification to the WordPress app during login, and you click the button in the app to approve the login. Not the one where you have to end a code for the 2nd factor.

Testing instructions

  • Checkout branch locally
  • Install WordPress app on phone (I found I needed to enable notifications for the app, can always disable later 😄)
  • In console:
    • wp.auth.submitUsernameOrEmail('username') using an account that's logged into the mobile app
    • wp.auth.submitPassword('correctpassword')
    • Console will print that it's now waiting for 2FA
    • Go to your phone and approve the push notifcation
    • After a few seconds the console will update to say you're logged in
    • Also try running wp.auth.reset() while polling is happening, polling should stop

Should probably try a passwordless account with 2fa. I don't actually know if passwordless accounts can have a second factor 🤔 (we might ask them to pick a password)

@matticbot
Copy link
Contributor

@matticbot
Copy link
Contributor

matticbot commented Mar 13, 2020

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

App Entrypoints (~779 bytes added 📈 [gzipped])

name                   parsed_size           gzip_size
entry-gutenboarding        +2427 B  (+0.1%)     +630 B  (+0.1%)
entry-main                  +202 B  (+0.0%)      +49 B  (+0.0%)
entry-login                 +202 B  (+0.0%)      +50 B  (+0.0%)
entry-domains-landing       +202 B  (+0.0%)      +50 B  (+0.0%)

Common code that is always downloaded and parsed every time the app is loaded, no matter which route is used.

Legend

What is parsed and gzip size?

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@p-jackson p-jackson changed the base branch from update/add-wpcom-proxy-request-branch to master March 17, 2020 04:52
@p-jackson p-jackson force-pushed the add/auth-store-2fa-app branch from 923ffe6 to 027010d Compare March 17, 2020 04:52
@p-jackson p-jackson marked this pull request as ready for review March 17, 2020 05:28
@p-jackson p-jackson requested a review from a team as a code owner March 17, 2020 05:28
@p-jackson p-jackson requested a review from a team March 17, 2020 05:29
@p-jackson
Copy link
Member Author

Should probably try a passwordless account with 2fa. I don't actually know if passwordless accounts can have a second factor 🤔 (we might ask them to pick a password)

Turns out passwordless account can have a second factor! It won't impact gutenboarding though because you enter the 2nd factor after following the email link, so that's done at /log-in

Copy link
Contributor

@roo2 roo2 left a comment

Choose a reason for hiding this comment

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

I couldn't test it with the app on my phone (I got no google services 😅). I had a comment on the style of reusing variables and the login response type which I think should be a union type, and also error handling.

if ( ! response.success ) {
two_step_nonce = response.data.two_step_nonce;
yield wait( POLL_APP_PUSH_INTERVAL_SECONDS * 1000 );
}
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 we should have a timeout here, I'm not sure what length of time it should be. Is this code called as we wait for the user to acknowledge the push notification on their phone?

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree. I don't the current login has a timeout but it really should. It does handle errors by stopping the polling, and we should too.

packages/data-stores/src/auth/actions.ts Outdated Show resolved Hide resolved
packages/data-stores/src/auth/types.ts Outdated Show resolved Hide resolved
packages/data-stores/src/auth/actions.ts Outdated Show resolved Hide resolved
two_step_push_token: push_web_token,
} );

if ( ! response.success ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

There should also be error cases which indicate that the push authentication has really failed an will never succeed. I think you should be more specific with this check and then handle any other error's seperately

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree, I reckon this goes hand in hand with timing out too.

@andrewserong
Copy link
Member

Nice work so far @p-jackson! Following the steps in the testing instructions worked well for me.

I ran into an issue attempting to test when something doesn't quite work out right, like if a user doesn't have notifications switched on in the app, or accidentally clicks ignore, so we need to submit the password again.

The first time I attempted this, I appeared to get stuck in a situation where there were two lots of the while loop running checking the two-step-authentication-endpoint. So after I tapped Approve in the app, the other loop was still throwing a 403.

The steps to do this were:

  1. I had the WordPress app open but notifications weren't enabled
  2. I did wp.auth.submitPassword( myPassword );, realised that I didn't have the push notifications enabled so then
  3. I switched on notifications
  4. Called wp.auth.submitPassword( myPassword ); again
  5. Tapped Approve in the app
  6. Noticed the additional network requests firing in the Network tab in Chrome

I attempted to recreate these steps, but encountered slightly different behaviour. I tapped "Ignore" after the first password submit, and then after the second password submit, the network requests for checking the two-step-authentication-endpoint threw 400 errors instead of 403. The error response looks like:

{"success":false,"data":{"errors":[{"code":"empty_two_step_nonce","message":"two_step_nonce required."}]}}

Inspecting the network request that generated the 400, the request payload was missing the two_step_nonce param after the second submitPassword.

I've run out of time for today to investigate further, but I'm wondering if in the while loop it'd be worth trying to explore how it can check the loginFlowState as a means of breaking out of the endless loop? I'm not sure it needs a timeout, but would a mechanism for stopping the check / turning the check into kind of a singleton behaviour work?

This behaviour might be a little artificial of course, because we might handle things quite differently in the consuming code. Taking a look at the existing behaviour at /log-in if I get to the push notification screen with the app open, it asks me to verify in the app. If I click back, I get taken to the input field to enter my email address (not the password field). After entering my password, I get redirected to /log-in/sms. So the behaviour of dealing with the push notification appears to be closely tied to dealing with the other 2fa approaches, too 🤔

I'm sure you're well-aware of most of these complexities anyway, so apologies for the info dump! Happy to take a closer look again tomorrow.

@p-jackson
Copy link
Member Author

I've updated polling logic so if there's an API error the polling will stop and return the user to the password state. However if it's a network error (i.e. fetch throws) then it keeps polling. This copies what the current log in does, I think it makes sense that having a shoddy connection doesn't stop the polling.

I decided not to add a timeout for polling. How long is long enough? Does it take a user 5min to find their phone? 10min? Instead I've made sure the reset() action will stop the polling. That way if the user wants to go back and start login again they can.

@ramonjd
Copy link
Member

ramonjd commented Mar 19, 2020

Just tested and works as described for me.

How long is long enough?

I clicked on Ignore in my app, just to see what would happen.

It's hard to guesstimate how long is appropriate. Sometimes I'm fumbling around with my phone and it takes me 20 seconds just to unlock the thing.

Would it make sense to give them, say, 1 min, after which we could offer someway to resend the permission request?

@p-jackson
Copy link
Member Author

Would it make sense to give them, say, 1 min, after which we could offer someway to resend the permission request?

Yeah I was thinking something like this. The current UI has links like "I don't have access to my phone right now". So I think these sorts of things should actually be dealt with at the UI layer not automatically in the store.

@p-jackson p-jackson changed the title WIP: Add push 2fa support to auth store Add push 2fa support to auth store Mar 23, 2020
@p-jackson p-jackson added [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. and removed [Status] In Progress labels Mar 23, 2020
@p-jackson p-jackson changed the title Add push 2fa support to auth store Gutenboarding: Add push 2fa support to auth store Mar 23, 2020
@p-jackson p-jackson force-pushed the add/auth-store-2fa-app branch from 0aab026 to 9a56f30 Compare March 26, 2020 08:26
@p-jackson p-jackson requested a review from a team March 26, 2020 08:27
@@ -27,21 +27,30 @@ export const setupWpDataDebug = () => {
Site.register( clientCreds );

window.wp.auth = {};
let previousState = window.wp?.data.select( AUTH_STORE ).getLoginFlowState();
Copy link
Member

Choose a reason for hiding this comment

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

A question, because I'm not sure...

Does the optional chaining inwindow.wp? makes sense seeing as, in the line above we're assuming that wp is a property of window anyway? (window.wp.auth = {};)

Copy link
Member Author

Choose a reason for hiding this comment

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

Eagle eyes! No they're not needed, just a copy paste bug from when I copied code from inside the callback.

It is needed inside the callback because window.wp could be undefined again by the time the callback is called (according to the type checker). Given it's just devtools it's a case of do-what-you-need-to-do-to-shut-the-type-checker-up

Copy link
Member

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

This is working great for me now @p-jackson, I really like the elegant approach of handling both the wait and cancelling an existing poll via task ids. From testing in the console, it correctly errors out and stops polling when push authentication has been throttled, too.

Just left a couple of notes of tiny typos, but once they're fixed up, it's looking good to go to me!

packages/data-stores/src/auth/actions.ts Outdated Show resolved Hide resolved
packages/data-stores/src/auth/actions.ts Outdated Show resolved Hide resolved
@p-jackson
Copy link
Member Author

Thanks for the reviews. Merging to wrap up this round of the auth store work.

Copy link
Member

@andrewserong andrewserong left a comment

Choose a reason for hiding this comment

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

Nice work on all this! 🚢

@p-jackson p-jackson merged commit 3139a7f into master Mar 27, 2020
@p-jackson p-jackson deleted the add/auth-store-2fa-app branch March 27, 2020 01:45
@matticbot matticbot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Mar 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API Data [Goal] New Onboarding previously called Gutenboarding
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants