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

Change email dialog #22309

Merged
merged 29 commits into from May 15, 2018
Merged

Change email dialog #22309

merged 29 commits into from May 15, 2018

Conversation

islemaster
Copy link
Contributor

@islemaster islemaster commented May 10, 2018

Progress on the spec Email opt-in consent on account changes.

What's in this PR

This first change replaces the current email update flow on the account page with a new React dialog for changing email address.

Here's the flow for a teacher:
out

And for a student (we leave the email hidden on the actual account page):
out

Validation

Email is required (checked on client and server)
image

Email must be valid (checked on client and server)
image

Email must have changed (checked on client)
image

Email must be available (checked on server)
image

Password is required (checked on client and server)
image

Password must be correct (checked on server)
image

Error handling

An unexpected server error results in this message:
image

Unit-tested behaviors

ChangeEmailForm
  calls onChange
    ✔ when the email field changes
    ✔ when the password field changes
    ✔ when the email opt-in field changes
  calls onSubmit
    ✔ when the enter key is pressed in the email field
    ✔ when the enter key is pressed in the password field
    ✔ when the enter key is pressed on the opt-in field
    ✔ but not when other keys are pressed
    ✔ and not when the form is disabled
  when disabled
    ✔ the email field is disabled
    ✔ the password field is disabled
    ✔ the opt-in field is disabled
  focusOnAnError()
    ✔ does nothing if there are no validation errors
    ✔ focuses on the email field if there is an email validation error
    ✔ focuses on the password field if there is a password validation error
    ✔ focuses on the email field if there are both email and password validation errors
ChangeEmailModal
  ✔ disables everything and shows save text when saving
  ✔ shows unknown error text when an unknown error occurs
  ✔ calls handleCancel when clicking the cancel button
  validation
    ✔ checks that email is present
    ✔ checks that email is valid
    ✔ checks that email is not the same as the current email
    ✔ reports email server errors
    ✔ checks that password is present
    ✔ reports password server errors
    ✔ disables the submit button when validation errors are present
    ✔ enables the submit button form passes validation
  changes clear server errors
    ✔ on email
    ✔ on password
  onSubmitSuccess
    ✔ calls handleSubmit with the new email
  onSubmitFailure
    ✔ puts the dialog in UNKNOWN ERROR state if response has no JSON
    ✔ loads returned validation errors into dialog state

@islemaster islemaster requested a review from ewjordan May 11, 2018 01:25
@islemaster
Copy link
Contributor Author

Hey Eric. I've still got to do a little cleanup and write tests for this, but could you do an initial scan? I'm taking a somewhat unconventional approach here and maybe you can stop me from doing something dumb now before I spend a bunch of time cleaning it up. 😁

@islemaster islemaster changed the title [WIP] Change email dialog Change email dialog May 11, 2018
@islemaster
Copy link
Contributor Author

islemaster commented May 11, 2018

Also, security/optics question (@poorvasingal @jeremydstone):

Email must have changed (checked on client)
image

For students, this check sort of lets the user manually rainbow-table attack their own hashed email, guessing email addresses until they get this validation error. This not new from a security point of view: We're already rendering the hashed email to the client's DOM, and the user must be signed in to see this view (this is the "edit my account" page). But do we think this will look like a security problem to users that don't understand how hashed emails work? Do we care?

I personally think the improved user experience outweighs the imagined security concern, but thought I should bring it up.

@poorvasingal
Copy link
Contributor

I wonder if we need to care if the email address is the same? So basically, just end up doing a no-op if the email address is updated to the same as before? That way we can avoid this issue?

Other alternative is to not show the error until they hit the submit button.

@islemaster islemaster requested a review from breville May 11, 2018 18:21
@islemaster
Copy link
Contributor Author

  • @breville : Tests still coming soon, but this is ready for early review. There's some ugly action-at-a-distance stuff between the HAML view and JavaScript, I'd love advice on fixing that.

@breville
Copy link
Member

Oh, one other possibility is to not post via a form at all, but to use a more simple web API. While you wouldn't have the token, your server-side code can still authorize the user using CanCan. @aoby has expertise here.

@@ -61,8 +91,6 @@ $(document).ready(() => {
if (needToConfirmEmail) {
e.preventDefault();
showConfirmEmailModal(onCancelModal, onSubmitModal(e));
} else if ($('#user_email').length) {
prepareEmailData();
Copy link
Contributor

Choose a reason for hiding this comment

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

Are we sure that everything else works fine if this is removed? I don't really remember the logic here, and when prepareEmailData() needs to be called or not.

Copy link
Contributor

Choose a reason for hiding this comment

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

I presume this is ok because the email fields have been removed from the form, and are now handled by the React modal.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Andrew's got it: I took a pretty close look at this. I've removed the only #user_email element on this page to replace it with the uneditable email display and "Update" link, so $('#user_email').length would always evaluate false, making this dead code.

@@ -116,7 +116,11 @@ def respond_to_account_update(successfully_updated, flash_message_kind = :update
format.any {head :no_content}
else
format.html {render "edit", formats: [:html]}
format.any {head :unprocessable_entity}
format.any do
render status: :unprocessable_entity,
Copy link
Contributor

Choose a reason for hiding this comment

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

The opaque erroring here has bothered me for sooooo long, but I always forgot about it when I was working nearby - thank you for setting this up!

@ewjordan
Copy link
Contributor

As far as I'm aware, the way you've handled the HAML/JS stuff there is about as good as it can get while sticking with that method of updating (leaning on the autogenerated user update machinery). @breville's suggestion about using a Web API and handling auth through CanCan is interesting, and maybe worth looking into whether it's a better pattern for all of this stuff?

I don't really like the autogenerated Rails updaters at all, because they're opaque to debug, a bit too magical, and (especially) leave the configuration of business functionality inside a HAML view. But that may be a longer term thing to look into, depending how tough API-ifying it would be.

Copy link
Contributor

@aoby aoby left a comment

Choose a reason for hiding this comment

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

I only skimmed the UI part, but the Rails side LGTM. As @breville mentioned, and as I discussed offline w Brad, pulling this out into a separate API has tradeoffs: without a token, it will be arguably less secure in that it won't require updating through the UI (but will still require log in), and it will mean writing a new API controller. On the other hand, the submit logic will be cleaner, send the state directly over JSON instead of filling and submitting a hidden form. I don't have a strong preference.

@@ -61,8 +91,6 @@ $(document).ready(() => {
if (needToConfirmEmail) {
e.preventDefault();
showConfirmEmailModal(onCancelModal, onSubmitModal(e));
} else if ($('#user_email').length) {
prepareEmailData();
Copy link
Contributor

Choose a reason for hiding this comment

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

I presume this is ok because the email fields have been removed from the form, and are now handled by the React modal.

this._form.find('#change-email-modal_user_email').val(this.props.userAge < 13 ? '' : this.state.newEmail);
this._form.find('#change-email-modal_user_hashed_email').val(hashEmail(this.state.newEmail));
this._form.find('#change-email-modal_user_current_password').val(this.state.currentPassword);
// this._form.find('#user_email_opt_in').val(this.state.emailOptIn);
Copy link
Contributor

Choose a reason for hiding this comment

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

why commented out? Is this a future field?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. The whole reason we're converting this to a dialog is so that we can explicitly ask teachers to re-opt-in to marketing email when they change their email address. I started building this out, then realized just switching to a dialog for this flow was complicated enough, so I'll be adding the opt-in as a follow-on PR. I wasn't sure it was worth extracting the work I'd done so far though.

    ChangeEmailForm
      calls onChange
        ✔ when the email field changes
        ✔ when the password field changes
        ✔ when the email opt-in field changes
      calls onSubmit
        ✔ when the enter key is pressed in the email field
        ✔ when the enter key is pressed in the password field
        ✔ when the enter key is pressed on the opt-in field
        ✔ but not when other keys are pressed
        ✔ and not when the form is disabled
      when disabled
        ✔ the email field is disabled
        ✔ the password field is disabled
        ✔ the opt-in field is disabled
      focusOnAnError()
        ✔ does nothing if there are no validation errors
        ✔ focuses on the email field if there is an email validation error
        ✔ focuses on the password field if there is a password validation error
        ✔ focuses on the email field if there are both email and password validation errors
    ChangeEmailModal
      ✔ disables everything and shows save text when saving
      ✔ disables everything and shows save text when saving
      ✔ calls handleCancel when clicking the cancel button
      validation
        ✔ checks that email is present
        ✔ checks that email is valid
        ✔ checks that email is not the same as the current email
        ✔ reports email server errors
        ✔ checks that password is present
        ✔ reports password server errors
        ✔ disables the submit button when validation errors are present
        ✔ enables the submit button form passes validation
      changes clear server errors
        ✔ on email
        ✔ on password
@islemaster
Copy link
Contributor Author

PTAL!

I've done the additional cleanup I wanted to get to before merging this and added some unit test coverage of the React parts of this change.

I've extracted a lot of the rendering code out to a ChangeEmailForm display component, leaving ChangeEmailModal where most of the ugliness lives smaller and easier to understand.

I'm not submitting a UI test with this change because there's expected follow-on work immediately after it (the email opt-in work) and I'd like to save the end-to-end test until that's in place.

@islemaster
Copy link
Contributor Author

Just discovered that this change breaks the ability to upgrade a student account to a teacher account, because it depends on the email field that this PR removes.

My immediate follow-up work involves changing that "change account type" flow 😢 and maybe I need to combine that work with this one to not break anything?

@islemaster
Copy link
Contributor Author

Spoke with Poorva and we're okay with temporary disabling the ability for student accounts to upgrade to teacher accounts, for the day or two between these two features. We'll give our helpdesk a heads-up before we ship this change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants